Files
dsh_ai1net_server/交付物/模型管理-S7落地与四轮回执-20260926.md
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

17 KiB
Raw Permalink Blame History

S7「第三块:模型管理」· 落地与四轮回执(2026-09-26 · 第 45 棒)

工作区:E:/ProgramData/AIProject/aliyun-dsh-server | 会话名:插件投放与分库线-45 执行依据:本线入口 §0 最新行 + §2「🎯 本轮动作」(第 44 棒块)|规格书 05-交接单/交接单_包规格书_20260926.md §〇/§五 A3·A4·C|方案 交付物/功能打包到基础插件-分步实施方案-20260926.md §3.10.9 + §4 本棒只做一件事:S7(⛔ 不碰 S8、⛔ 不跨 S9 的 P2)


一、一句话结论

S7 四条判据 全部落地并逐条取证——包 @dsh-local/ai1net 现在自带模型管理面:三条精确路由 / 六种方法(/api/ai1net/models〔GET·PUT〕· /models/credentials〔GET·POST·DELETE〕· /models/grant〔POST〕),明文密钥只走一个入口、读路径根本不存在密钥,共享模型默认不共享 + 管理员逐用户授权,写 home 一律走平台 UserFs 且「直写 fs 的静默空操作」成了可断言的检出。

本棒未抓到产品侧真 bug;抓到并修掉的是五处仪器错(其中三处不修会产出假绿 / 假红)——逐条见 §九。

读数:包内门禁 85 ✅ / 0 ❌(rc=0,S6 时为 60)|S7 取证编排 73 ✅ / 0 ❌(rc=0)|门禁负控 8/8(7 处改坏全部被门禁抓到)|四把尺子 装配端A/端B · 取数 · 回调 · 运行 全 rc=0|v-neg all rc=0。


二、四条判据逐条回执

# §3.10.9 判据原文 本棒落地 取证 结论
S7-1 模型配置/凭据/共享授权/界面 进包 ⇒ v-plugin.sh 绿 lib/models-sdk.mjs(新建 32,634 B)+ lib/index.js 三条注册面 + lib/client.js 第四张卡 装配 端A/端B · 取数 · 回调 · 运行 全 rc=0;spy ctx 抓到三条路由原文;stub fetch 抓到 13 条出向逐条对得上 ✅
S7-2 共享模型走管理员逐用户授权(默认未授权)⇒ 负控:未授权用户看不见 SHARED_MODEL_POLICY(default:'not-granted' · grantedBy:'admin-only' · scope:'per-user')+ grantShared() 就地闸 策略表三条机验;grant 缺 asAdmin / 缺 userId / 缺 enabled 三条负控;撤权负控:管理员撤掉授权 ⇒ 用户自己开关虽开,视图里 granted=false ✅
S7-3 写 home 一律走 UserFs ⇒ 负控:直写 fs ⇒ 能检出"静默空操作" HOME_WRITE_POLICY + createHomeWriter() + 纯函数 classifyHomeWrite();内核零 fs 用法(门禁扫代码行) 支撑两支真跑:走 UserFs ⇒ delivered=true;直写 ⇒ silentNoopDetected=true(写成功、回读为空);无 UserFs ⇒ 拒(⛔ 不退化成本机 fs) ✅
S7-4 验「平台级凭据 ⛔ 不下发客户端」(R5 红线)⇒ 客户端侧零平台凭据 redactModelConfig() 白名单净化器(全包唯一)+ 落台账前的明文自检(fail-closed) 平台故意把明文塞进回体 ⇒ 出包体零明文(读配置 / 观测面 / 新增回包三条);明文在包内只出现在写入那一条出向上;stats() / 台账 / 日志三处零明文 ✅

完成判据(§3.10.9 原文):模型配置在实例内可见(三条路由挂在本实例,经 connection.fetch.register 精确路径)+ 平台级凭据不下发客户端(读路径结构化无密钥字段)。


三、三条路由 / 六种方法 → 平台真端点(逐条真发 · stub fetch 记账)

包内路由 方法 平台真端点 依据(真实实现)
/api/ai1net/models GET GET /api/me/keys src/web/routes/auth.ts:484
/api/ai1net/models PUT〔role:'shared'〕 POST /api/me/models/shared auth.ts:617(只动自己的偏好)
/api/ai1net/models PUT〔role:'entry-toggle'〕 POST /api/me/keys/<id>/toggle auth.ts:598
/api/ai1net/models/credentials GET GET /api/me/keys 同上(只回"是否已配置")
/api/ai1net/models/credentials POST POST /api/me/keys auth.ts:526(🔴 明文唯一一次转交)
/api/ai1net/models/credentials DELETE DELETE /api/me/keys/<id> auth.ts:664
/api/ai1net/models/grant POST POST /api/admin/users/<id>/models/shared admin.ts:107(preHandler: requireAdmin)

