- 变更规模:新增 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/ 知识文件,按口径入库)
15 KiB
S6「第二块:技能插件管理」· 落地与四轮回执(2026-09-26 · 第 44 棒)
工作区:
E:/ProgramData/AIProject/aliyun-dsh-server| 会话名:插件投放与分库线-44执行依据:本线入口 §0 最新行 + §2「🎯 本轮动作」(第 43 棒块)|规格书05-交接单/交接单_包规格书_20260926.md§〇/§五 A·C|方案交付物/功能打包到基础插件-分步实施方案-20260926.md§3.10.8 + §4 本棒只做一件事:S6(⛔ 不碰 S7、⛔ 不跨 S9 的 P2)
一、一句话结论
S6 四条判据 全部落地并逐条取证——包内那一半(上传/检测/建库/启用 + 「更新」四拆的 ①②)真发到平台面;「搬包」与「自动处理的执行者」两拆明确留在包外(分别为 S4 拉取器与引导层),并由负控证明「关掉包外执行者 ⇒ 自动更新行为确实停」。
过程中抓到并修掉一处会让 M3 五条路由全线静默失效的真 bug(包名形态校验不收带 scope 的冻结包名),并已把它钉成包内门禁的第 ⑩ 条断言。
读数:包内门禁 60 ✅ / 0 ❌(rc=0)|S6 取证编排 58 ✅ / 0 ❌(rc=0)|S6-1 四条面 rc=0|v-neg all rc=0。
二、四条判据逐条回执
| # | §3.10.8 判据原文 | 本棒落地 | 取证 | 结论 |
|---|---|---|---|---|
| ① | 上传/检测/建库/启用 进包 ⇒ v-plugin.sh 四面绿 |
七条包内路由(lib/plugins-sdk.mjs + lib/index.js 注册面) |
装配 端A/端B · 取数 · 回调 · 运行 全 rc=0;spy ctx 抓到九条平台请求逐条对得上 |
✅ |
| ② | 「更新」四拆:①②进包(只到"请求")/③搬包走 S4 拉取器/④执行者留包外 ⇒ 能逐条指出"谁在做",且执行者在包外 | UPDATE_SPLIT 数据表 + describeOwnership()(UI 与回报件共用同一份来源) |
四拆逐条具名(见 §三);机验「凡 executor:true ⇒ inPackage:false」;机验「包内零搬包实现」 |
✅ |
| ③ | package.json 指向 yaml;自增 id ⛔ 不照搬;UI ≤900px 独占 ⇒ 窄屏截图 |
dsh.data.schema = ./dsh.data.yaml(S3 已落)|行键 = 自然键(包名/版本/user_id)|窄屏样式段独立成块 |
门禁 ⑨(断点逐字 900px + 四条硬判据 + 零 100vh)+ 门禁「零自增 id」两处(包内 + 声明件)全绿;⚠️ 真壳窄屏截图未取到(见 §八) |
🟡 机械判据 ✅/截图未取到 |
| ④ | 负控:关掉包外执行者 ⇒ 自动更新行为必须停 | 包内只有 requestUpdate(登记请求)与 setAutoHandling(写偏好) |
对比实验:执行者在位 ⇒ pulled=2 + 取包口被调 2 次;执行者关掉 ⇒ 本地零包目录 + 取包口 0 次调用,而包内台账仍记着"请求发生过" |
✅ |
三、S6-2「谁在做」——四拆逐条(判据的输出就是这个表)
| # | 拆 | 包内? | 执行者? | 归属 | 做到哪为止 |
|---|---|---|---|---|---|
| ① | 看见新版本 | ✅ 是 | ❌ 否 | package |
只报差异(differs/same/unknown 三态),⛔ 不代替用户下"该更新"的结论 |
| ② | 点「更新」 | ✅ 是 | ❌ 否 | package |
只到 requested(回 requestId + executor:'s4-puller'),⛔ 不假装进度 |
| ③ | 搬包 | ⛔ 否 | ✅ 是 | s4-puller |
S4 已落地的拉取器(平台/引导层),⛔ 包内一个字节都不搬 |
| ④ | 设置自动处理 | ⛔ 否 | ✅ 是 | bootstrap-layer |
包内只写一条偏好(pullsTriggered:0)——执行者必须活在被自动化对象之外(实例没起 = 插件不存在) |
机验(不靠自述):
UPDATE_SPLIT.filter(r => r.executor).every(r => r.inPackage === false)⇒ 执行者全在包外。- 归属只落两个具名外部角色,
['s4-puller','bootstrap-layer'],不含本包。 - 包内代码行(去注释)扫
spawn/createGzip/syncSharedLayer/pullOnePack/extract/rename(/cpSync⇒ 零命中。 - 包内零自增字眼(
autoIncrement/AUTO_INCREMENT/rowid/serial)⇒ 行键确为自然键。
四、包内七条路由 → 平台端点(逐条真发,spy ctx 记账)
| 包内路由 | 平台端点(真实实现) | 方法 | 备注 |
|---|---|---|---|
GET /api/ai1net/plugins |
/api/plugins/business |
GET | 候选池清单 |
POST /api/ai1net/plugins/upload |
/api/plugins/business |
POST | body {file, filename, trust?};检测链安全/兼容两项在这条响应里 |
POST /api/ai1net/plugins/detect |
/api/plugins/business/datastore |
POST | body {id, dryRun:true};⚠️ 返回值如实标 covers:'dataplane-declaration',⛔ 不让前端以为"三项都重跑了" |
POST /api/ai1net/plugins/db(plan) |
/api/plugins/business/datastore |
POST | {id, dryRun:true},⛔ 不动库 |
同上(create) |
同上 | POST | {id},✅ 真建库 |
同上(migrate) |
/api/plugins/business/datastore/migrate |
POST | {id, confirm:true, expect};🔴 expect 必填且包内不生成(生成 = 替用户确认改动) |
POST /api/ai1net/plugins/enable |
/api/plugins/mine/apply |
POST | {plugins:[{id, enabled}]};平台两道门禁(基础插件不可关/未建库不许开)都在平台,包内只转交 |
GET /api/ai1net/plugins/updates |
/api/plugins/shared/status |
GET | 折成三态,判据只看 treeSha256 |
POST /api/ai1net/plugins/update-request |
/api/plugins/business |
POST | 只到请求;role='auto' 时改走 setAutoHandling,零平台请求 |
「该拒就拒」五条负控(一律零平台请求,⛔ 不发注定失败的请求):
| 场景 | 结论 | 平台请求数 |
|---|---|---|
mode=migrate 缺 expect |
bad-args |
0 |
upload 缺 file |
bad-args |
0 |
包名形态不合法(../../etc/passwd) |
bad-args |
0 |
| 出示空凭据(带了身份头却没带令牌) | plugin-not-enabled |
0 |
| 出示别人的令牌 | plugin-not-enabled |
0 |
🔴 三态凭据语义(继承第 43 棒):没带身份头 ⇒ 视为本包自己(用本机装配注入)|带了身份头却是空串 ⇒ 必拒(⛔ 不许回退到本机 env —— 那正是"插件自称身份"的缝)。
五、S6-4 负控(本棒核心)
装置:真执行者 = tmp/p42/puller.mjs(S4 已落地的拉取器,⛔ 不重造)+ tmp/p42/mgr-mock.mjs(照抄平台对账/产物口口径)。
| 局面 | 观测 | 结论 |
|---|---|---|
| 执行者在位 | ok=true;pulled=[_dsh-local_ai1net, _dsh-local_probe2];盘上真出现包目录;取包口被真实调用 2 次 |
行为确实会发生 |
| 执行者关掉(清空本地后,只跑包内动作) | 本地零包目录;取包口 0 次调用;setAutoHandling 回 pullsTriggered:0 |
行为确实停 |
⚠️ 这条负控不是空绿:同一轮里包内台账记着 updateRequests=1 / autoHandlingWrites=1 ⇒ 证明"请求确实发生过,只是没人执行",而不是"脚本压根没跑"。
叠加三条既有判据(S4 口径,本棒复测):
- 清单拿不到 ⇒
ok=false+pulled空 + 本地零改动(目录清单前后一致)。 - 单包失败(截断 gzip)⇒
failed恰含该包、另一包照常落地、失败已回报平台。 extra(本地多余目录)⇒ 如实出现在extra里,目录仍在(⛔ 平台不据此删)。
六、🔴 真实 vs 建模(逐条标注 · ⛔ 不许把建模当"已通")
| 面 | 本棒是真实还是建模 | 说明 |
|---|---|---|
| 包内七条路由的实现与注册 | 真实 | lib/index.js 真装配 + 真 apply(ctx)(spy ctx 抓到路由原文) |
| 包内 → 平台的出向请求 | 真实(HTTP 层) | 三条头(包名 / 每插件令牌 / 实例令牌)+ body 真的 JSON.stringify |
| 平台侧收到的效果 | ⛔ 建模 | 平台端由 spy / mock 代替;未对真机投放(属投放线) |
| 「更新」③ 搬包 | 真实(复用 S4 产物) | 真拉取器 + 真 mock Manager;⚠️ 平台的权威共享层是沙箱替身 |
| 「更新」④ 自动处理的执行者 | ⛔ 仅契约 | 包内只有"偏好"这一半;引导层那一半尚未实现(本棒只证明"包外不干活 ⇒ 行为停") |
| 每插件令牌的平台侧那一半 | ⛔ 建模 / 尚无 | 真实 im.ts 里仍无这道闸 ⇒ ⛔ 不能断言"平台侧令牌闸已上线"(属投放线待落地) |
| 长轮询 / 会话面 / 桥传输 | 继承第 43 棒口径 | 会话面走 imAuth;桥端点才有令牌闸 |
| 窄屏 UI | 机械判据真实 / 视觉效果未取到 | 样式段与断点由门禁逐条断言;真壳截图未取到(§八) |
七、§4 避坑总表对表(本棒相关类目)
| 类目 | 本棒处置 |
|---|---|
| B1 桌面端无 webServer | 服务端一律走 E1(connection.fetch.register 精确路径),⛔ 不用 webServer 具名路由 |
| B2 只认精确路径 | 七条逐字冻结、⛔ 无前缀匹配;门禁把七条与规格书 §五 A3 冻结清单逐字比对 |
| B3 平台不是实例 | 「写平台库」的动作(建实例/租约/配额)⛔ 一律不进包 |
| C1 指纹两侧不一致 | 判据只看 treeSha256,⛔ 不比版本号;diffOfSharedStatus 只聚合不重算(避免第二真相源) |
| C5 算不出指纹就报算不出 | 三态强制:全 current ⇒ same/有 stale ⇒ differs/有 unknown ⇒ unknown(⛔ 绝不折成 same) |
| D1 一个包 = 一个库,表名分段 | 库 dshs_pl_ai1net/前缀 p_ai1net_/M3 段 p_ai1net_plugin_(S3 已落,DECL_DIGEST 未破) |
| E1 测试跑的是构建产物 | 改完必 node scripts/build.mjs(本轮改 build.mjs 自身后先 --check-only 复验) |
E6 禁 exports.default |
门禁覆盖;lib/client.js 仍只导出 apply + inject |
八、尺子回归(⛔ 未改尺子一行)/ 未取到(如实)
⓪ 包内门禁 node scripts/build.mjs --check-only | rc=0 (60 ✅ / 0 ❌)
M5_DIGEST=55cdc0d5fc8eb99d ← 与第 43 棒逐字相同 ⇒ M5 的绿没被弄红
M3_DIGEST=7045a778baa82cfe DECL_DIGEST=88c752a3e12597b3…(8 表 29 列未破)
S6-1 装配 · 端A 客户端形态(桌面 profile 真件副本 + 本包) | rc=0
S6-1 装配 · 端B 平台形态(profiles/web 键位建模 link: 依赖) | rc=0
S6-1 取数 · 真实 import 本产物 | rc=0
S6-1 回调 · 真实调用 apply(ctx)(spy ctx) | rc=0
运行 · 真启动日志的失败面(⚠️ 弱证据) | rc=0
v-neg.sh all | rc=0
S6 取证编排 node tmp/p44/run-s6.mjs | rc=0 (58 ✅ / 0 ❌)
⚠️ 未取到 / 不成立(如实,⛔ 不折算成"已知/通过")
| 项 | 读数 | 原因 |
|---|---|---|
| 窄屏截图(S6-3 第三条) | v-ui.sh rc=3 |
需真壳在跑;启动器属客户端线(dsh-ai1net-desktop/.workbuddy/_devkit/launch-desktop-dev-017.mts),本线 ⛔ 不代启 ⇒ 机械判据已绿,视觉效果未取到 |
| M5 卡片 + S4-6 实例页能力栏 | 同上 | 同因(第 43/42 棒已欠,本棒未消) |
v-baseline.sh 平台侧 |
rc=255(客户端侧 ✅ 取到 5 个 bundle,@dsh-local/* 计 0 个) |
ssh 47.77.182.89 本轮连不上(瞬时;该项按尺子自述「只作开工对照,⛔ 不进判据」) |
v-plugin.sh 运行 面 |
rc=0 但空真 | 可用日志 mtime 早于本包 ⇒ 弱证据,⛔ 不当"运行期已验" |
九、本棒唯一一处真 bug(如实记录)
🔴 包名形态校验不收带 scope 的包名 —— lib/plugins-sdk.mjs 首版 PKG_RE = /^[a-z0-9][a-z0-9._-]*$/i,而规格书 §五 A4 冻结:包名 = @dsh-local/ai1net(带 scope),pluginId 只是它的末段。
- 后果:
upload之外的五条走pkgOf()的路由(detect/db/enable/update-request)全线bad-args就地拒,一个平台请求都不发;且这是静默失败(看着像"平台不认",其实是包内先拒的)。 - 发现方式:
tmp/p44/run-s6.mjsB 段的真发取证(九条里 5 条报"零请求")。 - 修复:
PKG_RE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i—— ⛔ 仍拒'../'穿越(scope 后那段与无 scope 时用同一把尺子)。 - 防复发:把「带 scope 的冻结包名必须过 +
../必须拒」钉成包内门禁 ①f-⑩(读package.json的name+ 从源码取PKG_RE字面量真跑:4 个合法名全过 / 7 个非法名全拒),并把包名与正则字面量一起喂进M3_DIGEST。
另一处自家脚本的坑(不是包的问题,如实记):编排脚本首版把「记账窗口」取在 promise 创建之后,而 guarded() 是同步走到 callPlatform 的 ⇒ 九条全报"零请求"(假红)。改成传 thunk 后即正确。
十、硬边界自查 / 遗留 / 回滚
- ⛔ 未 commit / 未 push(未明确要求)。
- ⛔ 未触碰官方 dsh 主程序与缓存(R2);未把包装进任何 profile(装配用
tmp/p44/沙箱 profile 副本)。 - ⛔ 未重造分发与更新机制:「搬包」直接复用 S4 已落地的拉取器(
tmp/p42/puller.mjs)。 - ⛔ 未代用户起实例;⛔ 未动客户端工作区(
dsh-ai1net-desktop/dsh-desktop-0.1.7rc2只读引用)。 - 包内零直连平台库路径(红线扫描绿)。
- S9 的 P2 未跨过;本棒未发现新停点。
- 回滚:本棒包内改动集中在
lib/plugins-sdk.mjs(新建)/lib/index.js(M3 装配块 +statusOfReason)/lib/client.js(M3 卡片 + 窄屏段)/scripts/build.mjs(①f + ⑩)/package.json(exports加一条)。回滚 = 删lib/plugins-sdk.mjs+ 撤 M3 装配块与卡片即可(M5 与 S2–S5 的绿不受影响,M5_DIGEST可作前后一致的锚点)。
十一、出处
| 项 | 出处 |
|---|---|
| 七条包内路由冻结清单 | D:/github/dsh_shenxian/dsh-server-docs/05-交接单/交接单_包规格书_20260926.md §五 A3 |
| 包名/库名/表前缀冻结值 | 同上 §五 A4 / §五 A5 |
| 四拆判据原文 | 本线入口 §2 第 43 棒块 → 方案 §3.10.8 |
| 避坑类目 | 交付物/功能打包到基础插件-分步实施方案-20260926.md §4 |
| 编排脚本 | tmp/p44/run-s6.mjs(58 ✅ / 0 ❌)|tmp/p44/run-scales.sh |
| 读数 | tmp/p44/读数-S6-20260926.txt|tmp/p44/尺读数-S6-20260926.txt |
| 真执行者 | tmp/p42/puller.mjs + tmp/p42/mgr-mock.mjs(S4 产物,本棒⛔ 未改一行) |