范围 = 本线(插件投放与分库线)产物 + 记忆类,共 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/规则与载体/、归档/、接续包_*。
189 lines
15 KiB
Markdown
189 lines
15 KiB
Markdown
# 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 棒 · 执行棒)。本件**不含不可逆操作**。
|