范围 = 本线(插件投放与分库线)产物 + 记忆类,共 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/规则与载体/、归档/、接续包_*。
15 KiB
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.js6 处:打开账号详情(:3782)· 打开视频详情(:4669)· 切收藏(:3456)· AI 创作(:3714)· AI 视频视图(:3675)· 删除(:4579)。切档后新写入的账号/视频在平台上没有 id(本地表也不再新增行)⇒ 这些按钮拿到undefined⇒ 功能失效(R11「功能」维净变差)。 - 为什么不能"具名降级"了事:D16 已规定"映射缺席 ⇒ 具名降级",但该处降级=按钮不可用,不是"少一列数据"。
乙 · account_video_source 的正文超过自己声明的 30 KB 上限
- 现象:18/39 行被
quota-exceeded拒收(content_json3 行 /analysis_json15 行),最大 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 案(唯一能让三表都真正搬完、且不牺牲界面功能的路径);其中"编号台账"是纯新增、可回滚,风险集中在"上限调整"那一步,建议先在测试库验证。
七 未闭合项(现象 / 归属 / 下一步)
A5三表切档未达成 —— 归属:口径缺口(规划侧未覆盖甲)+ 声明面配置(乙)。下一步:等 §六 拍板。- ≈30 处第三批调用点的平台档接线未做 —— 归属:插件侧;理由见 §二(做了 = 死代码)。下一步:拍板后一次性接线。
A2实例内 E2E 未达成 —— 需 47 实例重启窗口(待拍板㈠);实例现载0.3.15(不含本棒代码)。A8两机一致未达成 —— 需出包/投放(属收口 S9–S11)。- 迁移到平台的行已保留(
account_videos362 ·hot_accounts5+5 ·account_video_source18)—— ⛔ 未删(04 单:不删平台库);切档前它们不参与读路径。 - 平台侧缺陷(⛔ 未改
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 棒 · 执行棒)。本件不含不可逆操作。