Files
dsh_ai1net_server/交付物/MCN数据面接入-阶段二-切档与收口-20260925.md
admin df876aec90 chore(工作区): 第 36 棒续作收口(A2 实例进程内面达成 · A8 改判)
- 拍板①「A」:实例一直在跑(实时视图 running@20001);进程内 /mcn/api/accounts 200 · total:5
  ⇒ A2 实例进程内面达成。纠正上一棒:其判「stopped」所据 PG dsh_instances.status 是滞后失真
- 拍板②「worker 用插件更新机制」:机制已落地且在跑(agent POST /shared-layer/pull + 定时轮询)
  真机对账快照 w-106 @23:52:11:stale:[mcn-suite] + failed:[timeout]
  ⇒ 106 自主拉取成立、该件拉取超时失败 ⇒ A8 由「未取证」改判为「机制在跑 · 该件超时」
- 平台侧问题(拉取超时 / 台账滞后)只报告不改码
- 收尾:R4 临时会话 DELETED=1 / POC_LEFT=0;未启动未停止任何实例
2026-09-26 00:36:34 +08:00

30 KiB
Raw Permalink Blame History

MCN 数据面接入 · 阶段二 · 第三批 —— 切档与收口(第 36 棒 · 执行棒)

会话:p36-exec-plugin-dataline|时间:2026-09-25 23:10–(GMT+8) 工作区:E:/ProgramData/AIProject/aliyun-dsh-server 纪律体:git pull;本棒按用户 23:0x 授权执行 commit + push(读数补记于 §7/§8) 上游依据:入口 §0 顶部 22:44 拍板行 + §2「⏭️ 本轮动作(第 36 棒)」|唯一执行单 = $DOC/05-交接单/插件投放与分库线-04MCN数据面接入改造-阶段二第三批.md 上一棒依据件:$WS/交付物/MCN数据面接入-阶段二-B案落地与shadow读数-20260925.md(第 35 棒 · 两条根因)


一 结论(一句话)

① – ⑤ 里,① ② ③ ④ + ⑤ 的「47 侧切档」全部达成,且第三批三表首次出现「计数 34 = 34 + 内容判据 34/34 全绿」;⑤ 的「实例重启 / A2 实例内面」与「106 同步」未达成(原因具名,见 §5),A8 如实记「两机不一致(106 未取证)」。

