- 变更规模:新增 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/ 知识文件,按口径入库)
7.2 KiB
7.2 KiB
S9 多租户 · 平台侧那一半 — 落地回执(2026-09-27)
执行棒回执。代码仓 =
D:\github\dsh_shenxian(不是本工作区;本工作区只放回执)。 用户 2026-09-27 拍板契约(逐字):「数据库 做 数据隔离就行 不需要单独建表建库,数据和实例都需要隔离」 方案出处:E:\ProgramData\AIProject\aliyun-dsh-server\交付物\功能打包到基础插件-分步实施方案-20260926.md§S9(307–318 行)
① 结果
已交付(接口可跑、有测试、门禁全绿):平台进程内的多租户管理面 API 四条 —— 租户列表、单租户详情、确保隔离实例、回收隔离实例。
口径三件(照拍板落):
- 数据隔离 = 逻辑隔离:租户 = 既有账号(
users.id,网名u:<id>,与tenantNetworkOf同口径)。 ⛔ 未建库、⛔ 未建表、⛔ 未加列、⛔ 未写迁移。 - 实例隔离 = 每租户一份独立实例:直接复用平台已有的按需编排(
launchForUser→supervisor.launch/supervisor.stop)。 ⛔ 没有另造第二套实例生命周期;⛔ 路由层不 spawn 进程、不 kill 进程。 - 「创建隔离实例」这个动作落平台进程:动作面 = 平台 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,⛔ 无编造字段
④ 未达成项(具名)
- 成员读数的真库查询未接:
GET .../:id的成员面走可选 db 接缝listTenantMembers(); 本仓DbAdapter目前没有这个只读查询 ⇒ 实部署会回state: 'not_connected'(接口形状与计数语义已定、已测,缺的只是src/db/adapter.ts+src/db/pg.ts里那条 SELECT 与真库用例)。 ⚠️ 未擅自加:那属于"跑 SQL 的那一层",且真库用例要--test-concurrency=1。 - 宿主台账同理:
listDshHosts()接缝已留;未接线时列表不带hosts段(⛔ 不编宿主规格)。 - 用量 / 账单无数据源(平台既无计费表也无用量计数器)⇒ 恒
not_connected。这是如实回,不是"待办"。 - 未做:插件包内的「租户界面 + 请求」(S9 的另一半,另排一棒);前端页面;真机部署与浏览器级复验。
- ⛔ 不能说"多租户可用"——只能说平台侧接口已就绪且有测试。
⑤ 回滚点
- 备份目录(改前原样 + 新增件留痕):
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是开工前就存在的脏 (mtime2026-09-26 23:22:48,早于本棒开工07:20);本棒对schema.ts零改动。 - ✅ 未碰
src/net/relay/identity.ts(同为开工前脏,mtime2026-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,全在本棒窗口内)。
⑦ 需父会话接手
- 接线成员读数(要动
DbAdapter):在src/db/adapter.ts+src/db/pg.ts加listTenantMembers(tenantId)的只读查询(读既有成员模型,按租户 = 账号聚合; ⛔ 不建表、⛔ 不加列),并补真库用例(--test-concurrency=1)。 - 宿主规格显示:
listDshHosts()接缝已就位,接线后列表自动多出hosts段。 - S9 的另一半:插件包内「界面 + 请求」(另排一棒);S9 验证点「停掉平台侧 ⇒ 界面必须明确报做不到」 需在插件侧那一棒做负控。
- 部署与浏览器级复验未做 ⇒ 若要"交付"口径,还差生效链路 + 用户可见面复验。