Files
dsh_ai1net_server/交付物/技能插件管理-S6落地与四轮回执-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

15 KiB
Raw Permalink Blame History

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 口径,本棒复测):

  1. 清单拿不到 ⇒ ok=false + pulled 空 + 本地零改动(目录清单前后一致)。
  2. 单包失败(截断 gzip)⇒ failed 恰含该包、另一包照常落地、失败已回报平台。
  3. 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.mjs B 段的真发取证(九条里 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 产物,本棒⛔ 未改一行)