Files
dsh_ai1net_server/交付物/S9-多租户-平台侧-回执-20260927.md
T
admin c1b5e4d966 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/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

7.2 KiB
Raw Blame History

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. 部署与浏览器级复验未做 ⇒ 若要"交付"口径,还差生效链路 + 用户可见面复验。