🔴 三条路径 → 六种方法:role 分流复用同一条路径(与 M5 长轮询、M3「设置自动处理」同一手法)⇒ ⛔ 没有出现第四条路径(门禁 ①g 第 ③/④ 条断言)。

🔴 为什么包内读不出密钥(真实依据,⛔ 不是设计稿):平台 CREDENTIAL_COLS = 'id, key_name, enabled, updated_at, route, base_url, api, models'(src/db/repo.ts:346)—— 本来就没有密钥列。包内再过一遍净化,是防"平台日后把 ref 带出来"。

该拒就拒:十二条,一律零平台请求

类别 条数 例
入参形态闸 9 新增缺 name / 缺 apiKey|删除缺 id / id 穿越(../)|PUT 的 role 非法|授权缺管理员声明 / asAdmin:false / 缺 userId / userId 穿越
凭据闸(复用 createHostKit) 3 出示空凭据(带身份头不带令牌)|借别人的令牌|身份头与用户名不符

复核命令:node tmp/p45/run-s7.mjs → B 段 12 行;每行都同时断言 reason 与 平台请求=0。


四、🔴 凭据不出包(S7-4「明文只出现一次」是机验结论)

观测面 断言 结论
读配置出包体 平台回体故意带 apiKey/secret/apiKeyRef ⇒ 出包体零明文 + redactions>0 ✅
凭据观测面 只回 {id,name,enabled,configured:true,route,models} —— 连 apiKey 字段都没有 ✅
新增的回包 平台回体回显密钥 ⇒ 出包体零明文(包内只挑元数据重造) ✅
出向请求 含明文的出向全都是 POST /api/me/keys(那唯一一条写入) ✅
stats() / 包内台账 / 包内日志 三处序列化后零明文;台账只记 hasKey:true ✅
落台账前自检 plaintextBlocked=0(⛔ 没有任何明文走到过台账;命中即拦下并计数) ✅

五、🔴 S7-2/S7-3 两条负控(本棒核心)

S7-2 负控(撤掉授权 ⇒ 立刻拿不到):同一份代码、只差授权位—— sharedModelEnabled:true(用户自己开着)+ sharedModelGranted:false(管理员撤了)⇒ 视图里 granted=false;把授权位改回 true ⇒ granted=true。⇒ "能不能用"确实由授权决定,而不是由界面或偏好决定。

S7-3 负控(直写 fs ⇒ 检出静默空操作):

走 UserFs :{"via":"userfs","ok":true,"delivered":true,"silentNoop":false}
直写 fs  :{"via":"local-fs","silentNoopDetected":true,"ok":true,"delivered":false,"silentNoop":true,"reason":"readback-empty"}
无 UserFs :{"ok":false,"reason":"userfs-unavailable"}   ← ⛔ 不退化成本机 fs

判据不是"写没报错",是回读(且回读走权威路径 UserFs)—— §4-D4 的症状正是"写入成功返回、内容根本没到"。


六、🔴 真实 vs 建模(逐条标注 · ⛔ 不许把建模当"已通")

面 本棒是真实还是建模 说明
包内三条路由的实现与注册 真实 lib/index.js 真装配 + 真 apply(ctx)(spy ctx 抓到三条路由原文与 methods)
包内 → 平台的出向请求 真实(HTTP 层) 三条头(包名 / 每插件令牌 / 实例令牌)+ body 真的 JSON.stringify;stub fetch 逐条记账
平台侧收到的效果 ⛔ 建模 平台端由 stub fetch / spy 代替;未对真机投放(属投放线)
平台端点字面量与形状 真实(照抄) 逐条取自 auth.ts / admin.ts 的实际行(见 §三表最后一列)
/models/grant 的管理员权威闸 ⛔ 平台侧未在本棒真跑 真实判据 = 平台 requireAdmin(admin.ts:109)。包内那道 asAdmin 只是误调用闸,⛔ 不许当成安全边界、⛔ 不许写"平台侧管理员闸已上线"
写 home 走 UserFs ⛔ 建模(注入式假 UserFs) 真实平台侧 app.userFs(auth.ts:646-652 同款口径)未在本棒真跑;本棒证的是机制与检出成立
客户端第四张卡片 机械判据真实 / 视觉效果未取到 门禁覆盖封包契约(lazy-CJS · 零 exports.default · type=password · 提交后清空);真壳截图未取到(§八)
「共享模型两列两个主体」 真实(照抄口径) shared_model_granted(管理员授权)⇄ shared_model_enabled(用户偏好),两者是与关系(auth.ts:608-624 + admin.ts:92-105 原文口径)

⇒ ⛔ 不能断言「真机投放已通 / 平台侧管理员闸已上线 / 客户端卡片在真壳里长这样」。


