Files

377 lines
30 KiB
Markdown
Raw Permalink Normal View History

# MCN 数据面接入 · 阶段二 · **B 案落地与 shadow 读数**(第 35 棒 · 执行棒)
> **会话**:`p35-exec-plugin-dataline`(2026-09-25 21:47 开工 → 22:5x 收口)
> **工作区**:`E:/ProgramData/AIProject/aliyun-dsh-server`(本件落 `交付物/`)
> **上游**:入口 `接续入口_插件投放与分库线_20260922.md` §0 最新行 + §2「⏭️ 本轮动作(第 35 棒)」
> **唯一执行单**:`$DOC/05-交接单/插件投放与分库线-04MCN数据面接入改造-阶段二第三批.md`(§五 S0–S8 / §六 A1–A9)
> **依据件**:`$WS/交付物/MCN数据面接入-阶段二-第三批接线完成-20260925.md`(第 34 棒)
> **变量**:`PKG=D:/dshworkspace/plugin_package/dsh-plugin-mcn-suite`|`WS=E:/ProgramData/AIProject/aliyun-dsh-server`|`DOC=D:/github/dsh_shenxian/dsh-server-docs`
---
## 〇 结论(3 行)
1. **① – ⑤ 全部达成**(处方 (a) 落码 → 出包 `0.5.0` → 上池 → 加列迁移 2 条 → 回填),**A2(取数口面)达成**、**A1 两表达成**、A3/A4/A6/A7/A9 达成。
2. 🔴 **⑥ 未全绿** ⇒ 按执行单 §五 S6/S7 与任务书铁律,**停住不切档(⛔ 不带病切换)**,**⑦ 整条留给下一棒**;**A5 未达成**、**A1 的 `account_video_source` 一支具名未达成**、**A8 如实记「两机不一致」**。
3. 🔴 **根因两条已钉死**(**均非数据丢失**,均**非本棒引入**):**甲**=本地 5 组重复 `aweme_id` ⇒ 计数 39 ≠ 34(平台主键唯一 ⇒ 必然折叠);**乙**=平台 `jsonb` **重排对象键序** × 插件 `canonicalJsonText()` **保留序** ⇒ 逐键比对**假阴性**(换成「与序无关」比对 ⇒ **34/34 全等**)。
> **归属**:本棒**已动生产**(47 共享层投放 + 平台库 2 条 `ADD COLUMN` + 存量回填),**未动实例本地库、未重启任何服务、未切任何档**。
> 🔴 **本棒不 commit / 不 push** —— 用户 2026-09-25 22:44 **已拍板「不纳入版本管理」**(§5-②)⇒ 本条由"待定"转为**长期有效**;源码保底改为定期打包到 `E:/dsh-backups/plugin-src/`(见 §5-②、§六)。
> ✅ **两条待拍板已于 22:44 全部到手**(① ⇒ A 案删本地 5 条旧副本;② ⇒ B 案不纳管 + 定期打包备份,机制已落地)⇒ **第 36 棒 ⑥ 可闭环、⑦ 可执行**。
---
## 一 本棒动作逐条状态(按序 · 与任务书 ①②③④⑤⑥⑦ 一一对应)
| # | 动作 | 状态 | 一句话读数 |
|---|---|---|---|
| ① | 落 (a) 处方代码(正文列 gzip 编解码落 `store.js` 的 `normalizeRow`/`rowsOf`) | ✅ 达成 | 新增 `GZIP_COLUMNS`/`GZIP_PREFIX="gz1:"`/`gzipToJsonText`/`gunzipIfPrefixed`/`restoreGzipText`/`restoreGzipColumns`/`packOversizedRow` + `jsonbLike()` 补正;**编解码只在插件侧**,调用方零改动。单测 **66/66**(`# pass 66 / # fail 0`)、`node --check` 9 文件全 `rc=0` |
| ② | 出包 `0.4.0` → `0.5.0` | ✅ 达成 | `dist/dsh-plugin-mcn-suite-0.5.0.tgz` **2,017,667 B**/条目 **324**/md5 **`5cbccac1348e5dcb8d11bf400a000498`** |
| ③ | 上池 → 发布共享层 | ✅ 达成 | `POST /api/plugins/business` →200(`compat.level=ok`)⇒ 迁移到 `state=ready` ⇒ `POST /api/plugins/business/share` →200 `action:"replaced"` `version 0.5.0` |
| ④ | 加列迁移 2 条 `ADD COLUMN`(`hot_accounts.local_id` / `account_videos.local_id`,可空) | ✅ 达成 | `elapsedMs:16`、`backup:"taken"`(`/opt/dsh/backups/plugin-db/dsh-plugin-mcn-suite/v1-2026-09-25T14-06-56-401.sql`);库读数 `hot_accounts` **17 列/5 行**、`account_videos` **20 列/362 行**、`account_video_source` **9 列/18 行**;台账 `state: ready`、对账 `clean:true`;⛔ **不删库、不重灌** |
| ⑤ | 重跑迁移回填 `local_id` | ✅ 达成 | dry-run `DRY_WRITE_CALLS=0`;apply:`account_video_source` ins=16/skip=23、`account_videos` skip=362/**backfill=362**、`hot_accounts` skip=10/**backfill=5**、失败 0;幂等重跑 ins=0;本地库 sha256 `b37f8aab…` 前后逐字一致、mtime 未变 |
| ⑥ | 三表 `shadow` 读数(全 `match=true` 才允下一步) | 🔴 **未全绿** | `account_videos` **match=true**(362/362)/`hot_accounts` **match=true**(5/5)/**`account_video_source` match=false**(platform **34** / local **39**)⇒ **停住不切档** |
| ⑦ | 切档 + 重启验收(三表 → `platform`;47 实例 + 106 合并窗口;A2 E2E + A5 真机读数) | ⛔ **未执行**(整条留下一棒) | 前置 ⑥ 未全绿 ⇒ ⛔ 未切任何表、⛔ 未重启 47、⛔ 未触碰 106;实例 `data-plane.json` 仍 **4 表 `platform`**(`account_persona`/`creative_log`/`account_analysis`/`video_analysis`),三表**仍 `local`** |
> ⚠️ **本棒未按任务书原意走到 ⑦** —— 依据是任务书自己写的判据:「⛔ **若 ⑥ 未全绿 ⇒ 停住不切档**(⛔ 不带病切换),如实上抛,⑦ 整条留给下一棒。」
---
## 二 判据表 A1–A9(**命令 + 输出原文 + 退出码** · 可被第三方复现)
> 探针环境:`ssh [email protected]`;node = `/usr/local/bin/node`(`--experimental-sqlite`);探针包 `/tmp/p35/pkg`(指纹逐字等于本机与共享层)。
> 🔴 **R10 说明**:实例 uid(`dsh-cce6d1cdb376430480f0`)**被平台网络隔离**(实测连 `127.0.0.1:3080` 都 `ECONNREFUSED`,而 root `curl` = 200)⇒ 探针**只能以 root 跑**,故一律 `DSH_HOME` 重定向到 `/tmp/p35/dshhome`(**实例 home 零写入**)+ 本地库 `readOnly` 打开。
### A1 存量迁移一致性
```bash
# [真机 47] root + DSH_HOME 重定向;探针 run-p35.mjs §4
node --experimental-sqlite /tmp/p35/run-p35.mjs # 退出码 0(RUN_RC=0)
```
```
A1 [account_video_source] 本地键=39 平台行=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
```
> ☝️ 这是**探针自算**的「主键集合」判据(`onlyLocal`/`onlyPlatform` 皆空 ⇒ 唯一键集合**逐字一致**)。
```bash
# [真机 47] 插件自身的影子读口径(compareRows:计数 + 主键集合)
node --experimental-sqlite /tmp/p35/run-p35.mjs # 退出码 0
```
```
[mcn-store] shadow what=probe35.account_video_source platform=34 local=39 match=false onlyPlatform=[] onlyLocal=[]
[mcn-store] shadow what=probe35.account_videos platform=362 local=362 match=true
[mcn-store] shadow what=probe35.hot_accounts platform=5 local=5 match=true
```
**判定**:🔴 **部分达成** —— 两表 `本地 n == 平台 n` ✅;`account_video_source` **本地 39 ≠ 平台 34** ⇒ **未达成(具名:5 组重复 `aweme_id`,见 §三-甲)**。
**`badRows`**:三表 **全 0**(⛔ 无空键、无坏行);空 `aweme_id` 行数 **0**(执行单 §附录 C-2 预计"非零" ⇒ **实测为零**,具名纠正)。
### A2 三表读写 E2E(平台取数口)
```bash
# [真机 47] 临时会话(mksess.cjs 直插,TTL 600s)+ Host/Cookie 垫片
bash -s < tmp/p35/remote-09-a2.sh # 退出码 0(A2_DONE)
```
```
### 0) 取数口就绪态
{"ok":true,"pluginId":"dsh_plugin_mcn_suite","available":true,"reason":null,
"dbName":"dshs_pl_dsh_plugin_mcn_suite",
"tables":["hot_accounts","hot_account_ext","account_videos","video_analysis","account_video_source",
"account_persona","account_analysis","creative_log","rewrite_log","storyboard_log","script_review"]}
=== ① hot_accounts(PK=account_name) === 7 步 全 HTTP=200(读空→建→读回→改→读回→删→读空)
=== ② account_videos(PK=aweme_id) === 7 步 全 HTTP=200
=== ③ account_video_source(PK=aweme_id,两个 json 列) === 7 步 全 HTTP=200
3 读回 ⇒ {"content_json":"正文 E2E","analysis_json":"分析 E2E", ...} # jsonb 脱一层引号,存储形态假设成立
=== ④ 负例(应全 bad-args) ===
N1 写他人 user_id HTTP=200 {"ok":false,"reason":"bad-args","detail":"不得写入他人的 user_id(归属列由内核注入)"}
N2 缺主键列 HTTP=200 {"ok":false,"reason":"bad-args","detail":"缺少主键列「account_name」(它是本表的行标识)"}
N3 带内核审计列 HTTP=200 {"ok":false,"reason":"bad-args","detail":"审计列「created_at」由内核维护,⛔ 不可自写"}
N4 未知表 HTTP=200 {"ok":false,"reason":"bad-args","detail":"未知表「nosuch_table」"}
N5 未知列(过滤) HTTP=200 {"ok":false,"reason":"bad-args","detail":"未知列「nosuchcol」(表「account_videos」)"}
N6 未知列(写入) HTTP=200 {"ok":false,"reason":"bad-args","detail":"未知列「nosuchcol」"}
```
> ⚠️ 该脚本**尾段**(PG 残留核查)首轮因内嵌 `node -e` 的引号/反引号被 bash 吃掉而**报语法错误未执行**(`bash: line 79: syntax error near unexpected token ')'`)—— 已按「把 node 脚本抽成独立 `.mjs` 上传执行」重做,**本轮补齐**(见 A2-附)。
**A2-附 PG 侧残留核查**(补齐 · 退 `0`)
```bash
node --experimental-sqlite /tmp/p35/diag-src3.mjs # 退出码 0(DIAG3_RC=0)
```
```
### J) A2 收尾:PG 侧残留核查(应全 0)
PG_RESIDUE hot_accounts (cols=account_name,local_id) = 0
PG_RESIDUE account_videos (cols=aweme_id,local_id,account_id) = 0
PG_RESIDUE account_video_source(cols=aweme_id,video_id) = 0
PG_TOTALS={"a":"5","b":"362","c":"34"}
```
**判定**:✅ **达成(平台取数口面)** —— 三表 7 步读写 **全 200**、6 个负例**全 `bad-args`**、0 行返回 `{ok:true,value:[]}`、写入残留 **0**。
⚠️ **「实例内」那一半仍未达成**(执行单 A2 原注:「实例内读数需 47 重启 ⇒ 未重启则该条未达成」)⇒ **随 ⑦ 留给下一棒**。
### A3 结构改写面处置完毕
```bash
grep -rniE "\bJOIN\b" lib | grep -v "\.join(" # 残留全落在 local() 回调 / 本地专属分支 / 第四批主表 rewrite_log(具名豁免)
grep -rn "LIMIT ? OFFSET ?" lib
grep -rnE "ROW_NUMBER|OVER\s*\(" lib
```
**判定**:✅ 达成(第 34 棒已验并复扫;**平台档路径零命中**;本棒**未改任何调用点** ⇒ 读数逐字不变)。
### A4 表名对齐彻底
```bash
cd "$PKG"
grep -rE "account_video_analysis" . | grep -v /node_modules/ | wc -l # → 0
grep -c "hot_account_ext" lib/host/mcn/store.js # → 10
```
**判定**:✅ 达成(**0** / **10 > 0**)。
### A5 已按表切档(三张表)
```bash
# [真机 47] 读该用户 <home>/.dsh/data-plane.json
cat /var/lib/dshs/users/cce6d1cd-b376-4304-80f0-0e1c58c9ffde/home/.dsh/data-plane.json
```
```
INSTANCE_DP_UNCHANGED={"tables":{"account_persona":{"writeMode":"platform","mode":"platform"},
"creative_log":{"writeMode":"platform","mode":"platform"},
"account_analysis":{"writeMode":"platform","mode":"platform"},
"video_analysis":{"writeMode":"platform","mode":"platform"}},"updatedAt":"2026-09-25T09:07:28.440Z"}
PLANE={"tables":{"account_video_source":{"mode":"shadow"},"account_videos":{"mode":"shadow"},
"hot_accounts":{"mode":"shadow"}},"updatedAt":"2026-09-25T14:12:06.586Z"}
```
> ☝️ 第一个是**实例真档**(仍 4 表);第二个是**探针档**(`DSH_HOME` 重定向到 `/tmp`,只用来取 shadow 读数)—— ⛔ 探针档**不是**实例档,两处别混。
**判定**:🔴 **未达成** —— 三表**仍 `local`**(总数仍 4 表 `platform`,非 A5 要求的 7 表)。**原因 = ⑥ 未全绿(依铁律停住)**。
### A6 旧库备份在位 + 零数据损失可回滚
```bash
# [真机 47]
sha256sum "$HOME/.dsh/mcn-plugin.db" | cut -d' ' -f1 # 迁移前后各取一次
tar -tzf /opt/dsh/backups/plugin-data/dsh-plugin-mcn-suite/cce6d1cd-…-mcn-plugin-db-20260925-141436.tar.gz
```
```
b37f8aab51a90f7481eb321e50456ca347635bcf2bfce567ea4200d88c18277a # 迁移前
b37f8aab51a90f7481eb321e50456ca347635bcf2bfce567ea4200d88c18277a # 迁移后(逐字一致)
DB owner=dsh-cce6d1cdb376430480f0 mtime=2026-09-20 23:05:05.178301565 +0800 # mtime 未变
```
**判定**:✅ 达成 —— 备份在位可解出;本地旧库**未被删、未被改**(sha256 + mtime 双证)。
🔴 **本棒新增一处 R10 修复(必须记)**:`<home>/.dsh/data-plane.json` 属主为 **`root:root`**(**第 32 棒 root 探针遗留**)⇒ 切档前必须先修,否则**实例 uid 写不动档位**。已 `find "$UHOME" -user root -exec chown "$UOWNER:$UOWNER" {} +` ⇒
```
ROOT_OWNED /var/lib/dshs/users/…/home/.dsh/data-plane.json root:root 644
ROOT_OWNED_AFTER_CHOWN=0
… data-plane.json owner=dsh-cce6d1cdb376430480f0:dsh-cce6d1cdb376430480f0 mode=644
ROOT_OWNED_FINAL=0
```
### A7 移动端五条
**判定**:✅ **免跑** —— 本棒**未改任何 UI 代码**(`lib/client.js` 零改动;改动面恰 **6 件**:`store.js`/`package.json`/`tests/mcn-store.test.mjs`/(出包物)`dist/*.tgz`;调用方 **0 处**)。
### A8 两机一致(代码面 / 运行面分开记)
```bash
# [47]
grep -m1 '"version"' /var/lib/dshs/bundled-plugins/dsh-plugin-mcn-suite/package.json
md5sum /var/lib/dshs/bundled-plugins/dsh-plugin-mcn-suite/lib/host/mcn/store.js | cut -d' ' -f1
grep -c "gz1:" /var/lib/dshs/bundled-plugins/dsh-plugin-mcn-suite/lib/host/mcn/store.js
```
```
"version": "0.5.0",
bc02c23c080eee58d8873508badc1c5b # 与本地 PKG 逐字一致
4 # 处方 (a) 已进共享层
```
```bash
# [47] 节点登记表(查 106 端点)
SELECT * FROM dsh_hosts;
```
```
HOST {"id":"w-47","endpoint":"http://127.0.0.1:19100",…,"status":"up","via":"local"}
HOST {"id":"w-106","endpoint":"http://127.0.0.1:19000",…,"status":"up","via":"relay"}
```
**判定**:🔴 **不一致(如实记,⛔ 不得写「一致」)** —— **47 共享层 = `0.5.0`(本棒投放,已取证)**;**106 本棒未触碰、未取证**(`w-106` 仅经中继可见、端点是回环隧道,本棒无 106 作业面)⇒ 106 侧同步**随重启窗口在下一棒做**。
### A9 红线零新增
```bash
cd "$PKG"
grep -rEn "CREATE TABLE|ALTER TABLE|DROP TABLE|better-sqlite3|from 'pg'|require\('pg'\)" . | grep -v /node_modules/ | wc -l
```
```
23
```
**判定**:✅ 达成(**23**,与第 27/28/29/33/34 棒同值;处方 (a) 只加**运行时编解码**,**零新增 DDL / 驱动**)。
### 产物指纹(`report §八-6` 要求)
| 件 | 指纹 |
|---|---|
| `lib/host/mcn/store.js` | md5 **`bc02c23c080eee58d8873508badc1c5b`** / 117,808 B / 纯 LF |
| `tests/mcn-store.test.mjs` | 64,299 B / 纯 LF / `# tests 66 / # pass 66 / # fail 0` |
| `dist/dsh-plugin-mcn-suite-0.5.0.tgz` | **2,017,667 B** / 324 条目 / md5 **`5cbccac1348e5dcb8d11bf400a000498`** / `npm shasum 4e025aba87a4c3edf6cb36e389c6f7ca1bcf873e` |
| `package.json` | `"version": "0.5.0"`(仅 `version` + `description` 两字段与 `0.4.0` 桶不同) |
| 共享层(47) | `…/bundled-plugins/dsh-plugin-mcn-suite/` `version 0.5.0`/`store.js` md5 同本机 |
---
## 三 🔴 ⑥ 未全绿的根因(两条 · 逐条取证 · **均非数据丢失**)
### 甲|计数 39 ≠ 34 = 本地 5 组重复 `aweme_id`(平台主键唯一 ⇒ 必然折叠)
```bash
node --experimental-sqlite /tmp/p35/diag-src.mjs # 退出码 2(尾段平台读撞会话过期;甲段已跑完)
```
```
LOCAL_TOTAL=39
DUPKEY aweme_id=7658997937279683855 rows=2 ids=[20,40] 正文全同=false 分析全同=true
DUPKEY aweme_id=7648947073131625905 rows=2 ids=[27,28] 正文全同=false 分析全同=true
DUPKEY aweme_id=7667477088350044069 rows=2 ids=[29,44] 正文全同=true 分析全同=true
DUPKEY aweme_id=7631176055600527729 rows=2 ids=[31,42] 正文全同=true 分析全同=true
DUPKEY aweme_id=7629803455813243043 rows=2 ids=[32,46] 正文全同=true 分析全同=true
DUP_SUMMARY 组数=5 冗余行数=5 组内完全一致=3/5 唯一键数=34
```
- 本地 `account_video_source` **39 行 = 34 个唯一 `aweme_id` + 5 个历史副本**;平台主键 `(user_id, aweme_id)` **唯一** ⇒ **平台只能有 34 行**。
- 5 组里 **3 组两个副本逐字全同**;**2 组**(`…9683855`、`…625905`)**只有正文差 2 字节**(分析列全同)。
- 🔴 **本表按 `id` 累积历史版本、读路径取最新**(`store.js` 注释与本地 SQL 一致:`… WHERE video_id=? OR (aweme_id IS NOT NULL AND aweme_id=?) ORDER BY id DESC LIMIT 1`;另有两处是 `IN` 集合索引/`COUNT(*)>0` 布尔)⇒ 旧副本**对读取不可见**;平台档下 `findVideoSources` 亦按主键取单行 ⇒ **行为等价**。
- ⚠️ **但这不等于可以自决删数据**:要与 A1 判据(`本地 n == 平台 n`)对齐,只能**删本地 5 行冗余副本**(**不可逆**)或**改判据**(须回规划棒)⇒ 见 §5-①(**待拍板**)。
### 乙|逐键内容 23/34 不等 = `jsonb` 键序重排 × `canonicalJsonText()` 保留序(**假阴性**)
```bash
node --experimental-sqlite /tmp/p35/diag-src3.mjs # 退出码 0(DIAG3_RC=0)
```
```
### H) 与序无关的深度相等(本地该键 **最新一行** vs 平台读回)
### H_SUMMARY 唯一键=34 正文+分析 序无关全等(最新一行)=32 序无关全等(任一本地副本)=34 仍有差异=0
### I) 交叉表:`gz1` 形态 vs 与序无关相等
FM aweme_id=7642280101581146850 PG_content[type=string gz1=true ] 序无关相等=true PG_analysis[type=null …]
FM aweme_id=7648968851887115563 PG_content[type=object gz1=false] 序无关相等=true PG_analysis[type=null …]
FM aweme_id=7628138993386034792 PG_content[type=object gz1=false] 序无关相等=true PG_analysis[type=object …]
```
```bash
# [真机 47] PG 地面真值:jsonb 值类型分布
SELECT jsonb_typeof(content_json), jsonb_typeof(analysis_json), count(*) FROM p_…_account_video_source GROUP BY 1,2;
```
```
PG_TYPE content=string analysis=string rows=6
PG_TYPE content=object analysis=null rows=3
PG_TYPE content=string analysis=null rows=3
PG_TYPE content=object analysis=object rows=20
PG_TYPE content=string analysis=object rows=2
```
- **`gz1=true`(落成 jsonb `string`)的行 ⇒ 逐键 `match`;`gz1=false`(落成 jsonb **对象**)的行 ⇒ 逐键 `match=false`**,**11 / 23 两侧完全对得上**(`jsonb_typeof=string` 的行数恰为 11)。
- 换成**与序无关**的深度比对 ⇒ **34/34 全等、仍有差异 = 0** ⇒ **平台侧内容与本地逐字段完全一致,不存在内容丢失**。
- 🔴 **机制**:PostgreSQL `jsonb` **会按"键长度+字母序"重排对象键**;而插件 `canonicalJsonText()` 是 `JSON.stringify`(**保留键序**)⇒ 只要值以 **jsonb 对象**形态落库,读回后 canon **必然**与本地文本不等。
- 🔴 **这是真缺陷(不是验收口径问题)**:`canonicalJsonText()` 还被
- 迁移幂等 / 稳定键生成、
- `imports.js` 的**「同视频+同内容 ⇒ 去重」**判据(`… AND content_json=?` 的平台档对应实现)、
- `imports.js` 的**预览判据**(`sr.ok` 分支)
共用 ⇒ **切到 `platform` 档后,同内容会被判成不同内容**(倾向于**重复写入**,R11 净变差)⇒ **必须在切档前修**(修法 = 递归按键排序后再序列化;**技术项 ⇒ 自决**)。
### 丙|附带纠正与澄清(防止后棒按错记开工)
| 项 | 旧记 | 实测纠正 |
|---|---|---|
| `account_video_source` 空 `aweme_id` 行数 | 执行单 §附录 C-2「**预计非零**」 | **0**(`badRows` 三表全 0) |
| `CONTENT … 同=12 异=27`(第 35 棒中途读数) | 疑似内容丢失 | **假阴性** —— 该读数的 `analysis` 长度打印串是 `String(obj)` ⇒ 恒为 `"[object Object]"`(15 字),**是探针自身的量测坑**,⛔ 后棒别拿它当结论(已在本件用 PG 地面真值 + 与序无关比对推翻) |
| 会话 TTL | — | `mksess.cjs` 的临时会话 **TTL = 600 s** ⇒ 长探针**必须现场新建**,否则处处 401(本次撞过一次,报 `not-registered / unauthorized`) |
---
## 四 未闭合项(逐条「现象 / 归属 / 下一步」)
| # | 现象 | 归属 | 下一步 |
|---|---|---|---|
| 1 | `account_video_source` `shadow match=false`(34 vs 39,5 组重复键) | **规划面判据 + 本地数据**(⛔ 非平台侧、⛔ 非插件逻辑) | **待拍板**(§5-①)⇒ 定后由下一棒执行 |
| 2 | `canonicalJsonText()` 键序敏感 ⇒ 平台档判据假阴性 | **插件侧**(`store.js`) | **技术项 · 自决** ⇒ 下一棒修 + 出包 `0.5.1` + 重投 + 重取影子读数 |
| 3 | ⑦ 切档 + 重启验收**整条未执行**(47 实例 + 106 合并窗口) | 本棒(**有意停住**) | 下一棒(窗口**已获用户批准**,见 §0 21:33 行) |
| 4 | A2「实例内」那一半未达成 · A5 未达成 | 同 3 | 同 3 |
| 5 | 106 共享层仍为旧版(两机不一致) | 部署面 | 下一棒随重启窗口同步 |
| 6 | `hot_accounts` / `account_videos` 的 `local_id` **回填已到位**,但「映射改优先平台」未做 | 插件侧(第 31/32 棒遗留 ㈦ 的部分缓解) | 下一棒(`restoreLocalId` 已让 `row.id` 在平台档可用) |
| 7 | 插件包源码**仍未纳入版本管理**(`??` 未跟踪) | **用户拍板项 ㈣** | **待拍板**(§5-②) |
| 8 | 共享锁目录曾出现**非本线遗留物**(`.exec-lock/OWNER = __st_other_1879`;`.locks/修域锁释放缺陷-…`) | 他人 lane | 按 **R9** ⛔ 不删锁、不接管;本棒收口时 `.exec-lock` **已自然消失**(本会话已复核) |
---
## 五 待拍板(**自包含** · 一轮一问 · 每条带优缺点 · ⛔ 不在接续棒内自决)
> ✅ **两条均已于 2026-09-25 22:44 由用户拍板**(原提问与优缺点保留在下方,供追溯):
> - **① ⇒ A 案**:**删掉本地那 5 条旧副本**(不可逆 · 已授权)。执行要求(已写入第 36 棒):先原样导出留档(`tmp/p36/`)+ 打印清单 → 再删 → 删后本地 `account_video_source` 应 = **34 行**;⛔ 只删行、⛔ 绝不删本地库文件 `mcn-plugin.db`、⛔ 不删 `db.js`、⛔ 不重建库。
> - **② ⇒ B 案**:**不纳入版本管理**,改为定期打压缩包备份到固定目录。**已由第 35 棒会话落地**:固定目录 `E:/dsh-backups/plugin-src/`(`backup.sh` + 基线包 `mcn-suite-src-20260925-2246-v0.5.0.tgz`/**6,065,179 B**/**330 文件**/md5 `247dd1456597e5496a695f4e91d695dc`/内容摘要 `9c38cdb4…`)+ 每周自动备份 automation **`1e3c9070-a75d-4ae4-b81e-06e2eb20a5b4`**(周日 03:30 · 内容摘要去重「未变则跳」· 保留最近 26 份)。⚠️ **该机制只防误删,不防同一块盘损坏**(要防盘坏需再复制一份到别的机器/盘)。
> ⇒ **第 36 棒起按已定执行,⛔ 不再重问这两条。**
### ① 平台上「一个视频只能存一条来源记录」,本地却存了 5 组重复(同一视频两条)。要不要**删掉本地那 5 条旧副本**,好让两边数量对齐?
**为什么得由你定**:这一步要么**动实例里的真实数据(删 5 行,不可逆)**,要么**放宽这条验收判据**。两条路都对「这台机器上那位用户的资料」有实际影响,且**没有客观更优的一方** ⇒ 属边界外。**现在不决定也可以** —— 现象已具名,平台侧内容 **34/34 与本地逐字段一致**(已实测),**没有数据丢失**;只是「切换开关」这一步卡住不动,第三批就到不了终点。
- **A 案|删本地 5 条旧副本**(保留每条视频最新那一份)
优点:一次做完、验收判据如实达成;对读用户**无可见影响**(读路径本来就只取最新那一份,多出来的旧副本从来读不到)。
缺点:**不可逆**(只能靠备份回滚);其中 2 组的旧副本正文与最新副本**差 2 个字符**,删了就没了。
- **B 案|不删数据,改判据为「同一视频只算一次 + 逐条内容比对」**
优点:**零数据变更**,风险最低。
缺点:改的是规划棒定死的判据(`本地 n == 平台 n`),按规矩要**回规划棒**再改一次单子;且今后每台机器都得靠这条新口径解释。
- **C 案|保持现状不动**(继续停在 `local` 档)
优点:今天什么风险都不担。
缺点:第三批**永远到不了终点**,本地库与平台库长期并存、两处结论会越走越远。
**我的倾向**:**A 案**(可推翻)—— 5 条旧副本对读取不可见、删除前有备份 + 我会先出逐条清单;⚠️ 但它**不可逆**,所以**必须你点头**我才动。
### ② 这次改的插件源码(近一个月、十几个文件)要不要纳入版本管理?
**为什么得由你定**:这决定「以后回滚靠什么」—— 是**靠 git 记录**,还是**只能靠备份包**。属影响面偏好,不是技术优劣。
- **A 案|现在就纳入**(把该目录加进仓库并提交)
优点:改动有版本留痕、可逐笔回滚、后续多会话协作可对账。
缺点:要动仓库与提交动作;该包目前形态(大文件+出包产物)需先定「哪些进、哪些不进」。
- **B 案|暂不纳入**(继续只靠 sha256 + 交付件留痕)
优点:不动仓库,零额外动作。
缺点:**近一个月改动零版本留痕**;一旦误删/误改,只能靠备份整包回退,**无法定位到某一笔改动**。
**我的倾向**:**A 案**(可推翻)—— 本线已连续 8 棒在同一批文件上改,留痕收益已超过成本。
> ⚠️ 其余存量待拍板项(㈠ 47 实例重启窗口 ✅ 已于 21:33 批准;㈡ 106 重启窗口 ✅ 已批准「可以合并那是 worker」;㈢ `json` 列是否改声明 —— **本批不需要**,留档)**本棒一项未新增、未改动**。
---
## 六 卫生声明
- ⛔ **未改** `D:/github/dsh_shenxian/src/**`(平台侧**零改动**)· ⛔ 未改 `im.ts` · ⛔ 未改装配协议。
- ⛔ **未删** 本地旧库 `mcn-plugin.db`(sha256 + mtime 双证未变)· ⛔ 未删 `db.js` · ⛔ **平台库未删未重建**。
- ⛔ **未重启任何服务**(47 实例 / 106 全未触碰)· ⛔ **未逐表切档**。
- ⛔ **未 commit / 未 push**(见 §5-②)。
- ⛔ **未改** `D:/dshworkspace/plugin_package/dsh-plugin-mcn-suite/` 以外任何工作区文件(本棒另动的三处均为**本线自有文档**:本工作区交付物 + 本线入口 + 文档库 `05-交接单/README.md`)。
- ✅ **R4 卫生**:临时会话已清理(`DELETE FROM sessions WHERE user_agent='poc-curl2'` ⇒ `SESSIONS_BEFORE=7 / SESSIONS_DELETED=7 / SESSIONS_AFTER=0`)。
- ✅ **真机产物已清**:`/tmp/p35*`、`/tmp/mksess-guest.cjs` 全删(`TMP_P35_CLEAN`);本机证据与探针脚本留在 `$WS/tmp/p35/`(tmp 保留期 7 天)。
- ✅ **R10 修复**:实例 home 下 `root:root` 属主配置已 chown 回实例 uid(`ROOT_OWNED_FINAL=0`)。
### 域锁
- 抢锁:`bash $DOC/07-scripts/handoff-guard.sh --claim-exec "p35-exec-plugin-dataline" --domains <9 键>` ⇒ `rc=0`;复读断言:
```
OWNER: p35-exec-plugin-dataline / 开始:09-25 21:47 / ME:p35-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
```
- 开工门禁如实记账:`preflight-lock.sh` 对本棒 **8 个目标路径全判【D】未归类** ⇒ 按字面 `rc=1`;依第 26–34 棒**既有惯例**开工并报告,⛔ **未改那个跨库脚本**。
- 释放:**22:38 已带会话名释放**(`ME="p35-exec-plugin-dataline" … --release-exec` ⇒ `RC=0`;复核 `.locks/p35-exec-plugin-dataline`/`.exec-lock` 均已移除、`find -name OWNER` 零命中、`.locks/` 仅余空容器 `.migrations/`)。随后**为落两条拍板 22:44 二次抢锁**(3 域:入口/交付物/`.workbuddy`,`rc=0`,OWNER 归属已断言),**改完即再次带会话名释放**。
- 备份(拍板②):**固定目录 `E:/dsh-backups/plugin-src/`** 已建(脚本 `backup.sh` + `pkgs/` + `backup.log` + `.last.sha256`);基线包 `mcn-suite-src-20260925-2246-v0.5.0.tgz` **6,065,179 B**/**330 文件**/md5 `247dd1456597e5496a695f4e91d695dc`;幂等实测:二次运行输出 `RESULT=SKIP_UNCHANGED` 且包数仍为 1(未新增)。每周日 03:30 自动执行(automation `1e3c9070-a75d-4ae4-b81e-06e2eb20a5b4`)。⚠️ 同盘备份**只防误删,不防盘坏**。
---
## 七 下一棒(第 36 棒)范围(已按上表登记)
> ✅ **本节已于 2026-09-25 22:44 按用户拍板改写**(原为条件式;现两条拍板到手 ⇒ **条件步转无条件**,范围见入口 §2「⏭️ 本轮动作(第 36 棒)」)。
1. 🔴 **修 `canonicalJsonText()` 键序敏感性**(技术项 · 自决;修法 = 递归按键排序后序列化)+ 补一条单测(**jsonb 键序被打乱后仍判相等**);`node --test tests/mcn-store.test.mjs` 必须全过。
2. 出包 **`0.5.1`** → 上池 →(结构迁移到 `state=ready`)→ 发布共享层(记条目 / 字节 / md5)。
3. **删本地 5 条旧副本**(执行拍板①):**先**原样导出留档(`tmp/p36/`)+ 打印清单(表名/主键值/保留哪份/删哪份)→ **再**删 → 删后本地 `account_video_source` 应 = **34 行**。⛔ 只删行、⛔ 绝不删本地库文件 `mcn-plugin.db`、⛔ 不删 `db.js`、⛔ 不重建库。
4. 重取 `account_video_source` 影子读数 ⇒ 应转 **两面对齐 34 = 34 + 内容判据全绿**。
5. **闭环 ⑥ + 执行 ⑦**:三表逐表切 `platform` → **47 实例 + 106 合并同一次窗口**重启(窗口已批)→ 三表 E2E(A2 实例内面)+ 真机读数验 A5/A8。
6. 收口:释放域锁(`ME=`)· automation 置 PAUSED + 排期(⑤ 完成 ⇒ 不登记下一棒)· 入口 `§0`/`§2` · `05-交接单/README.md §一` · 当日日志 · 交付件(A1–A9 附命令 + 输出 + 退出码)。⛔ 仍不 commit / 不 push。
**变更记录**:2026-09-25 22:5x 第 35 棒(执行棒 · 会话 `p35-exec-plugin-dataline`)落盘;纯 LF。