Files
dsh_ai1net_server/交付物/MCN数据面接入-阶段二-第三批阻塞取证与存量迁移-20260925.md
T
admin 318430c9e9 chore(工作区): 插件投放与分库线收口入库(第 35 棒 + 22:44 拍板落盘)
范围 = 本线(插件投放与分库线)产物 + 记忆类,共 27 件:
- 接续入口_插件投放与分库线(1 件):§0 新增 22:44 拍板行;§2 第 36 棒范围改 5 步、条件步转无条件
- 交付物(22 件):MCN 数据面接入阶段一/阶段二系列(含 B 案落地与 shadow 读数)、
  P0 修通与移动端真机验收、pnpm-EPERM、两机 lib 差异、共享层台账语义、
  基础插件身份与回滚、插件接入验收、插件数据面取数口、移动端迁包与字号扩面、跨机错误消息
- 记忆(4 件):2026-09-24 / 2026-09-25 日志、MEMORY.md、本棒 automation 执行记录

⛔ 未含他线在途改动(只报告、不代提交):机制层 CODEBUDDY.md / state.py /
.codebuddy/rules/server-ops.md、接续入口_IM线、接续入口_StoryForge验收线、
其余 29 个 automation 目录、docs/规则与载体/、归档/、接续包_*。
2026-09-25 23:06:29 +08:00

15 KiB
Raw Blame History

MCN 数据面接入 · 阶段二 第三批:真实阻塞取证 + 存量迁移(2026-09-25 · 第 32 棒 · 执行棒)

上游:04-交接单/插件投放与分库线-04MCN数据面接入改造-阶段二第三批.md(唯一执行单 · §五 S0–S8 / §六 A1–A9) 上一棒:交付物/MCN数据面接入-阶段二-第三批按表切换-20260925.md(第 31 棒 · S0/S1/S2 完成 · S3 20/23) 入口:接续入口_插件投放与分库线_20260922.md(§0 最新行 + §2「🎯 本轮动作」) 变量:PKG=D:/dshworkspace/plugin_package/dsh-plugin-mcn-suite;NODE=E:/ProgramData/.workbuddy/binaries/node/versions/22.22.2-3/node.exe;PY=E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe;47=bt-server(47.77.182.89)


一 结论(一句话)

第三批的"四步链路"走完了它的前两步,但"逐表切 platform"这一步在三张表上全部不成立 —— 不是在等代码改完,而是撞到两处结构性阻塞**:account_videos / hot_accounts 缺本地自增 id 的承载位(前端以 row.id 为主标识),account_video_source 有 18/39 行正文超过自己声明的 30 KB 上限(最大 94,889 B)。 ⇒ 本棒不宣称第三批完成;A5(三表切档)未达成,A2 未达成(需实例重启窗口),其余判据见 §三。**


二 本棒完成项

# 项 状态
1 index.js /mcn/api/videos(04 单点名的"本棒唯一入口")平台档取数路径 ✅ 落地(平台无 JOIN/窗口/OFFSET ⇒ 插件内归约;local 档逐字不变)
2 store.js 新增 hotAccountMetaMap()(id ↔ {account_name, content},按表自己的档取) ✅
3 store.js 新增 videoLocalIdMap()(本地 id ↔ aweme_id 双向映射) ✅
4 单测:49 → 51 / 51(新增 ㊸ hotAccountMetaMap、㊹ videoLocalIdMap) ✅
5 迁移工具扩表:DEFAULT_TABLES +3 表、KEY_BUILDERS +3 天然键、拆表两写(hot_accounts ⇒ 主表 + hot_account_ext)、重复键预检 dupKeys ✅
6 三表存量迁移(47 真机 · 幂等 · 零数据损失) ✅ 两表全 match,一表受 quota 阻塞(§四)
7 node --check 全绿(store.js / index.js / migrate-local-to-platform.mjs) ✅

未做(理由不是"没时间",而是"做了会制造死代码"):data.js / imports.js / workshop.js / index.js 其余 ≈ 30 处第三批调用点的平台档接线 —— 它们全部以"该表已切 platform"为前提;而 §五 证明三表都切不了 ⇒ 现在写等于往生产包里塞永不被走到的分支(R11「可维护性」净变差)。⇒ 等 §五 的拍板落地后再一次性接线。


