- 变更规模:新增 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/ 知识文件,按口径入库)
25 KiB
S4「接进既有的分发与更新机制」· 落地与回执(2026-09-26 · 第 42 棒)
线 / 棒次:插件投放与分库线 · 第 42 棒(执行棒)|会话
插件投放与分库线-42执行依据:规格书D:/github/dsh_shenxian/dsh-server-docs/05-交接单/交接单_包规格书_20260926.md(§〇 / §五 A / §五 C) + 方案交付物/功能打包到基础插件-分步实施方案-20260926.md§3.10.6(S4 逐步照做唯一权威)+ 方案 S4 段 + §4 C 类坑 C1–C6 前置:第 41 棒 S3「数据面」已落地(建库dshs_pl_ai1net→ 迁移 M1→M5 → 对账差集双空,三轮全绿) 产物:拉取器tmp/p42/puller.mjs(照抄版)+ 沙箱 Managertmp/p42/mgr-mock.mjs+ 编排tmp/p42/run-s4.mjs+ 真机锚点tmp/p42/anchor-real.mjs读数:tmp/p42/读数-20260926.txt(S4 编排,53 ✅ / 0 ❌)|tmp/p42/尺读数-S4-20260926.txt(尺子)|tmp/p42/real-anchor/(真机包) 包内改动:零。S4 全部落在「分发侧 + profile 装配侧」,⛔ 未改E:/ProgramData/AIProject/dsh-plugin-ai1net/任何文件。
一、一句话结论
包能被平台「对账 + 增量拉取 + 原子换上」,落地后两端指纹逐字一致;三条反例判据全过(清单拿不到 ⇒ 本地零改动/单包失败 ⇒ 其余照常更新/extra ⇒ 只告知不删)。
判据表(退出码即结论):
| # | 判据 | 期望 | 实测 | 判 |
|---|---|---|---|---|
| S4-1 | 与权威实现逐段对得上(⛔ 不自创第二套) | 逐字 | 11 段逐字照抄(见 §二) | ✅ |
| S4-2 | 四步 happy path 跑通 | rc=0 | 对账→取包→原子换→回报 全通 | ✅ |
| S4-3 | 依赖写 link:<绝对路径>,两端逐字同形 |
逐字相同 | 两端字符串 100% 相同 | ✅ |
| S4-4 | 补 @deepseek-ai/* 软链 |
指向本机 scope | linked=1 / kept=3 / relinked=1(负控) | ✅ |
| S4-5 | 三条反例判据 | 全过 | 全过(见 §五) | ✅ |
| S4-6 | 装配后实例页「能力管理」栏出现 | 需真壳 | ⚠️ 未取到(rc=3) | ⚠️ |
| — | 尺子:装配端A / 端B + 取数 + 回调 + v-baseline | 全绿 | 5/5 rc=0 + baseline rc=0 |
✅ |
| — | 包内门禁 build.mjs --check-only |
rc=0 | rc=0 |
✅ |
| — | 真机锚点:本份指纹实现 vs 真机权威 treeSha256 |
逐字相同 | 两包都相等(storyforge 117 / mcn-suite 659,§六) | ✅ |
二、S4-1「照抄」逐段对照表(⛔ 判据 = 逐段对得上)
权威 = D:/github/dsh_shenxian/src/worker/agent.ts(节点侧)+ src/supervisor/plugin-assembly.ts(软链自足)+ src/web/routes/business-plugins.ts(Manager 侧)。
| # | 段 | 权威位置 | 落到本棒哪里 | 改了什么 |
|---|---|---|---|---|
| 1 | hashTreeAt() |
agent.ts:241-260 |
puller.mjs |
逐字(仅去 TS 类型) |
| 2 | envNumOr() + SHARED_PULL_* 7 个常量 |
agent.ts:194-227 |
puller.mjs |
逐字(默认值一字未改:20 000 / 300 000 / 900 000 / 64 KiB/s / 2 / 2 000 / stable) |
| 3 | NODE_PULL_STATE_NAME / MAX_PACK_BYTES / 拉取记录读写 |
agent.ts:262-298 |
puller.mjs |
逐字 |
| 4 | errText / PackError / isRetryable / defaultSleep / backoffOf |
agent.ts:365-393 |
puller.mjs |
逐字 |
| 5 | effectiveDownloadTimeout() |
agent.ts:412-421 |
puller.mjs |
逐字 |
| 6 | pullOnePack()(原子换六动作) |
agent.ts:423-525 |
puller.mjs |
逐字(含 tar 用 cwd+相对名的纪律、gzip 魔数、成员白名单、chown 仅 Linux、graveyard 回滚) |
| 7 | syncSharedLayerOnce() |
agent.ts:539-733 |
puller.mjs |
逐字 + 新增 1 个测试缝 __dumpPullStateTo(只多写一份记录,⛔ 不改算法/默认值) |
| 8 | resolveDshPackageRoot() / resolveHostDshScopeDir() |
plugin-assembly.ts:1185-1233 |
puller.mjs |
逐字同源副本(权威文件是 .ts,Node 跑不了;两文件均纯 Node 零框架依赖) |
| 9 | ensureSharedScopeLink() |
plugin-assembly.ts:1265-1311 |
puller.mjs |
逐字 |
| 10 | healSharedLayerSelfSufficiency() |
plugin-assembly.ts:1330-1365 |
puller.mjs |
逐字 |
| 11 | sharedDirNameOf()(flat 名口径) |
plugin-assembly.ts:871-873 |
puller.mjs |
逐字 |
沙箱 Manager 侧(mgr-mock.mjs):hashTree() ← business-plugins.ts:242-259 逐字|sharedLayerDiff() ← 同 :789-798 逐字|产物口打包口径 ← 同 :2528 起(tar -czf - --owner=0 --group=0 --numeric-owner <flat>,cwd=root)|对账端点口径 ← 同 :2432 起(inventory 指纹只认 64 hex,其余 null="不知道";pull = missing + stale)。
✅ 自查:本棒产出没有第二套机制 —— 全部是照抄 + 一个测试缝。
三、S4-2 四步实现(happy path)
对账 → 取包 → 落地(原子换六动作)→ 回报,全部走既有链路口径:
- 对账:
POST /api/plugins/shared/node/sync(头部带x-dsh-agent-token/x-dsh-host-id/x-dsh-pull-channel;body 带 inventory)。清单拿不到 ⇒ 早退,⛔ 什么都不动。 - 取包:
GET /api/plugins/shared/node/pack/<flat>?version=<v>;对账 20 s 与下载 300 s 两套超时分开(syncTimeoutMs/downloadTimeoutMs),并带packBytes量级提示动态放宽、封顶 900 s、最低速率 64 KiB/s(§4-C2)。 - 落地(原子换六动作):① 落
.pull-stage-<hex>②tar -xzf③ 成员白名单(必须全在<flat>/下,挡../与绝对路径)④ 自算hashTreeAt与平台告知的treeSha256逐字比 ⑤chown root:root(仅 Linux)⑥rename原子换(旧目录挪 graveyard → 新的就位 → 成了再删旧的;换失败把旧的挪回来);finally只清 stage ⇒ 最坏结果 = "没更新",⛔ 绝不留下半包。 - 回报:有失败才
POST /api/plugins/shared/node/result(⛔ 不参与任何判定)。
happy path 原始输出(节选自 tmp/p42/读数-20260926.txt)
════ B · happy path(四步:对账 → 取包 → 落地原子换 → 回报) ════
✅ ok=true —— reason=null
✅ missing = 两个包(本地全空) —— ["_dsh-local_ai1net","_dsh-local_probe2"]
✅ stale 全空(本地无记录 ⇒ 只能是 missing,⛔ 不会被折算成"一致") —— []
✅ extra 如实告知本地自有目录(⛔ 平台不据此删 —— 见 C-③) —— ["_dsh-local_leftover"]
✅ pulled = 两个包 —— ["_dsh-local_ai1net","_dsh-local_probe2"]
✅ failed 空 —— []
✅ channel = stable —— stable
✅ 回报端点:无失败 ⇒ 平台侧零回报 —— resultCalls=0
✅ 产物口调用 2 次(每包一次) —— ["_dsh-local_ai1net","_dsh-local_probe2"]
✅ 落地后指纹逐字相同 · _dsh-local_ai1net —— node=6f9e6fb121433adc2b29d6f58c2ff9b130b796f3fa70810fd99d4a384ca13d09 mgr=6f9e6fb121433adc2b29d6f58c2ff9b130b796f3fa70810fd99d4a384ca13d09
✅ 落地后指纹逐字相同 · _dsh-local_probe2 —— node=cd3bd919577b85e09c810cb3ff2ef511eda45504136eadb518c8c2bec19445d5 mgr=cd3bd919577b85e09c810cb3ff2ef511eda45504136eadb518c8c2bec19445d5
✅ .node-pull.json 记下了两包的 treeSha256(形状同真机 NodePullEntry) —— {"_dsh-local_ai1net":"6f9e6fb12143","_dsh-local_probe2":"cd3bd919577b"}
✅ 第二轮:ok=true 且 pull 清单为空 —— pulled=[] missing=[] stale=[]
✅ 第二轮:产物口零调用(不重拉 = C1 判据的正面) —— packCalls=2
🔴 「指纹逐字相同」是本节的核心判据:节点侧解包后自算的 hex 与沙箱 Manager 独立实现算出的 hex 逐字相等(36 字节全串,不是前 12 位摘要)⇒ 这同时是 §4-C1 的正面证明。
§4-C1 的机验(两侧算法必须同规则)+ 三条负控
════ A · 指纹算法两侧逐字一致(§4-C1) ════
✅ 两侧 hex 逐字相同 · _dsh-local_ai1net —— fileCount 8=8
✅ 两侧 hex 逐字相同 · _dsh-local_probe2 —— fileCount 5=5
✅ 负控①:不归一化 `\` ⇒ 与权威 hex 不等(判据能变红)
✅ 负控②:"只哈希文件名清单" ⇒ 与权威 hex 不等 —— C1 点名的近似物之一
✅ 负控③:改一个字节内容 ⇒ 权威 hex 变(指纹对内容敏感) —— 并已还原 ⇒ 指纹可复现
⚠️ 首版负控写的是"去掉排序 ⇒ 应不等",实测失败(该 fixture 只有 5 个文件,readdirSync 顺序恰好等于排序序)⇒ 已换成 C1 点名的两类真近似物(不归一化 \ / 只哈希文件名清单)⇒ 均能变红。教训:负控要挑"真会翻车的写法",不是随便改一刀。
四、S4-3 profile 装配(link: 两端逐字同形)
- 端A = 桌面 profile 实件副本(
E:/github/dsh-desktop-0.1.7rc2/.../home-adapt/profiles/desktop/package.json,只读引用,未改真件)+ 追加本包; - 端B = 平台侧形态(
profiles/web键位:bundles+dependencies的link:,patchReload: live); - 🔴 两端
dependencies["@dsh-local/ai1net"]逐字相同,且指向 拉下来的那一份(不是源包目录)⇒ 闭环成立:
✅ 两端 `link:` 值**逐字相同**(§4-C3)
A=link:E:/ProgramData/AIProject/aliyun-dsh-server/tmp/p42/node-shared/_dsh-local_ai1net
B=link:E:/ProgramData/AIProject/aliyun-dsh-server/tmp/p42/node-shared/_dsh-local_ai1net
✅ 端A / 端B 链接均解析到落下来的那一份
- flat 名口径实测:
sharedDirNameOf('@dsh-local/ai1net')=_dsh-local_ai1net;与真机既有包实读同形(_dsh-local_storyforge/_dsh-local_im-conversation-tabs/_softspark_dsh-file-preview)⇒ ✅ 口径一致。 - ⚠️ 真机平台侧的真值形态 =
link:/var/lib/dshs/bundled-plugins/_dsh-local_ai1net(/var/lib/dshs/bundled-plugins为真读值)。本沙箱用本地等价绝对路径代替 —— 这是建模,⛔ 不等于"真机装配已通"。
五、S4-5 三条反例判据(⛔ 不验不算完成)
① 清单拿不到(黑洞)⇒ 本地零改动
════ C-① 反例:清单拿不到(黑洞)⇒ 本地文件**零改动** ════
✅ ok=false 且 reason 指明"取不到" —— reason=sync_unreachable: The operation was aborted due to timeout
✅ pulled 空(一个包都没拉) —— []
✅ 本地文件零改动(全树 sha256 前后逐字一致) —— 23b0ab26f2788194 → 23b0ab26f2788194(31 项)
留痕条数 31 ⇒ 31;含软链快照,⛔ 不是只看文件
做法:黑洞服务收请求永不响应(比"连接被拒"更狠,连 HTTP 层都不给);实测走的是 AbortSignal.timeout(syncTimeoutMs) 那条真实路径。
⚠️ "零改动"口径:用自写强留痕(每个文件的 size + mtimeMs + 内容 sha256 + 每条软链的目标)算全树单值 —— ⛔ 不能只用 hashTreeAt,因为它刻意不计软链,会漏掉"软链被动了"这一类改动。
② 单个包失败 ⇒ 其余包照常更新
════ C-② 反例:单个包失败 ⇒ 其余包**照常更新**(不是整轮放弃) ════
✅ stale 含两包(都对不上权威) —— ["_dsh-local_ai1net","_dsh-local_probe2"]
✅ 主包**照常更新**(pulled 含它) —— pulled=["_dsh-local_ai1net"]
✅ 主包新指纹=权威新指纹(逐字)
✅ 失败只记备包一项(不是整轮放弃) —— ["_dsh-local_probe2"]
✅ 失败原因是**永久性**(错误页打进产物口 ⇒ pack_not_gzip) —— pack_not_gzip
✅ 永久性错误**不重试**(attempts=1;§A-3 语义) —— attempts=1
✅ ok=false 且 reason = partial:…(部分失败语义) —— reason=partial: _dsh-local_probe2=pack_not_gzip
✅ 备包**旧版仍在、零改动**(半包绝不落地) —— 5356194d200f01fb → 5356194d200f01fb
✅ 失败已回报平台侧(/node/result 1 次) —— resultCalls=1 failed=["_dsh-local_probe2"]
加演(同族、语义相反):把坏体换成截断 gzip ⇒ tar 抛裸异常 ⇒ 判为瞬态 ⇒ 会重试:
✅ 加演:截断 gzip ⇒ 判为**瞬态**并重试(attempts=3) —— attempts=3 err=Command failed: tar -tzf pack.tgz
⇒ 永久性 vs 瞬态的分流是真的在起作用(同一个 flat,两种坏体,两套行为),⛔ 不是"一律重试"也不是"一律放弃"。
③ extra(本地多出来的包)⇒ 平台只告知,本地一个文件没被动过
════ C-③ 反例:`extra`(本地多出来的包)⇒ 平台只告知,本地**一个文件没被动过** ════
✅ 对账告知了 extra 含它 —— ["_dsh-local_leftover"]
✅ 该目录**仍存在**(⛔ 没被删)
✅ 该目录内容与 mtime **零改动** —— 92cc4a9d4bb63109 → 92cc4a9d4bb63109
✅ extra ⛔ 不进 pull 清单(不会去拉它)
六、🔴 真机锚点:本份指纹实现 = 真机权威指纹(⛔ 不是近似物)
| flat | 真机台账 treeSha256(读 /var/lib/dshs/bundled-plugins/.manifest.json) |
本份 hashTreeAt 实算 |
文件数 | tgz 字节 | 判 |
|---|---|---|---|---|---|
_dsh-local_storyforge |
1c45c8418875df50a687359806b5b44e5125ffaf7f54c405a566a17d90b1ac3c |
同串 | 117 | 1,766,902 | ✅ |
dsh-plugin-mcn-suite |
27f6c6d7ee84f14666ff966750a7d7d41379d280aef72544ce154bc9f49fac9f |
同串 | 659 | 7,689,968 | ✅ |
════ 真机锚点 · _dsh-local_storyforge ════
取包命令:ssh [email protected] 'tar -czf - -C /var/lib/dshs/bundled-plugins _dsh-local_storyforge'
{"flat":"_dsh-local_storyforge","tgzBytes":1766902,"fileCount":117,
"got":"1c45c8418875df50a687359806b5b44e5125ffaf7f54c405a566a17d90b1ac3c",
"want":"1c45c8418875df50a687359806b5b44e5125ffaf7f54c405a566a17d90b1ac3c","equal":true}
rc=0
════ 真机锚点 · dsh-plugin-mcn-suite ════
{"flat":"dsh-plugin-mcn-suite","tgzBytes":7689968,"fileCount":659,
"got":"27f6c6d7ee84f14666ff966750a7d7d41379d280aef72544ce154bc9f49fac9f",
"want":"27f6c6d7ee84f14666ff966750a7d7d41379d280aef72544ce154bc9f49fac9f","equal":true}
rc=0
(原始读数:tmp/p42/真机锚点-20260926.txt)
做法(全程只读):ssh 47 'tar -czf - -C /var/lib/dshs/bundled-plugins <flat>'(与产物口逐字同口径的打包)→ 本机解开 → 本份 hashTreeAt() 算 → 与真机台账逐字比。⇒ 这一步把"照抄"从"看起来一样"变成"算出来一样",是本节最强的一条证据。两个包(117 / 659 文件)都逐字相同。
⚠️ 两处如实(都不影响上述判据):
- 首版清场把判据的退出码污染了:解包暂存目录带 659 个文件,本机的安全删除护栏会拦"一次删 >50 个文件"(
SAFE_DELETE_BULK_CONFIRM_REQUIRED)⇒rmSync抛错 ⇒ 脚本在equal:true之后仍以rc=1退出。 ⇒ 已把清场改成 best-effort(清场失败只打日志,退出码只由equal决定)。教训:辅助动作(清场)不许影响判据的退出码。 - 中途一次误读已被纠正:护栏那次"拦截"实际已部分删除了暂存树 ⇒ 事后想复用那份解包物复算,算出 415 文件 / 指纹不符。那不是指纹问题(当时比对是 659 文件 /
equal:true),而是素材已被动过 ⇒ 已改为重新解包再算,结论以上表为准。教训:"复用旧素材复算"前先确认素材没被动过。
七、尺子回归(⛔ 未改尺子一行)
| 面 | 靶子 / 入口 | rc | 说明 |
|---|---|---|---|
| 装配 · 端A 客户端形态 | --profile-json tmp/p42/端A-客户端形态/package.json |
0 | 解析到 tmp/p42/node-shared/_dsh-local_ai1net/lib/index.js 8405 B |
| 装配 · 端B 平台形态 | --profile-json tmp/p42/端B-平台形态/package.json |
0 | 同上(两端解析到同一份落下来的产物) |
| 取数(via 端B) | 同上 | 0 | 真实 import + 真实模块求值;inject=['connection'] ✅ |
| 取数(via 端A) | 同上 | 0 | 两端加载同一份 ⇒ 一致 |
| 回调(via 端B) | 同上 | 0 | 真实 apply(ctx),routeCalls 逐字记录 {"path":"/api/ai1net/ping","methods":["GET"]} |
v-baseline.sh |
47 共享层 + 本机 desktop profile | 0 | 平台侧 4 包;客户端侧 4 bundle |
| 包内门禁 | node scripts/build.mjs --check-only(包目录) |
0 | 「✅ 门禁通过」(含数据面 ①d 段,DECL_DIGEST=88c752a3e12597b3…) |
⇒ S2 / S3 的绿没被本棒弄红(尺子一行未改,用 P39_OUT 把读数引到 tmp/p42/ruler/,⛔ 未覆盖 p40/p41 读数)。
⚠️ 未取到 / 不成立(如实)
| 项 | 结果 | 原因 |
|---|---|---|
| S4-6 实例页「能力管理(插件/技能)」栏开始出现 | ⚠️ 未取到 | 需真壳在跑;启动器属客户端线(dsh-ai1net-desktop/.workbuddy/_devkit/launch-desktop-dev-017.mts),本线 ⛔ 不代启 |
v-ui.sh(浏览器级) |
⚠️ rc=3 未取到 | CDP 127.0.0.1:9330 不可达 ⇒ 本机无运行中的真壳(与前两棒同因) |
v-plugin.sh 运行 面 |
⚠️ 不成立(空真) | 可用启动日志 mtime = 2026-09-25T09:19:43,早于本包存在 ⇒ "无失败条目"不构成证据 |
| 真机投放(把包真投到 47/106) | ⛔ 刻意不做 | 属硬边界「⛔ 不代用户起实例 / 不碰把包装进去」;且包尚在沙箱验证阶段 |
八、🔴 平台既有链路的真实行为 vs 沙箱建模(逐条标注 · ⛔ 不许把建模当"已通")
| 项 | 平台既有链路的真实行为(读码 + 真机只读取证) | 本棒做法 | 是否 = 真实 |
|---|---|---|---|
| 对账/产物/回报三个端点 | 真端点存在且在跑:真机 .node-sync-reports.json 有 w-106 的汇报行(authoritative:4 missing:[] stale:[] extra:[]),时间 09-26 |
沙箱同口径建模(本机 http) | ⚠️ 建模 |
| 指纹算法 | hashTree(Manager)与 hashTreeAt(节点)两份实现、同算法,靠 test/shared-layer-sync.test.mjs 钉住 |
两份都照抄,互相对照 + 真机锚点验证 | ✅ 真实(见 §六) |
flat 名/link: 目标 |
/var/lib/dshs/bundled-plugins/<sanitizeFileName(pkg)>,真机实读 4 个 flat |
口径照抄,路径本地等价 | ⚠️ 口径真实、路径建模 |
@deepseek-ai 软链 |
真机 4 个包全部已有:→ /usr/local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai |
本机真 scope 目录(见 §九) | ✅ 形态与目标形状一致 |
| 鉴权 | 真平台查 dsh_hosts.agent_token 表(nodeIdentity) |
沙箱只比固定令牌 | ⚠️ 建模 |
| 版本 / 通道 / 钉版 | 真平台有 .versions/ 归档 + channel{stable,canary} + pinnedHosts(真机台账实读 dsh-plugin-mcn-suite 两个键都在) + resolveSharedTarget(钉版 > 通道 > 现役) |
沙箱只做"现役一版" | ⚠️ 未覆盖 B-1/B-2/B-3 三条路 |
| 审计落库 | 真平台写 app.db.audit(shared_layer_report) |
沙箱只写文件 | ⚠️ 建模 |
| 传输 | 真平台走公网 / relay(47↔106) | 回环 | ⚠️ 建模 |
🔴 结论纪律:本棒能断言的只有 —— 「照抄的那段拉取逻辑,在既有口径下四步跑通、三条反例成立、指纹与真机权威同源」。⛔ 不能据此断言「真机投放已通」「门户链路已通」「跨机对账已通」。真机投放属 S9/部署面。
九、S4-4 @deepseek-ai/* 软链(缺链现象 = "包在但加载不了")
════ S4-4 ════
✅ 解析到**本机真实** dsh scope 目录(⛔ 不是造一个)
scope=E:\github\dsh-desktop-0.1.7rc2\apps\desktop\.desktop-build\development\project-adapt\node_modules\@deepseek-ai\dsh\node_modules\@deepseek-ai
✅ scope 的父链确是 `<dsh 包根>/node_modules/@deepseek-ai`(用权威 isDshPackageDir 的同一判据)
dsh 包根 package.json name=@deepseek-ai/dsh
✅ scope 目录**名字层**含 dsh 宿主子包(dsh-base / dsh-agent) —— 共 119 项
✅ (预置后)三包链都在 ⇒ 本轮全 kept,一个字节没动
✅ 软链指向本机 scope · _dsh-local_ai1net / _dsh-local_probe2 / _dsh-local_leftover
✅ 缺链 ⇒ 补齐(linked=1),即"包在但加载不了"会被自愈
✅ 幂等:第二轮全 kept,一个字节没动
✅ 负控:指歪的链被纠正(relinked=1)
✅ 软链不计入 treeSha256(补链不改变指纹 ⇒ ⛔ 不会因此永远 stale)
🔴 为什么"必须每轮全量补"(照抄段注释):软链不进指纹 ⇒ 缺链的老包永远不会被判 stale ⇒ 只在铺装那一步补的话,老包会永远缺这一条。
⚠️ 一处如实:本机这份 dev 副本的 scope 里,这些条目是 pnpm 软链指向 monorepo packages/**,而该路径在本机不存在(悬空)—— 那是客户端线 dev 构建的现状,与本棒无关。本棒验证的不变量是"链指向本机解析出的 scope",⛔ 不验证 scope 内部是否可用。
十、§4-C1–C6 坑对表(逐条)
| # | 现象 | 本棒怎么处理 | 证据 |
|---|---|---|---|
| C1 | 永远 stale / 每轮全量重拉(指纹算法两侧不一致) | 两份实现都照抄;跨实现互算 + 真机锚点;3 条负控证明判据能变红 | §二 / §三 / §六;第二轮 packCalls 停在 2(不重拉) |
| C2 | 对账与下载共用一个超时 | syncTimeoutMs / downloadTimeoutMs 分开,packBytes 提示动态放宽、封顶、最低速率 —— 全部可被环境变量覆盖、⛔ 不写死 |
逐字照抄;黑洞反例走的是同步超时那条路径 |
| C3 | link: 目标两机不同 ⇒ 装配失配 |
两端 link: 字符串逐字相同;flat 名与权威 sharedDirNameOf 同口径 |
§四 |
| C4 | ERR_MODULE_NOT_FOUND(包在但加载不了) |
补链 + 缺链补齐 + 指歪纠正 + 幂等 四条判据 | §九 |
| C5 | 「不知道」被折算成「一致」 | 节点侧无记录 ⇒ 报 null(⛔ 不报假 hex);平台侧指纹只认 64 hex,其余当 null ⇒ 判 stale 重拉一次自愈 |
逐字照抄;实测第二轮 stale=[] 且 packCalls 未增 |
| C6 | 失败一轮全放弃 / 删了不该删的 | 三条语义纪律逐条实测:① 取不到 ⇒ 零改动 ② 单包失败 ⇒ 只记 failed,其余照常 ③ extra ⇒ 只告知、⛔ 不删 |
§五 |
十一、硬边界自查 / 遗留 / 回滚
- ⛔ 未改官方 dsh 主程序与缓存(R2):本棒只读
D:/github/dsh_shenxian/src/**(照抄来源)与本机 desktop 构建的 dev 副本,零写。 - ⛔ 未重造分发与更新机制:全部照抄(§二 对照表 11 段 + Manager 侧 4 段)。
- ⛔ 未代用户起实例;⛔ 未把包装进任何 profile(装配用的是 沙箱 profile,真件 profile 只读引用)。
- ⛔ 未改客户端工作区
dsh-ai1net-desktop/dsh-desktop-0.1.7rc2任何文件(只读引用;端A是 profile 副本)。 - ⛔ 包内零改动:
E:/ProgramData/AIProject/dsh-plugin-ai1net/一字未动。 - ⛔ 未 commit / 未 push;真机侧零写(只读
tar取包 + 读.manifest.json/.node-sync-reports.json/ls -l软链)。 - ⚠️ 红线门禁:本棒未触发任何一项 —— 未重启控制面、未中断在线用户、改动文件数 < 10、无不可逆操作(沙箱目录可整体
mv归档)。 - 🔴 停点:S4 的 P1 已解除(09-26 13:30 用户拍板 = A「放开桌面只读读包权」);S9 的 P2 仍待拍板,⛔ 本棒未跨过。
- 遗留(如实):① S4-6 /
v-ui.sh仍欠一笔真壳取证(客户端线窗口)② 沙箱未覆盖版本/通道/钉版三条路(真平台有,属 S9/发布面)③tmp/p42/real-anchor/_x-*是真机锚点的解包暂存(含 mcn-suite 659 文件树),因安全删除护栏(一次删 >50 文件需确认)保留未删 ⇒ 随tmp/7 天口径处理,⛔ 未绕过护栏。 - 回滚:本棒 无生产副作用 ⇒ 回滚 =
mv tmp/p42/ 归档/(按 §七 规格书回滚口径,⛔ 不用rm)。沙箱外部唯一变化 = 沙箱共享层里的软链与 profile 副本,全在tmp/p42/内。
十二、出处
- 规格书:
D:/github/dsh_shenxian/dsh-server-docs/05-交接单/交接单_包规格书_20260926.md - 方案:
交付物/功能打包到基础插件-分步实施方案-20260926.md §3.10.6 / S4 段 / §4 C 类 - 前置回执:
交付物/数据面-S3落地与三轮回执-20260926.md - 权威实现(只读):
D:/github/dsh_shenxian/src/worker/agent.ts|src/supervisor/plugin-assembly.ts|src/web/routes/business-plugins.ts - 本棒产物(⛔ 不入库):
tmp/p42/{puller,mgr-mock,run-s4,anchor-real}.mjs|tmp/p42/读数-20260926.txt|tmp/p42/尺读数-S4-20260926.txt|tmp/p42/real-anchor/