七、§4 避坑总表对表(本棒相关类目)

类目 本棒处置
B2 桌面端只认精确路径 三条逐字冻结、⛔ 无前缀匹配;门禁把三条 + 六种方法与规格书 §五 A3 逐字比对
B3 平台不是实例 「写平台库」的动作⛔ 不进包:授权只发请求,落库权威在平台(回体标 executorInPackage:false)
D3 列级 maxBytes 唯一把关人 = 插件侧 本棒未改 dsh.data.yaml ⇒ DECL_DIGEST=88c752a3e12597b3… 逐字未变(8 表 29 列未破)
D4 写 home 静默空操作 HOME_WRITE_POLICY.onlyPath='userfs' + 内核零 fs 用法(门禁扫代码行)+ 直写支检出
E1 测试跑的是构建产物 改完必 node scripts/build.mjs --check-only(本棒改 build.mjs 自身后复验两次)
E6 禁 exports.default lib/client.js 仍只交 apply + inject(门禁 ①b 绿)
G5 ⛔ 不下发平台共享密钥 明文只在写入那一条出向上;读路径无密钥字段(S7-4)
G8 改 / 重启要写进回执 见 §八:⛔ 未重启任何控制面、⛔ 未对真机投放 —— 如实标注"机械判据绿 / 真机未验"

八、尺子回归(⛔ 未改尺子一行)/ 未取到(如实)

⓪ 包内门禁  node scripts/build.mjs --check-only        | rc=0   (85 ✅ / 0 ❌,S6 时 60 ✅)
   M5_DIGEST=55cdc0d5fc8eb99d   ← 与第 43/44 棒逐字相同 ⇒ M5 的绿没被弄红
   M3_DIGEST=7045a778baa82cfe   ← 与第 44 棒逐字相同
   DECL_DIGEST=88c752a3e12597b3…(8 表 29 列未破)
   M2_DIGEST=11d7b1be8aea3996   ← 本棒新增
S7-1 装配 · 端A 客户端形态(桌面 profile 真件副本 + 本包)  | rc=0
S7-1 装配 · 端B 平台形态(profiles/web 键位建模 link: 依赖) | rc=0
S7-1 取数 · 真实 import 本产物                          | rc=0
S7-1 回调 · 真实调用 apply(ctx)(spy ctx)               | rc=0
运行 · 真启动日志的失败面(⚠️ 弱证据)                    | rc=0
v-neg.sh all                                          | rc=0   (装配/运行/取数/回调 四项红绿双态均成立)
S7 取证编排  node tmp/p45/run-s7.mjs                    | rc=0   (73 ✅ / 0 ❌)
门禁负控     node tmp/p45/neg-gate.mjs                  | rc=0   (8/8:7 处改坏**全部**被 ①g 抓到)
v-baseline.sh(开工对照,尺子自述⛔ 不进判据)             | rc=0   (平台侧 4 个包 / 客户端 5 个 bundle;`@dsh-local/*` 计 0)

⚠️ 未取到 / 不成立(如实,⛔ 不折算成"已知/通过")

项 读数 原因
浏览器级(第四张卡片的真壳) v-ui.sh rc=3(UNAVAILABLE / no page target) 需真壳在跑;启动器属客户端线(dsh-ai1net-desktop/.workbuddy/_devkit/launch-desktop-dev-017.mts),本线 ⛔ 不代启 ⇒ 机械判据已绿、视觉效果未取到
M5 卡片 + S4-6 实例页能力栏 同上 同因(第 42/43 棒已欠,本棒未消)
v-plugin.sh 运行 面 rc=0 但空真 可用日志 mtime 早于本包 ⇒ 弱证据,⛔ 不当"运行期已验"
平台侧真跑(真库 / 真 requireAdmin) 未取到 属投放线,本棒⛔ 不越界

九、本棒的 5 处「仪器错」(如实记录 · ⛔ 其中 3 处不修就是假绿/假红)