三 判据表(命令 + 输出原文 + 退出码)

# 判据 命令 输出原文(关键行) 码
A9 红线零新增 grep -rEn "CREATE TABLE|ALTER TABLE|DROP TABLE|better-sqlite3|from 'pg'" $PKG | grep -v /node_modules/ | wc -l 23(=第 27–31 棒同值;本棒零 DDL) 0
S1 单测 $NODE --test tests/mcn-store.test.mjs # tests 51 / # pass 51 / # fail 0 0
S1 语法 $NODE --check <各改动文件> 全 rc=0 0
A4 ⛔ 未改平台 src/** 本棒未 ssh 改任何文件;find $SRC -newermt "2026-09-25 16:49" -type f | wc -l 0 0
A7 未改 UI 改动面 = store.js / index.js / tools/ / tests/;lib/client.js 零改动 ⇒ 移动端五条免跑 —
A1 存量迁移一致性 §四(逐表 本地 n / 平台 n / onlyLocal / onlyPlatform) account_videos match=true;hot_accounts match=true;account_video_source match=false 0
A5 三表切档 读 <home>/.dsh/data-plane.json 未达成(三表均不切;档位已回滚到原 4 表) —
A2 三表读写 E2E 需 47 实例重启(待拍板㈠) 未达成(如实记) —
A6 旧库零损失可回滚 sha256sum mcn-plugin.db(迁移前后) 两次均 b37f8aab51a90f74…(2,383,872 B · mtime 未变) 0
A8 两机一致 未出包、未投放 代码面/运行面都还是旧的(共享层仍 0.4.0) —

四 真机读数(47 · 2026-09-25 17:0x · 探针 tmp/p32/run-p32.mjs + orchestrate.sh)

4.1 dry-run 清单(⛔ 零平台写请求 · 实测 DRY_WRITE_CALLS=0)

表 本地行 计划 坏行 重复键组 清洗列 剥列
account_video_source 39 21 18(全 quota-exceeded) 5 0 id
account_videos 362 362 0 0 0 id
hot_accounts 5 5 0 0 0 id

4.2 apply + 幂等重跑

APPLY [account_video_source] 本地=39 计划=21 插入=18 跳过=3 失败=0 坏行=18
APPLY [account_videos]       本地=362 计划=362 插入=362 跳过=0 失败=0 坏行=0
APPLY [hot_accounts]         本地=5  计划=5  插入=10 跳过=0 失败=0 坏行=0
   PART hot_accounts:     ins=5 skip=0 fail=0        ← 拆表两写
   PART hot_account_ext:  ins=5 skip=0 fail=0        ← 扩展表已接线(A4 判据 `grep -c hot_account_ext` > 0)
IDEM [account_video_source] 插入=0 跳过=21 失败=0     ← 幂等重跑全 skip
IDEM [account_videos]       插入=0 跳过=362 失败=0
IDEM [hot_accounts]         插入=0 跳过=10 失败=0

4.3 A1 比对(本地 vs 平台 · 主键集合)

A1 [account_video_source] 本地行=39 平台行=18 onlyLocal=[…5 例…] onlyPlatform=[] match=false
A1 [account_videos]       本地行=362 平台行=362 onlyLocal=[] onlyPlatform=[] match=true
A1 [hot_accounts]         本地行=5   平台行=5   onlyLocal=[] onlyPlatform=[] match=true

4.4 影子读(shadow 档真机)

SHADOW [account_videos]       source=local mode=shadow degraded=false {"platform":362,"local":362,"match":true}
SHADOW [hot_accounts]         source=local mode=shadow degraded=false {"platform":5,"local":5,"match":true}
SHADOW [account_video_source] source=local mode=shadow degraded=false {"platform":18,"local":39,"match":false}
STATS platformCalls=1188 platformOk=1188 platformFailed=0 shadowReads=3 shadowMismatch=1

4.5 47 侧精确诊断(sqlite3 现算)

account_video_source: 行=39 空 aweme_id=0 |
  content_json > 30720 B 的行=3(最大 78,682 B);analysis_json > 30720 B 的行=15(最大 94,889 B)
  重复 aweme_id 组=5 组(每組 2 行):7658997937279683855(20/40) · 7648947073131625905(27/28)
        · 7667477088350044069(29/44) · 7631176055600527729(31/42) · 7629803455813243043(32/46)
account_videos: 跨 account_id 重复 aweme_id 组=0 · 空 aweme_id=0
hot_accounts:   空 account_name=0

⚠️ 第 31 棒预期的 account_video_source「空 aweme_id 行」实测 0 行 ⇒ badRows 实际由 quota-exceeded 触发(预期外,见 §五-乙)。


五 🔴 两处硬阻塞(本棒新发现 · ⛔ 未拍板前不得自决)

甲 · account_videos / hot_accounts 缺「本地自增 id」的承载位

  • 现象:平台声明面没有 id 列 —— dsh.data.yaml 里 hot_accounts 首列 = account_name、account_videos 首列 = aweme_id,且文件头写明"本地 id INTEGER PRIMARY KEY 一律不声明(平台无代理自增 id)"。
  • 取证:① 声明面现读(13 / 16 列,均无 id);② 插件内核常量 LOCAL_ONLY_COLUMNS = ["id"](store.js)⇒ 写入前剥掉 ⇒ 实测迁移 剥列=["id"] 362/362。
  • 影响(用户可感知):前端以 row.id 为主标识 —— lib/client.js 6 处:打开账号详情(:3782)· 打开视频详情(:4669)· 切收藏(:3456)· AI 创作(:3714)· AI 视频视图(:3675)· 删除(:4579)。切档后新写入的账号/视频在平台上没有 id(本地表也不再新增行)⇒ 这些按钮拿到 undefined ⇒ 功能失效(R11「功能」维净变差)。
  • 为什么不能"具名降级"了事:D16 已规定"映射缺席 ⇒ 具名降级",但该处降级=按钮不可用,不是"少一列数据"。

乙 · account_video_source 的正文超过自己声明的 30 KB 上限

  • 现象:18/39 行被 quota-exceeded 拒收(content_json 3 行 / analysis_json 15 行),最大 94,889 B(≈ 上限的 3.1 倍);另 5 组重复键 ⇒ 平台最终只有 18 键。
  • 取证:迁移 dry-run BY_REASON={"quota-exceeded":18} + sqlite3 字节现算(§4.5);上限来源 = dsh.data.yaml 的 content_json/analysis_json: maxBytes=30720。
  • 影响:切档后这 18 条解析结果读不到(平台没有)⇒ 视频详情页的"解析结果/源脚本"缺一半(R11「功能」维)。

✅ 两处阻塞都不在待拍板 5 项里

04 单 §四 4.3 的 5 项(47 重启 · 106 重启 · 删库重建 · json 列两案 · 包源码纳管)一项未动;甲、乙是本棒实测新发现,上一棒只把甲登记为"未闭合项 3"(未给候选)。


六 待你拍板(一轮一问 · 自包含)

要你定的是:MCN 插件里"账号表 / 视频表 / 解析结果表"这三张表,还要不要继续往平台数据面搬(也就是要不要把插件改成"从平台读写这些数据")。 为什么需要你定:搬过去之后,插件工作台里的账号详情、视频详情、切收藏、删除、AI 创作这些按钮会读不到数据而失效(因为平台侧没有它们在用的内部编号),并且一部分解析正文(最大 94 KB)超出了当初设定的 30 KB 上限、搬不过去。要么先补两样东西再搬,要么先不搬。 ⛔ 不搬 = 现状不变、零风险;搬 = 需要先做下面的准备。

  • A 案 · 先补齐、再搬:① 给账号/视频在平台侧或插件本地补一个"编号台账"(新增账号时同时登记,编号就不会丢);② 把解析正文的上限从 30 KB 提到 100 KB 以上。(优点:三张表都能真正搬完,界面功能不受影响;缺点:要动插件声明面与写入逻辑,且上限调整需先在测试库验证不会让插件变成"无法启用"状态)
  • B 案 · 只搬干净的、难的先不搬:视频表 362 行已搬完且核对一致(这部分可以先切),账号表与解析结果表暂不搬。(优点:零风险推进一格,先把"视频列表"这条路走通;缺点:账号列表仍走本地,本次目标只完成三分之一)
  • C 案 · 本批到此为止:三张表都不搬,把"是否值得搬"留给后续统一评估。(优点:完全零风险、把精力挪到别的功能;缺点:前面两批已经投入的接通工作暂时用不上,且每拖一次代价只会更高)

我的倾向:A 案(唯一能让三表都真正搬完、且不牺牲界面功能的路径);其中"编号台账"是纯新增、可回滚,风险集中在"上限调整"那一步,建议先在测试库验证。


七 未闭合项(现象 / 归属 / 下一步)

  1. A5 三表切档未达成 —— 归属:口径缺口(规划侧未覆盖甲)+ 声明面配置(乙)。下一步:等 §六 拍板。
  2. ≈30 处第三批调用点的平台档接线未做 —— 归属:插件侧;理由见 §二(做了 = 死代码)。下一步:拍板后一次性接线。
  3. A2 实例内 E2E 未达成 —— 需 47 实例重启窗口(待拍板㈠);实例现载 0.3.15(不含本棒代码)。
  4. A8 两机一致未达成 —— 需出包/投放(属收口 S9–S11)。
  5. 迁移到平台的行已保留(account_videos 362 · hot_accounts 5+5 · account_video_source 18)—— ⛔ 未删(04 单:不删平台库);切档前它们不参与读路径。
  6. 平台侧缺陷(⛔ 未改 src/**,只报告):src/db/plugin-data/runtime.ts 对非法值返回的 reason 仍是笼统的 disabled(第 25 棒记档,本棒复核仍在)。

八 卫生声明(逐条点名)

项 结果 依据
⛔ 改 D:/github/dsh_shenxian/src/** 0 件 本棒只读该目录(0 次)
⛔ 手改用户 home 下 profile / .pnpm 唯一写入 = 该用户自己的档位文件 data-plane.json(即阶段一设计的回滚落点);已回滚到原 4 表 §4.6
⛔ 重启任何服务 未重启 全程 mksess.cjs 临时会话 + 探针,未动 systemctl
⛔ 投放任何版本 / commit / push 均未做 共享层仍 0.4.0;两个仓库未提交
⛔ 删任何库 / 删旧库 mcn-plugin.db 未删 迁移前后 sha256 逐字一致 b37f8aab51a90f74…
⛔ 未拍板前执行删库 / 重建 未做 json 列案 B 未出现在任何执行面
✅ 临时会话(R4) 造 2 次 → 删 2 次 → 残留 0 SESS_DELETED=1 SESS_LEFT=0
✅ 47 临时目录 /tmp/p32 已整体删除 ls -d /tmp/p32 ⇒ No such file
✅ 本机探针落点 $WS/tmp/p32/** —

4.6 档位回滚证据(⛔ 本棒探针曾误把两表留在 shadow,已修正)

回滚前:…, "account_video_source":{"mode":"platform"}, "account_videos":{"mode":"shadow"}, "hot_accounts":{"mode":"shadow"}
回滚后:{account_persona, creative_log, account_analysis, video_analysis} —— 4 表 platform(与迁移前逐字同构)

🔴 自证:探针把"切档"写成了无条件动作,未按 shadow 结果分支 ⇒ 与 04 单 S7"全 match=true 才可切"冲突。已当场回滚,并记入本件以防后棒照抄该探针。


九 域锁

项 值
会话名 p32-exec-plugin-dataline
域(11 个) dsh-server-docs/05-交接单 · aliyun-dsh-server/接续入口_插件投放与分库线_20260922.md · aliyun-dsh-server/交付物 · mcn/{store,data,imports,index,workshop,video-tools}.js · tools/migrate-local-to-platform.mjs · tests/mcn-store.test.mjs
复核方式 退出码 rc=0 + 复读 .locks/p32-exec-plugin-dataline/{OWNER,DOMAINS} 断言归属(会话 3e06798e-6379-460c-bd88-6c5ac88b767f)
门禁备注(如实) preflight-lock.sh 对 11 个目标路径全判【D】未归类(工作区根文件 + 文档库 05-交接单 + D:/dshworkspace 下的插件包路径都不在该脚本锚点覆盖内)⇒ 按脚本字面应 rc=1;依第 26–31 棒既有惯例开工并报告,⛔ 未改那个跨库脚本

变更记录:2026-09-25 17:0x 立件(第 32 棒 · 执行棒)。本件不含不可逆操作。