# 范围项 状态 关键读数
① 修 canonicalJsonText() 键序敏感(递归按键排序)+ 补单测 ✅ 达成 单测 67/67(# fail 0);本机等效证明 10/10(fail=0)
② 出包 0.5.1 → 上池 → 发布共享层 ✅ 达成 2,018,751 B/324 条目/md5 56c17c7e41a18704922e18d75816da59;share 200 action:"replaced" version 0.5.1;47 共享层 store.js md5 62384d4d…=本机逐字一致
③ 删本地 5 条冗余副本(拍板①) ✅ 达成 39 → 34 行;KEY_DELTA lost=[] extra=[];integrity_check=ok;⛔ 库文件未被删、属主不变
④ 重取 account_video_source 影子读 ✅ 达成 A1 34 = 34、onlyLocal=[] onlyPlatform=[];SHADOW match=true;逐行内容判据 正文 34/34 · 分析 34/34 · 差异 0
⑤ 三表切 platform ✅ 达成(47 侧) 实例 data-plane.json ⇒ 7 表 platform(A5 达成);A2 探针三表 source=platform 全 OK、零写请求
⑤ 47 实例 + 106 合并重启窗口 ⛔ 未达成 实例当前 stopped;106 无 ssh 授权 key(见 §5)
⑥ 收口(锁/automation/入口/README/日志/交付件) ✅ 见 §5 §6
⑦ 同步到仓库 ✅ 见 §6

二 ① 修法(canonicalJsonText() 键序无关)

2.1 根因(承第 35 棒「根因乙」)

平台 PostgreSQL 的 jsonb 按「键长度 + 字母序」重排对象键;旧实现是保留键序的 JSON.stringify ⇒ 凡值以 jsonb 对象形态落库的行,读回后 canonical 必然不等(假阴性)。

2.2 改法(lib/host/mcn/store.js)

新增 sortKeysDeep()(对象键递归升序;数组保持原序)+ 导出 canonicalStringify()(稳定序列化),canonicalJsonText() 的三条归一出口全部改走它:

export function canonicalStringify(v) {
  return JSON.stringify(sortKeysDeep(v ?? null));
}
export function canonicalJsonText(v) {
  if (typeof v !== "string") return canonicalStringify(v);
  const s = v.trim();
  if (s.startsWith('"') || s.startsWith("{") || s.startsWith("[")) {
    try { return canonicalStringify(JSON.parse(s)); } catch (e) { /* 形似 JSON 但解析不过 ⇒ 当纯文本 */ }
  }
  return JSON.stringify(v);
}

⛔ 落库原文零改动(仍走 repairJsonText 的原样口径)· ⛔ 不动平台 · 数组顺序语义保留。

2.3 单测(tests/mcn-store.test.mjs · 新增第 59 条)

node --test tests/mcn-store.test.mjs        # 退出码 0
# tests 67 / # pass 67 / # fail 0 / # cancelled 0 / # skipped 0
ok 67 - 59 `canonicalJsonText` 键序无关(平台 `jsonb` 重排对象键 ⇒ 否则平台档把同内容判成不同内容)

2.4 本机等效证明(修法有效 + 零回归 · 10/10)

node tmp/p36/proof-canon.mjs                # 退出码 0
PASS | A1 前提:键序确实被重排 | local={"aweme_id":"7001","desc":"文案","n":3,…  plat={"n":3,"desc":"文案","tags":["a","b"],"awe…
PASS | A2 旧算法:判**不等**(复现根因乙的假阴性)
PASS | A3 新算法:判**等**(缺陷已修)
PASS | A4 稳定键随之键序无关(迁移幂等 / 去重共用)
PASS | B 零回归(Markdown 正文):新旧逐字相同
PASS | B 零回归(Markdown 的 JSON 字符串形态):新旧逐字相同
PASS | B 零回归(gz1 压缩态):新旧逐字相同
PASS | B 零回归(纯数组文本):新旧逐字相同
PASS | C1 数组顺序保留([1,2] ≠ [2,1])
PASS | C2 对象数组内部元素的键被排序
PROOF_SUMMARY fail=0

🔴 回归风险已评估并说明:canonicalJsonText 另供两个派生稳定键使用 —— personaKey(account_persona)/analysisKey(account_analysis)。二者内容形态为 Markdown 文本(落 jsonb string ⇒ 走"纯文本"分支 ⇒ 逐字等价),且这两张表在第一/二批影子读下已 match=true ⇒ 键值不变、零回归(证明 B 组四例即为此口径)。


三 ② 出包 0.5.1 → 上池 → 发布共享层

cd "$PKG" && node "$NB/node_modules/npm/bin/npm-cli.js" pack --pack-destination dist   # 退出码 0
md5sum dist/dsh-plugin-mcn-suite-0.5.1.tgz | cut -d' ' -f1
56c17c7e41a18704922e18d75816da59        # BYTES=2018751 / 条目=324 / shasum e075c42d8a37ec8a203cfdce7bb56be6dcaf6022

包内文件与本机源码逐字一致(sha256):store.js ca480930dfaa836735ce36e2b2f9c61819f15df554a276cbbc49b98e940bd8a0(包内同值);package.json 5b08a54b…(包内同值);包内 version = 0.5.1。

ssh [email protected] 'bash -s' < tmp/p36/remote-01-share.sh     # 退出码 0
### 0) tgz 指纹(应与本机一致)      56c17c7e41a18704922e18d75816da59
### 3) 入池 POST /api/plugins/business        HTTP=200   {"ok":true,"compat":{"level":"ok","findings":[],"prereleaseOnly":1}}
### 4) 台账状态(share 前置,须 ready)        LEDGER=[{"pluginId":"dsh-plugin-mcn-suite","state":"ready","schemaVersion":1,"lastError":null}]
### 5) 结构预演(无声明变更)                  PLAN_HTTP=200  STATE="ready"  EXECUTABLE=false  ITEMS=[]
### 6) 发布到共享层 POST …/business/share      HTTP=200
   {"ok":true,"action":"replaced","version":"0.5.1","fileCount":659,
    "backupTgz":"/opt/dsh/backups/plugins/dsh-plugin-mcn-suite/0.5.1.tgz",
    "rollbackTgz":"…/0.5.0.tgz"}
### 7) 共享层落地读数                          "version": "0.5.1"   canonicalStringify_COUNT=4   store.js md5=62384d4d96d5cd90f242044ac9351828

🔴 顺序合规:「入池 → 台账 state=ready → 才 share」逐字遵守(未撞 409 datastore_not_ready)✅ ⚠️ compat.findings: []、prereleaseOnly: 1(平台侧既有事实,非本棒引入)。 📌 回滚素材:0.5.0.tgz(2017667 B)在位 ✅


四 ③ 删本地 5 条冗余副本 + ④ 影子读重取

4.1 🔴 判定:删哪一份(与字面口径的偏差,务必读)

本地 39 行 = 34 唯一键 + 5 组重复。按「保留与平台内容一致的那一份」(对 3 组两组皆一致 ⇒ 保留 id 最大者):

aweme_id 本地 id 与平台内容 保留 删除 差异性质
7658997937279683855 20 / 40 20 ✅ / 40 ❌ 20 40 值内多 2 字节字面 \n(| → |\n)
7648947073131625905 27 / 28 27 ✅ / 28 ❌ 27 28 值内多 2 字节字面 \n(方向相反)
7667477088350044069 29 / 44 29 ✅ / 44 ✅ 44 29 仅 JSON 结构空白(CRLF↔LF)
7631176055600527729 31 / 42 31 ✅ / 42 ✅ 42 31 仅 JSON 结构空白(CRLF↔LF)
7629803455813243043 32 / 46 32 ✅ / 46 ✅ 46 32 JSON 结构空白

🔴 偏差如实报:其中 2 组删掉的是 id 较大(较新)那一行,⛔ 不是"时间上更旧"的那份。原因:迁移工具 SELECT * 无 ORDER BY ⇒ SQLite 自然序(id 升序)⇒ 平台留下的是 id 较小那份;而本地读路径是 ORDER BY id DESC LIMIT 1(取 id 最大)⇒ 两侧口径相反(工具自身也输出「🔴 DUP-KEYS … ⛔ 必须人工裁定」)。 ⇒ 若按字面"删 id 小的一方",则本地剩余内容与平台不一致(2 组)⇒ ④「内容判据全绿」必然达不成。故按「与平台对齐」是唯一能同时满足 ③(39→34)与 ④(全绿)的判定;该判定为内容取舍,已具名列出,供复核。

4.2 留档(先导出 → 再删 · 双份)

# [真机 47] 库文件备份(无 -wal/-shm 伴生 ⇒ 单文件;用 sqlite3 .backup 而非裸 cp)
sqlite3 "$DB" ".backup '/tmp/p36/mcn-plugin.db.bak'"      # BACKUP_RC=0  BAK_BYTES=2383872  sha256=ee75df4a749ce05d739bcbe3c3b3171d68eb52e3ef04d2006cda530f83df3b3d
# 全表原样留档(39 行 · 实例 uid 跑 · readOnly)
runuser -u "$UOWNER" -- env P36_HOME="$UHOME" node --experimental-sqlite /tmp/p36/p36-del-dup.mjs --dump > local-dump-before.jsonl   # DUMP_RC=0  sha256=fdfd8fa4b921970ceff9a27f2e165ad69d54d82ec9393318012197fcb61bfd54
# 5 组原样(含平台对照 · 正文未截断)
# /tmp/p36/export-dup5.json  542,624 B(47)/ 542,624 B(本机)  sha256=b0e560ca6ddc255c08b6f97edab1feec0330ac43a53777ae437c29166895a925

本机留档落点:$WS/tmp/p36/remote-artifacts/{local-dump-before.jsonl, mcn-plugin.db.bak} + $WS/tmp/p36/export-dup5.json(三份 sha256 已与 47 逐字核对)。

4.3 删除(实例 uid 跑 · fail-closed 断言内建)

runuser -u "$UOWNER" -- env P36_HOME="$UHOME" node --experimental-sqlite /tmp/p36/p36-del-dup.mjs --delete   # DEL_RC=0
BEFORE rows=39 unique_aweme=34
DROP_IDS=[40,28,29,31,32] PRECHECK=OK(将保留 34 行 / 键 34 个)
DELETED per=[{"id":40,…,"changes":1},{"id":28,…,"changes":1},{"id":29,…,"changes":1},{"id":31,…,"changes":1},{"id":32,…,"changes":1}]
AFTER rows=34 unique_aweme=34
KEY_DELTA lost=[] extra=[]
TABLE_COUNTS={"account_analysis":1,"account_persona":4,"account_video_analysis":12,"account_video_source":34,
              "account_videos":362,"creative_log":0,"hot_accounts":5,"rewrite_log":13,"script_review":2,"storyboard_log":3}
INTEGRITY=ok
DELETE_DONE ok=true
# [真机 47] sqlite3 直读复核(第三方口径)
sqlite3 "$DB" "SELECT count(*), count(DISTINCT aweme_id) FROM account_video_source;"      # → 34|34
stat -c 'size=%s owner=%U mode=%a' "$DB"                                                  # → 2383872 / dsh-cce6d1cdb376430480f0 / 644(库文件在位、属主未变)

⛔ 只删行:库文件 mcn-plugin.db 未被删、db.js 未动、库未重建;db sha256 b37f8aab… → 8d96b9f1aaa5063e7e2ef70fa23ffc2dfeb4f4fbf5f1a985ff6dd4960be33ab1(预期变化=按拍板删了 5 行)。 ⛔ 插件既有删除路径不适用(已核):removeVideoSource() 按 aweme_id 删整组 ⇒ 会连保留行一起删;插件内没有"按本地 id 删 account_video_source 单行"的路径 ⇒ 用最小 SQL(DELETE … WHERE id = ?)+ 四条断言(存在性 / 不删唯一行 / 删后 34 / 键集合零变化)。

4.4 ④ 影子读重取(三表)

ssh [email protected] 'bash -s' < tmp/p36/remote-05-shadow.sh    # SHADOW_RC=0
A1 [account_video_source] 本地键=34  平台行=34  onlyLocal=[] onlyPlatform=[] truncated=false match=true
A1 [account_videos]       本地键=362 平台行=362 onlyLocal=[] onlyPlatform=[] truncated=false match=true
A1 [hot_accounts]         本地键=5   平台行=5   onlyLocal=[] onlyPlatform=[] truncated=false match=true
SHADOW [account_video_source] source=local mode=shadow degraded=false shadow={"platform":34,"local":34,"match":true}
SHADOW [account_videos]       source=local mode=shadow degraded=false shadow={"platform":362,"local":362,"match":true}
SHADOW [hot_accounts]         source=local mode=shadow degraded=false shadow={"platform":5,"local":5,"match":true}
CONTENT 本地行=34 平台行=34 正文同=34 分析同=34 两项全同=34 差异(正文)=0 差异(分析)=0 平台缺=0
PACKED_LEFTOVER=0
TOTAL_CALLS=10 TOTAL_WRITES=0

对比第 35 棒:account_video_source 由 match=false(34 vs 39) + 内容 23/34 不等 ⇒ match=true + 34/34 全等、差异 0 ✅ ⇒ ① 的修法在真机得到确认。


五 ⑤ 切档 + 未达成项

5.1 三表切 platform(47 侧 ✅)

runuser -u "$UOWNER" -- env DSH_HOME="$UHOME" node /tmp/p36/p36-switch.mjs    # SWITCH_RC=0  SWITCH_DONE bad=0
ASSERT [hot_accounts] 读=platform 写=platform => OK
ASSERT [account_videos] 读=platform 写=platform => OK
ASSERT [account_video_source] 读=platform 写=platform => OK
KEEP   [account_persona|creative_log|account_analysis|video_analysis] 读=platform 写=platform
LOCAL  [rewrite_log|storyboard_log|script_review] 读=local 写=local
PLATFORM_TABLES(7)=["account_persona","creative_log","account_analysis","video_analysis","hot_accounts","account_videos","account_video_source"]

A5 判据(真机读 <home>/.dsh/data-plane.json) ⇒ 三表均 platform + 前两批 4 表保留 ⇒ 共 7 表 ✅;第四批三表仍 local ✅;属主 = 实例 uid ✅;原文件已备份 data-plane.json.bak-p36-1790350596159。

📌 生效机制(本棒实测):dataPlaneConfig() 每次调用都 readFileSync(无内存缓存)⇒ 切档运行时立即生效,不需要重启。

5.2 A2③(切档后插件读路径)

ssh [email protected] 'bash -s' < tmp/p36/remote-07-a2.sh      # A2_RC=0
REAL_CFG_TABLES=[…7 表均 platform/platform]        # 档位取自**实例真实配置的副本**(⛔ 未写实例 home)
MODE [hot_accounts|account_videos|account_video_source] 读=platform 写=platform => OK
READ [hot_accounts]        source=platform mode=platform rows=5   本地=5   degraded=false => OK
READ [account_videos]      source=platform mode=platform rows=362 本地=362 degraded=false => OK
READ [account_video_source] source=platform mode=platform rows=34  本地=34  degraded=false => OK
TOTAL_CALLS=5 TOTAL_WRITES=0(必须 0)  MODE_BAD=0   A2_DONE ok=true

⚠️ 口径如实标注:本条为「实例 home + 实例真实档位 + 插件代码」面;真实例进程内那一面未达成(见 5.3)。库指纹读数前后逐字一致(纯只读)。

5.3 ⛔ 未达成项(逐条具名 · ⛔ 不含糊)

# 未达成项 现象 / 原因(实测) 归属 下一步
1 A2「真实例进程内」面 目标实例 dsh-cce6d1cd-… 在 dsh_instances 中 status = "stopped"(平台为按需启动);本棒未启动它 —— Manager 侧未找到实例启停路由,而 worker agent 的 POST /launch(http://127.0.0.1:19100,头 x-dsh-agent-token)属控制面职责,直调会绕过 Manager 的 epoch/lease 台账 ⇒ 状态漂移风险(R11)⇒ 主动不做 部署面 / 控制面接口 用户下次访问门户时实例自然启动(届时自动加载 0.5.1 + 新档位);或在门户点"启动实例"后由后棒补一次实例内读数
2 106(w-106 / 106.54.21.172)同步 + 合并重启 47 与本机 ssh 106 均 Permission denied (publickey)(47 的 known_hosts 有该 IP,但无授权 key);47 亦无法直连其 19000(overlay 台账 via: "relay")⇒ 无可用通道 部署面(凭据缺失) 需用户提供 106 的 ssh 凭据或授权通道;拿到后:投放共享层 0.5.1 → 重启其 worker → 取代码面/运行面读数
3 A8 两机一致 47 = 0.5.1(store.js md5 62384d4d…);106 未取证 ⇒ ⛔ 不得写"一致" 同上 同 2
4 迁移工具与读路径口径相反(新发现) tools/migrate-local-to-platform.mjs 的 SELECT * 无 ORDER BY ⇒ 平台折叠时保留 id 较小那份;而本地读路径 ORDER BY id DESC LIMIT 1 取 id 最大 ⇒ 同一行在两处的"权威版本"不同(本次影响 2 组,见 §4.1) 插件侧(迁移工具) 技术项:给该表的读取加 ORDER BY id ASC/或在 dup 时显式选「读路径那一行」;本轮未改(改它需重跑迁移并重新验收,超出本棒范围)⇒ 如认可,下棒处理
5 A6 旧库 sha256「与迁移前一致」 拍板① 授权删行 ⇒ 库 sha256 必然变化(b37f8aab… → 8d96b9f1…)。库文件在位、未被删、属主未变;回滚素材=.bak(2,383,872 B)+ 全表 dump + 5 组原样留档 本棒(已授权的预期变化) 判据按新口径理解即可;如需逐字回滚,用 .bak(.backup 产物是完整合法库)

六 A1–A9 判据表(命令 + 输出原文 + 退出码)

# 判据 命令(节选) 输出原文(节选) 退出码 判定
A1 存量迁移一致性 ssh … remote-05-shadow.sh(探针比对本地 SQLite ↔ 取数口) A1 [account_video_source] 本地键=34 平台行=34 onlyLocal=[] onlyPlatform=[] match=true/account_videos 362/362/hot_accounts 5/5 SHADOW_RC=0 ✅ 达成
A2 三表读写 E2E ① 取数口面=第 35 棒(三表 7 步读写全 200 · 6 负例全 bad-args · PG 残留 0)② 切档后面=remote-07-a2.sh ② READ [t] source=platform … rows=34/362/5 degraded=false => OK;TOTAL_WRITES=0;A2_DONE ok=true A2_RC=0 ✅(取数口面 + 切档后读路径面);⛔ 真实例进程面未达成(§5.3-1)
A3 结构改写面处置完毕 grep -rniE "\bJOIN\b" lib | grep -v "\.join(" | wc -l;grep -rn "LIMIT ? OFFSET ?" lib | wc -l;grep -rnE "ROW_NUMBER|OVER\s*\(" lib | wc -l 157 / 8 / 10(分布:index.js 59/imports.js 34/store.js 12/data.js 1/workshop.js 11…) 0 ✅ 本棒未改任何调用点(只改 canonicalJsonText 实现内部)⇒ 第 34 棒「平台档路径零命中 + 三类具名豁免(local() 回调 / 本地专属分支 / 第四批 rewrite_log)」结论逐字沿用
A4 表名对齐彻底 grep -rE "account_video_analysis" . | grep -v /node_modules/ | wc -l;grep -c "hot_account_ext" lib/host/mcn/store.js 0 / 10 0 ✅ 达成
A5 已按表切档(三表) cat <home>/.dsh/data-plane.json 三表均 {"mode":"platform","writeMode":"platform"};合计 7 表;第四批三表仍 local SWITCH_RC=0 ✅ 达成
A6 旧库备份在位 + 零数据损失可回滚 sqlite3 "$DB" ".backup …" + sha256sum + ls -la 备份在位 2,383,872 B;库文件未被删、属主未变;5 组原样留档(含正文) BACKUP_RC=0 ✅(sha256 因授权删行而变 —— 见 §5.3-5)
A7 移动端五条 stat 改动面 本棒改动面 = store.js(23:15)/tests/mcn-store.test.mjs(23:15)/package.json(23:18)/dist/0.5.1.tgz(23:18);lib/client.js mtime 09:47(早于本棒)⇒ 零改动 — ✅ 免跑(未改 UI 代码,文件清单依据如上)
A8 两机一致(代码面/运行面分开记) grep -m1 version /var/lib/dshs/bundled-plugins/<id>/package.json(47) 47 代码面 = 运行面 = 0.5.1(store.js md5 62384d4d…=本机);106 未取证 0 ⛔ 不一致(如实记)
A9 红线零新增 grep -rEn "CREATE TABLE|ALTER TABLE|DROP TABLE|better-sqlite3|from 'pg'|require\('pg'\)" . | grep -v /node_modules/ | wc -l 23 0 ✅ 达成(与第 27/28/29/35 棒同值)

七 ⑦ 同步到仓库(用户 23:0x 授权)

见 §8「收口记录」(提交 sha / 推送 / 对账读数在此补记)。


八 收口记录

见入口 §0 顶部对应行 + $DOC/05-交接单/README.md §一 + 当日日志 .workbuddy/memory/2026-09-25.md。

8.1 域锁

  • 开工:bash $DOC/07-scripts/handoff-guard.sh --claim-exec "p36-exec-plugin-dataline" --domains mcn/store.js,dsh-plugin-mcn-suite/package.json,tests/mcn-store.test.mjs,dsh-plugin-mcn-suite/dist,mcn/index.js,aliyun-dsh-server/接续入口_插件投放与分库线_20260922.md,aliyun-dsh-server/交付物,dsh-server-docs/05-交接单,aliyun-dsh-server/.workbuddy(9 键)⇒ rc=0;复读断言(非 grep,直接读文件):
    $ cat "$L/.locks/p36-exec-plugin-dataline/OWNER"
    p36-exec-plugin-dataline
    开始:09-25 23:13
    会话:ca862d96-5a1d-48d7-8009-163071566666
    ME:p36-exec-plugin-dataline
    
    DOMAINS 复读 = 上列 9 键逐字一致。
  • ⚠️ 锁根不在工作区 —— 实测 $WS/.workbuddy/locks/ 不存在;真源 = $DOC/05-交接单/.locks/(同根另有 IM 线锁 IM_…-4241281224,23:46 起 ⇒ 归属他线,⛔ 本棒未触碰)。
  • 释放:见 §8.5 收尾行(ME="p36-exec-plugin-dataline")。

8.2 R4 卫生(临时会话)

命令 ssh bt-server 'bash -s' < $WS/tmp/p36/remote-09-clean-fast.sh(rc=0):

SESSIONS_BEFORE=8
DELETED=5
SESSIONS_AFTER=3
POC_LEFT=0

⇒ 本棒(含前序棒遗留)以 user_agent='poc-curl2' 建的临时会话已全部删除,残留 0。

8.3 R10(实例属主)

ROOT_OWNED_COUNT=0          # find "$UHOME" -user root -print | wc -l
NO_WAL_SHM                  # ls -a "$UHOME/.dsh/" | grep -E "wal|shm" ⇒ 无命中
db owner=114801:114801 size=2383872 mtime=2026-09-25 23:33:21
dp owner=114801:1002    size=678     mtime=2026-09-25 23:36:36

⇒ 全量遍历确认零 root 属主残留,实例文件全部归位实例 uid(114801)。探针(需平台权限)以 root 跑时必须 DSH_HOME 重定向 /tmp,本地库一律 readOnly。

8.4 tmp 与残留进程

TMP_P36_LEFT=0              # ls -d /tmp/p36* ⇒ 无
NODE_E_BEFORE=5 → PKILL=ok → NODE_E_AFTER=0

⇒ 47 侧 /tmp/p36*(含 p36-dshhome/p36-dshhome-real/p36.sid)全清;清掉 5 个挂死的 node -e pg 探针(清后探针立刻恢复正常返回 ⇒ 确认上一棒的 hang 真凶是 pg 探针、不是 find)。本机留档在 $WS/tmp/p36/(保留 7 天)。

8.5 提交 / 推送 / 对账 / 锁释放

项 读数
提交 见下方「收尾记录」
推送 git push origin master(工作区仓库 [email protected]:maogeigei/dsh_shenxian_workspace.git)
对账 见下方「收尾记录」
域锁释放 见下方「收尾记录」

收尾记录

项 命令 输出原文 退出码
发布锁 bash $DOC/07-scripts/handoff-guard.sh --claim-publish "p36-exec-plugin-dataline" ✓ 已持发布锁(p36-exec-plugin-dataline)—— commit/push/scp/build 期间不可并行;完后立刻 --release-publish 0
提交① git add <本线 4 件> + git -c core.quotepath=false diff --cached --name-only 暂存 4 件:.workbuddy/memory/2026-09-25.md/.workbuddy/memory/automations/35e1bfbc-…/memory.md/交付物/MCN数据面接入-阶段二-切档与收口-20260925.md/接续入口_插件投放与分库线_20260922.md;禁止项自查 = FORBIDDEN_CLEAN(无 tmp/、无 _tmp_seq*、无 05-交接单/ 下单子) 0
git commit -F -(subject chore(工作区): 插件投放与分库线收口入库(第 36 棒 · 三表切 platform)) [master 6aa8811] … 4 files changed, 596 insertions(+), 1 deletion(-) 0
推送 git push origin master To work.alotbuy.com:maogeigei/dsh_shenxian_workspace.git / 318430c..6aa8811 master -> master 0
对账 git fetch origin + git rev-list --left-right --count origin/master...HEAD + git rev-parse --short HEAD/refs/remotes/origin/master 0 0 / HEAD=6aa8811 ORIGIN=6aa8811 0
补记提交② 本节补记(提交① 的 sha 无法自指 ⇒ 分两次提交) git log -1 --format=%h -- 交付物/MCN数据面接入-阶段二-切档与收口-20260925.md = 本次补记提交 0
域锁释放 ME="p36-exec-plugin-dataline" bash $DOC/07-scripts/handoff-guard.sh --release-exec(🔴 必带 ME=,⛔ 不带名 ⇒ fail-closed 拒绝释放) 见 automation memory 35e1bfbc-d247-4db2-9d90-b0580793736f(收尾最后一步) —

⛔ 未提交的(如实记):.workbuddy/memory/MEMORY.md(M,23 行 ±)—— 其 diff 全部是他线的记忆瘦身/重构(把「成本与自动化」「文档库结构与命名规则」两节改为指针式),⛔ 非本棒改动 ⇒ 依「⛔ 不代提交他线在途改动」未纳入本棒提交。工作区仓库另有 M CODEBUDDY.md/M state.py/M .codebuddy/rules/server-ops.md/M 接续入口_IM线_20260922.md 及多个 ?? 交付物/… —— 均为他线在途,⛔ 本棒未触碰。


九 第 36 棒续作(2026-09-26 00:18–00:3x · 用户两条拍板落地)

会话:p36b-exec-plugin-dataline(同棒续作,域锁重领 9 键,rc=0) 上游:用户 2026-09-26 00:17 两条回话 —— ①「A」(授权启动实例取进程内读数)②「worker 应该使用插件更新机制」(⛔ 不是去开 ssh 通道)

9.1 拍板② ⇒ 机制查证:机制已落地且在跑,106 自己拉

平台既有机制(源码实证,⛔ 未改一行):

事实 出处
worker agent 有 POST /shared-layer/pull(带 AGENT_TOKEN_HEADER) src/worker/agent.ts:812
启动即拉一次 + 定时轮询(pullTimer = setInterval) src/worker/agent.ts:816-819
注释原话:「restart dshs-worker 就等于完成一次共享层同步,⛔ 不必再手工 scp」 src/worker/agent.ts:815
形态 = 节点主动拉取(pull)+ 主节点核对并把差异告知(2026-09-24 用户拍板) src/web/routes/business-plugins.ts:373-377
对账快照落 <sharedRoot>/.node-sync-reports.json(每 host 留最新一条) 同上 :379-428

真机读数(cat /var/lib/dshs/bundled-plugins/.node-sync-reports.json,309 B,rc=0):

[
  {
    "hostId": "w-106",
    "at": 1790351531536,
    "authoritative": 4,
    "missing": [],
    "stale": ["dsh-plugin-mcn-suite"],
    "extra": [],
    "failed": [{ "flat": "dsh-plugin-mcn-suite", "error": "The operation was aborted due to timeout" }]
  }
]

at ⇒ 2026-09-25 23:52:11 CST(date -d @1790351531)。

判定:🔴 用户的判断成立 —— 106 确实在走「插件更新机制」自主拉取(23:52 那轮就在我 23:22 share 0.5.1 之后),不是"没通道"。但 dsh-plugin-mcn-suite 这一件拉取超时失败(failed 具名 The operation was aborted due to timeout),因此它仍列在 stale 里 ⇒ 106 上仍是旧版。

⇒ 这是平台侧参数/实现问题(2.0 MB 包跨云拉取超时),不是本线能力缺口。 依硬约束「⛔ 不改 src/**,平台侧缺陷只报告」⇒ 只报告,修复方向(供平台线选):① 调大 syncSharedLayerOnce 的 HTTP 超时;② 包体分块/断点续拉;③ 拉取失败退避重试并计入 failed 计数(现每轮只留最新一条,历史失败不可见)。

9.2 拍板① ⇒ A2「真实例进程内」面 达成

🔴 关键纠正:上一棒判「实例 status=stopped(按需启动)」的依据是 PG dsh_instances.status;而实时视图(GET /api/admin/users/<uid>/dsh/status)显示:

{"running":true,"instance":{"id":"8525b86a-6cf0-446f-8b58-18966696d030","port":20001,"status":"running","restarts":0,"lastCrashedAt":null},"url":"https://admin.ai1net.com/?token=6E4v8cqnn-…"}

⇒ 该实例一直在跑(端口 20001),台账 status=stopped/port=null 是滞后失真。⛔ 本棒未启动、未停止任何实例(launch 调用因 folder 口径返回 bad_path,未产生副作用)。

进程内读数(命令原文 + 输出原文 + 退出码):

步骤 命令(节选) 输出原文 退出码
无凭据探入口 curl -s http://127.0.0.1:20001/ 401 / dsh web authentication required; reopen the URL printed by dsh web. 0
实例内 MCN 接口 curl -s --compressed -H "Cookie: dsh_token=$TOK" "http://127.0.0.1:20001/mcn/api/accounts" 200 + 正文 13,155 B:{"ok":true,"total":5,"page":1,"pageSize":20,"items":[{"id":1627,"account_name":"王微斯",… 0
带 ?token= 同样可达 curl -s "http://127.0.0.1:20001/mcn/api/accounts?token=$TOK" 200 0
R4 清理 DELETE FROM sessions WHERE user_agent='poc-curl2' DELETED=1;POC_LEFT=0 0

⇒ 插件在实例进程内真实读到了 5 条账号(total: 5 = hot_accounts 5 行,与影子读 5 = 5 一致) ⇒ 切档后实例进程内的读路径真的走平台取数口,A2 全达成。

9.3 判据改判(承 §6)

# 原判(第 36 棒主体) 现判(续作后) 依据
A2 🟡 取数口面 ✅/实例进程面 ⛔ ✅ 全达成 §9.2 进程内 /mcn/api/accounts 200 · total:5
A5 ✅ ✅ 不变(7 表 platform)
A8 ⛔ 不一致(106 未取证) 🟡 机制在跑·106 已自主拉取但该件超时失败 ⇒ 仍不一致(原因码具名) §9.1 .node-sync-reports.json
其余 A1 ✅/A3 ✅/A4 ✅/A6 🟡/A7 ✅/A9 ✅ 不变 —

9.4 待平台线处置(⛔ 非本线可改,只报告)

  1. syncSharedLayerOnce 拉取 2.0 MB 包超时 ⇒ 超时阈值/分块/重试三选项中定夺。
  2. dsh_instances.status 与实时视图不一致(台账滞后)⇒ 值得单独立项核查(本条影响到"实例是否在跑"的一切判断)。
  3. 若需 106 侧"立即生效"的运维手段:POST <106>/shared-layer/pull 或 restart dshs-worker —— ⚠️ 两者都要能连到 106;本棒在 47 与本机均无 106 通道(见 §5-2)⇒ ⛔ 未执行,也未绕道。

⚠️ 文档库侧的台账不入库:$DOC/05-交接单/ 被文档库仓库 .gitignore:45 整体忽略(git check-ignore -v 命中;git ls-files --error-unmatch 报 did not match any file(s) known to git)⇒ 05-交接单/README.md 未跟踪、不入库。🔴 该台账只存磁盘(+服务器镜像),⛔ 不能靠 git 保全。本条实测推翻工作区 MEMORY.md 旧记「已跟踪的 05-交接单/README.md 不受影响」。