# 错在哪 症状 修法
① 门禁 ①g 的负控夹具用裸字符串当每插件令牌 三条授权断言全报"平台零调用" —— 看着像产品没打到平台,其实是门禁自己的夹具不合格(resolveEnableProof 会验签) 用 signPluginToken() 真签一枚(判据:夹具也要合规格)
② 门禁 ①g 第 ⑩ 条的 fs 扫描认"文件里出现过 fs 这个词" 把 HOME_WRITE_POLICY.forbidden 那一行自己的字面量当成引用 ⇒ 门禁自己假红 改成认用法(from 'node:fs' / fs.xxx( / writeFileSync(),⛔ 不认字符串里的名字
③ redactModelConfig() 首版先判白名单、再判敏感字段名 敏感字段被白名单静默丢掉 ⇒ redactions 恒 0:输出确实干净,但没有证据("看着干净"不是"证明干净") 调换顺序:先判敏感字段名、再判白名单;门禁加一条"喂明文必须计到净化数"
④ run-s7.mjs 首版令牌 exp 用固定时刻 1774000000000(= 2026-03-20) host 半内部用默认时钟(无注入点)⇒ 令牌已过期 ⇒ 全部正常路径被闸拒成 plugin-not-enabled,73 项里 24 项假红。识破它的是那一行 出向请求总数 = 0 改用真时刻 Date.now();并把"出向总数"打进读数摘要(假红时一眼可见)
⑤ run-s7.mjs 三条断言写过头 分别错在:身份头里其实是调用方出示的 pluginId(不是包名)/effective 本就在白名单内、应当保留/明文写入被调了两次 逐条改成与实现口径一致(⛔ 不为了"变绿"放松,而是把正确的口径写清)

产品侧真 bug:本棒 0 处(与第 44 棒不同 —— 那棒抓到 PKG_RE 不收带 scope 的包名)。如实记录,⛔ 不凑数。


十、硬边界自查 / 遗留 / 回滚

  • ⛔ 未 commit / 未 push(未明确要求)。
  • ⛔ 未触碰官方 dsh 主程序与缓存(R2);⛔ 未把包装进任何 profile(装配用 tmp/p45/ profile 副本)。
  • ⛔ 未重造分发与更新机制;⛔ 未代用户起实例;⛔ 未动客户端工作区(dsh-ai1net-desktop / dsh-desktop-0.1.7rc2 只读引用)。
  • ⛔ 未重启控制面 / 未中断任何在线用户 / 未批量改文件(红线门禁未触发);包内零直连平台库路径(门禁扫代码行)。
  • S9 的 P2 未跨过;本棒发现一处新停点(见下一行)⇒ 依纪律未登记下一棒,入口写「⏸ 等拍板」并列出候选。
  • 🔴 新停点(本棒发现)= S8 的硬门未满足:方案 §3.10.10 原文「⛔ 本步的硬门:F1.5(G1 序号 + G6 撤根)必须先有结论」,而现状 G1 未修、G6 无任何机制(依据 交付物/AI1NET-三类节点结构调整可行性-20260926.md §P0「G1 未修|G6 无机制 ⇒ 规模越大越撤不回去」与 交付物/全功能承载判定-底层插件能否承-20260926.md §七「F1.5 = 一切前置」)。⇒ 依用户 2026-09-17 明令「要用户拍板的,等拍了再新建接续会话」,本棒 ⛔ 未登记下一棒(候选 A/B 与倾向见入口 §2「⏳ 等拍板」块)。
  • 并发:本棒按惯例用 guard 锚点算法自定域键(8 个);preflight-lock.sh 仍把工作区外仓库目录 + 工作区根文件恒判【D】未归类(7 个)—— 与第 26–44 棒同一处已知误报,⛔ 未改那个跨库脚本,如实记此条。
  • 回滚:删 lib/models-sdk.mjs + 撤 lib/index.js 的 M2 装配块与常量 + 撤 lib/client.js 的 M2 字典/卡片/样式段与槽位注册 + 撤 scripts/build.mjs 的 ①g + 撤 package.json 的 exports["./models-sdk"] 一条。⚠️ 回滚后 M5_DIGEST / M3_DIGEST / DECL_DIGEST 因本就未变,仍可作前后一致锚点。

十一、出处

  • 规格书:D:/github/dsh_shenxian/dsh-server-docs/05-交接单/交接单_包规格书_20260926.md(§〇 · §五 A1/A2/A3/A4 · §五 C)
  • 方案:交付物/功能打包到基础插件-分步实施方案-20260926.md(§3.10.9 = S7 逐步照做唯一权威;§4-B/D/E/G 类)
  • 平台真实实现(只读引用):D:/github/dsh_shenxian/src/web/routes/auth.ts(484 / 526 / 598 / 617 / 664)· src/web/routes/admin.ts(107-133)· src/db/repo.ts(346 CREDENTIAL_COLS)· src/db/schema.ts(454-466 shared_model_granted v11)· src/fs/user-fs.ts(69 UserFs 接口)
  • 上两棒口径继承:交付物/技能插件管理-S6落地与四轮回执-20260926.md(§六 §九)· 交付物/IM插件侧-S5落地与四轮回执-20260926.md(§六)
  • 本棒读数(⛔ 不入库):tmp/p45/读数-S7-20260926.txt(73 ✅ / 0 ❌)· tmp/p45/尺读数-S7-20260926.txt · tmp/p45/门禁负控-20260926.txt(8/8)
  • 编排与负控脚本:tmp/p45/run-s7.mjs · tmp/p45/neg-gate.mjs · tmp/p45/run-scales.sh