chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)

- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
This commit is contained in:
admin committed 2026-10-10 23:13:22 +08:00
1 parent 30b46dbd0c
commit c1b5e4d966
735 files changed
+153192 -2415

No files matched your search

@@ -0,0 +1,99 @@
# S9 多租户 · **平台侧那一半** — 落地回执(2026-09-27)
> 执行棒回执。代码仓 = `D:\github\dsh_shenxian`(**不是**本工作区;本工作区只放回执)。
> 用户 2026-09-27 拍板契约(逐字):**「数据库 做 数据隔离就行 不需要单独建表建库,数据和实例都需要隔离」**
> 方案出处:`E:\ProgramData\AIProject\aliyun-dsh-server\交付物\功能打包到基础插件-分步实施方案-20260926.md` §S9(307–318 行)
## ① 结果
**已交付(接口可跑、有测试、门禁全绿)**:平台进程内的多租户**管理面 API** 四条 ——
租户列表、单租户详情、**确保隔离实例**、**回收隔离实例**。
**口径三件(照拍板落)**:
1. **数据隔离 = 逻辑隔离**:租户 = 既有账号(`users.id`,网名 `u:<id>`,与 `tenantNetworkOf` 同口径)。
⛔ 未建库、⛔ 未建表、⛔ 未加列、⛔ 未写迁移。
2. **实例隔离 = 每租户一份独立实例**:直接复用平台**已有**的按需编排(`launchForUser` → `supervisor.launch`/`supervisor.stop`)。
⛔ 没有另造第二套实例生命周期;⛔ 路由层不 spawn 进程、不 kill 进程。
3. **「创建隔离实例」这个动作落平台进程**:动作面 = 平台 HTTP 路由;插件包将来只放「界面 + 请求」。
⛔ 本棒未碰插件仓。
**配额按宿主算**:读数一律取 `supervisor.quotaInfo()`(= spawn 时写进 cgroup 的
`MemoryMax` / `--max-old-space-size` 的**同一份计算**)。⛔ 路由里没有任何配额常量可写死
⇒ 宿主规格一变,租户侧看到的数跟着变(用例 S9-② 就是这条的证伪实验)。
**未接入就如实说**:账单 / 用量 / 配额面缺失 / 成员读数缺失 ⇒ 一律 `state: 'not_connected'`
+ 原因话术;⛔ 不用 `0` 冒充、⛔ 不编数(用例 S9-④ 断言"不许出现编造字段")。
## ② 改动清单(绝对路径)
| 文件 | 性质 | 说明 |
|---|---|---|
| `D:\github\dsh_shenxian\src\web\routes\tenants.ts` | **新增** | 四条端点 + 数据源接缝(`TenantDbExtras`) |
| `D:\github\dsh_shenxian\src\web\server.ts` | 修改(2 处:import + `register`) | 挂载 `tenantRoutes` |
| `D:\github\dsh_shenxian\test\tenants.test.mjs` | **新增** | 10 个用例,`fastify.inject` + 注入桩,⛔ 不连真库、⛔ 不起进程 |
端点:
- `GET /api/admin/tenants` —— 每项含 标识 / 显示名 / 网名 / 角色 / **实例状态** / **配额读数** / **成员读数** / 创建时间 / 账单
- `GET /api/admin/tenants/:id` —— 成员列表 + 配额 + 用量(未接入)+ 账单(未接入)
- `POST /api/admin/tenants/:id/instance/ensure` —— **确保隔离实例**(复用 `launchForUser`)
- `POST /api/admin/tenants/:id/instance/reclaim` —— **回收隔离实例**(`supervisor.stop`)
四条**全部** `requireAdmin`(含跨用户归属信息 ⇒ 属管理面,与 `overlay-nodes.ts` / `admin-user-ops.ts` 同级)。
## ③ 门禁读数(真跑,命令 → 读数 → rc)
| 命令(cwd = `D:\github\dsh_shenxian`) | 读数 | rc |
|---|---|---|
| `npm run build` | `tsc -p tsconfig.json`,无输出 | **0** |
| `npm run typecheck` | `tsc -p tsconfig.json --noEmit`,无输出 | **0** |
| `node scripts/check-layering.mjs` | 扫描 131 个 .ts;现存违规 5 条(基线内 5 条);**✅ 无新增违规** | **0** |
| `node --test test/tenants.test.mjs` | `tests 10 / pass 10 / fail 0` | **0** |
测试覆盖(对应本棒验收点):
- S9-① 匿名 **401**、普通用户 **403**(四条端点全覆盖);租户不存在 ⇒ 具名 **404**
- S9-② 列表形状(含配额字段);**换一组宿主读数 ⇒ 端点读数跟着变**(证伪写死常量);成员读数接通后计数正确
- S9-③「确保」与「回收」**走的是 supervisor**(桩断言 `supervisor.launch` / `.stop` 被调用);
已在跑 ⇒ **409**、不是文件夹 ⇒ **400**、已停的租户 ⇒ `reclaimed: false`(⛔ 不假装成功)
- S9-④ 无数据源 ⇒ 账单 / 用量 / 配额 / 成员**全回 `not_connected`**,`count: null`,⛔ 无编造字段
## ④ 未达成项(具名)
1. **成员读数的真库查询未接**:`GET .../:id` 的成员面走可选 db 接缝 `listTenantMembers()`;
本仓 `DbAdapter` 目前**没有**这个只读查询 ⇒ 实部署会回 `state: 'not_connected'`
(**接口形状与计数语义已定、已测**,缺的只是 `src/db/adapter.ts` + `src/db/pg.ts` 里那条 SELECT 与真库用例)。
⚠️ 未擅自加:那属于"跑 SQL 的那一层",且真库用例要 `--test-concurrency=1`。
2. **宿主台账同理**:`listDshHosts()` 接缝已留;未接线时列表**不带** `hosts` 段(⛔ 不编宿主规格)。
3. **用量 / 账单无数据源**(平台既无计费表也无用量计数器)⇒ 恒 `not_connected`。这是**如实回**,不是"待办"。
4. **未做**:插件包内的「租户界面 + 请求」(S9 的另一半,另排一棒);前端页面;真机部署与浏览器级复验。
5. ⛔ **不能**说"多租户可用"——只能说**平台侧接口已就绪且有测试**。
## ⑤ 回滚点
- **备份目录**(改前原样 + 新增件留痕):`E:\ProgramDSH\.dsh\temp\s9-20260927\`
- `src\web\server.ts`(77705 B,改前原样)
- `src\web\routes\tenants.ts`(新增件副本)
- `test\tenants.test.mjs`(新增件副本)
- **回滚步骤**:① `server.ts` 还原备份(或删掉那 2 处改动:import + `app.register(tenantRoutes)`);
② 删 `src\web\routes\tenants.ts`、`test\tenants.test.mjs`;③ `npm run build` 复跑 rc=0。
三条都是纯增量 ⇒ 回滚无残留、不牵动任何既有路由语义。
- 未 commit / 未 push(本轮无提交授权)。
## ⑥ 边界自证
- ✅ **未建库、未建表、未加列、未写迁移**:`git status -- src/db/schema.ts` 显示的 `M` 是**开工前就存在的脏**
(mtime `2026-09-26 23:22:48`,早于本棒开工 `07:20`);本棒对 `schema.ts` **零改动**。
- ✅ 未碰 `src/net/relay/identity.ts`(同为开工前脏,mtime `2026-09-26 22:49:50`)、
`content/crypto.ts`、`registry.ts`、`directory.ts`。
- ✅ 未改插件仓、未改官方 dsh、未 commit / push / scp / 部署 / ssh。
- ✅ 路由层不跑 SQL、不 spawn 进程:起停一律经 `dsh.ts` 的 `launchForUser` + `app.supervisor.*`。
- ✅ 本棒实际改动:仅 `server.ts`(修改)+ 两个新增文件(mtime 07:23–07:24,全在本棒窗口内)。
## ⑦ 需父会话接手
1. **接线成员读数**(要动 `DbAdapter`):在 `src/db/adapter.ts` + `src/db/pg.ts` 加
`listTenantMembers(tenantId)` 的只读查询(读**既有**成员模型,按租户 = 账号聚合;
⛔ 不建表、⛔ 不加列),并补真库用例(`--test-concurrency=1`)。
2. **宿主规格显示**:`listDshHosts()` 接缝已就位,接线后列表自动多出 `hosts` 段。
3. **S9 的另一半**:插件包内「界面 + 请求」(另排一棒);S9 验证点「停掉平台侧 ⇒ 界面必须明确报做不到」
需在插件侧那一棒做负控。
4. **部署与浏览器级复验**未做 ⇒ 若要"交付"口径,还差生效链路 + 用户可见面复验。