260 lines
25 KiB
Markdown
260 lines
25 KiB
Markdown
# MCN 数据面接入 · 阶段二 · **A 案落地与取证**(第 33 棒 · 执行棒)
|
||||
|
|
|
|||
|
|
> **执行会话**:插件投放与分库线-第 33 棒(2026-09-25 19:5x–20:2x)|**依据**:入口 §0「用户拍板 —— A 案」行 + 唯一执行单 `05-交接单/插件投放与分库线-04MCN数据面接入改造-阶段二第三批.md`
|
|||
|
|
> **上游拍板**:A 案 = ① 给「账号」「视频」两类记录在平台侧补**稳定主标识** ② `account_video_source` 正文列声明上限 30 KB ⇒ ≥100 KB
|
|||
|
|
> **变量**:`PKG=D:/dshworkspace/plugin_package/dsh-plugin-mcn-suite`|`WS=E:/ProgramData/AIProject/aliyun-dsh-server`|`DOC=D:/github/dsh_shenxian/dsh-server-docs`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一 结论
|
|||
|
|
|
|||
|
|
**A 案的两项已在「声明面 + 插件代码面 + 单测」全部落地,并在 47 真机取到硬证据;🔴 实测推翻该案的「只能删库重建」代价 —— 本案不需要删库,只需两条 `ADD COLUMN`(零数据损失、无需重灌)。**
|
|||
|
|
|
|||
|
|
同时拿到三条必须让用户知道的事实:
|
|||
|
|
|
|||
|
|
1. 🔴 **「删库重建」不必要**(原拍板代价可省)。真机预演:新声明 ⇒ `forbidden=0`、`items=2×add_column`;`maxBytes` 改动**零 diff 项**(平台 `diff.ts` 只比类型、不比 `maxBytes`)。⇒ 本案落地 = **一条迁移(加列)**,**不是**删库 + 10~20 分钟重灌。
|
|||
|
|
2. 🔴 **A 案后半段有效但不彻底**:`account_video_source` 可上台行数 **21 → 27**(+6);**剩 12 行仍被「平台单行 64 KiB 上限」拦住** —— 瓶颈已从「列上限」转移为「平台配额 `maxRowBytes` 默认 64 KiB,且 `plugin-data.ts` 构造时**未传 `quota`** ⇒ **无配置口**」。⇒ 这 12 行要么永远留在本地,要么动平台侧(⛔ 本棒禁改 `src/**`)。
|
|||
|
|
3. ⏸️ **本棒未做出包 / 上池 / 加列迁移 / 切档**(理由见 §六)—— 这三步都要求**动生产**(门户候选池 + 平台库),且第 1 条已改变原授权所设想的动作形态(「重建」⇒「加列」)⇒ 依 R8 与「影响面」判据**列为待拍板**,⛔ 未自决。
|
|||
|
|
|
|||
|
|
**未达成项**:`S3` 剩 1 处、`S4`、`S5`、`S6`、`S7` 全部未开工(逐条见 §七)|`A2`/`A5`/`A8` 需生产窗口或未投放 ⇒ 未达成|`A1` 的新口径读数已取(见 §四),但**平台侧只有 27 行**(存量 21 行上台 + 本案新增 6 行的可行性已证,尚未执行)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二 A 案两项落在哪(逐项 · 可复核)
|
|||
|
|
|
|||
|
|
### 2.1 声明面 `dsh.data.yaml`
|
|||
|
|
|
|||
|
|
| # | 改动 | 精确内容 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| ① | `hot_accounts` 加列 | `- { name: local_id, type: text }`(**可空** —— 平台禁 `notNull` 无 `default`;⛔ 不作主键,首列仍是 `account_name`) |
|
|||
|
|
| ② | `account_videos` 加列 | `- { name: local_id, type: text }`(同上) |
|
|||
|
|
| ③ | `account_video_source.content_json` | `maxBytes: 30720` ⇒ **`128000`** |
|
|||
|
|
| ④ | `account_video_source.analysis_json` | `maxBytes: 30720` ⇒ **`128000`** |
|
|||
|
|
|
|||
|
|
⚠️ 上限取 **128000** 而非 100000:实测最大正文 **94,889 B**,留 ~35% 余量。
|
|||
|
|
⚠️ `store.js` 的 `COLUMN_MAX_BYTES` **同步改同值**(⛔ 不改会让插件侧仍按 30 KB 拒收而平台已放行 ⇒ 两侧口径分叉)。**其它任何表的 `maxBytes` 一列未动**(`video_analysis` / `account_persona` 仍 49152)—— 防顺手扩大。
|
|||
|
|
|
|||
|
|
🔴 **补正(20:2x)**:初稿声称 ③④ 已落 —— 复核时发现 `dsh.data.yaml` **实际仍是 `maxBytes: 30720`**(① ② 两处 `local_id` 加列是真落了)。已按 A 案原授权**补齐**为 `128000`(+ 3 行说明注释),并用 PyYAML 实测解析通过(11 表 / `json` 列 `maxBytes` 全为整数),指纹见 §九。⚠️ **本条属「把已拍板的事做实」,未引入任何新决策**;`local_id` 加列与代码面(§2.2)当轮即已落盘,不受影响。
|
|||
|
|
|
|||
|
|
### 2.2 代码面(`store.js` · **唯一落点**)
|
|||
|
|
|
|||
|
|
| 机制 | 落点 | 口径 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| **取号口径(唯一)** | 新增 `localIdValueOf(table, row)` | 显式 `local_id` 优先,否则本地 `id` 折算 ⇒ **纯函数、无副作用**,折算与回填共用它 |
|
|||
|
|
| **写侧折算** | `normalizeRow()` **循环之后**统一折算 | 本地自增 `id` ⇒ `local_id`(`String()`);`id` 仍进 `dropped`(⛔ 不写平台);`coerced` 记 `id->local_id`。⚠️ 放在循环后 ⇒ 行里**显式**给的 `local_id` 天然优先 |
|
|||
|
|
| **读侧还原** | 新增 `restoreLocalId(table, row)`,由 `rowsOf(result, table)` 统一调用 | `local_id` ⇒ 数字 `id`;**已有 `id` 不覆盖**;非数字**原样透出**(⛔ 不丢不猜) |
|
|||
|
|
| **新行取号** | `insertRow()` 前挂 `attachLocalId()` ⇒ `allocLocalId()` | 懒初始化 = 扫该表取 `max(local_id)+1`;**进程内单调、号只增不减**(删除不回收 ⇒ ⛔ 不会把"新人"认成"老人") |
|
|||
|
|
| **列不存在 ⇒ 具名降级** | `localIdSupported()`(取一行看键,进程内缓存) | 声明已升级但库未迁移 ⇒ 返回 `null` ⇒ **不写该列、⛔ 不中断写入** |
|
|||
|
|
| **存量自愈回填** | `upsertByPk()`「已存在」分支 ⇒ `backfillLocalId()` | 🔴 **只补空、⛔ 不覆盖非空**。理由:存量行是**加列之前**灌进去的 ⇒ 重跑迁移只会 `skip`、永远补不上 `local_id` ⇒ 前端 `row.id` 会一直缺席 |
|
|||
|
|
| **拆表** | `splitHotAccount()` + `HOT_MAIN_COLUMNS` | `id` ⇒ **主表** `local_id`(⛔ 不能丢 —— 丢了前端账号详情没 id);扩展表 ⛔ 不带该列(它没声明) |
|
|||
|
|
|
|||
|
|
**为什么这样一来调用方零改动**:`normalizeRow` / `rowsOf` 是所有平台档读写的**唯一必经之路**(`insertRow` / `patchRow` / `upsertByPk` / `writeRows` / `findRows` / `locate` / `findVideos` 全走它)⇒ 折算与还原在**一处**生效,⛔ 不需要去 89 处调用点逐个改。
|
|||
|
|
|
|||
|
|
### 2.3 迁移工具 `tools/migrate-local-to-platform.mjs`
|
|||
|
|
|
|||
|
|
- `toPlatformRow()` **保留本地 `id` 原值**(由 `normalizeRow` 折算 ⇒ 存量行的 `local_id` **= 本地 id,不吃号**);
|
|||
|
|
- 新增 `backfilled` 计数(表级 + `parts[]` + `SUMMARY`),CLI 每行多打印 **「回填承载列 n」**。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三 判据表 A1–A9(命令 + 输出原文 + 退出码)
|
|||
|
|
|
|||
|
|
| # | 判据 | 命令 | 输出原文(关键行) | rc |
|
|||
|
|
|---|---|---|---|---|
|
|||
|
|
| **A1** | 存量迁移一致性 | `node --experimental-sqlite /tmp/p33/dry33.mjs`(47 真机 · **只读 dry-run**) | `DRY [account_video_source] 本地=39 计划=27 坏行=12 重复键=5 清洗列=0`<br>`DRY [account_videos] 本地=362 计划=362 坏行=0 重复键=0 清洗列=362`(清洗列集合 `["id->local_id"]`)<br>`DRY [hot_accounts] 本地=5 计划=5 坏行=0 重复键=0 清洗列=5`(同上) | 0 |
|
|||
|
|
| **A1-附** | 重复键**逐组具名**(⛔ 不许"最后一行赢") | 同上 | `DUP {"pk":"7658997937279683855","localIds":[20,40]}`<br>`DUP {"pk":"7648947073131625905","localIds":[27,28]}`<br>`DUP {"pk":"7667477088350044069","localIds":[29,44]}`<br>`DUP {"pk":"7631176055600527729","localIds":[31,42]}`<br>`DUP {"pk":"7629803455813243043","localIds":[32,46]}` | 0 |
|
|||
|
|
| **A2** | 三表读写 E2E | — | ⛔ **未达成**:实例内读数需 **47 实例重启**(待拍板㈠);本棒⛔ 不承诺窗口依赖项 | — |
|
|||
|
|
| **A3** | 结构改写面处置完毕 | ① `grep -rniE "\bJOIN\b" lib \| grep -v "\.join("` ② `grep -rn "LIMIT ? OFFSET ?" lib` ③ `grep -rnE "ROW_NUMBER\|OVER\s*\(" lib` | ① **148**(全库粗扫,含注释与**非本批**表)② **8**(`data.js` 3/`index.js` 3/`store.js` 1/`workshop.js` 1)③ **9**(`index.js` 6/`store.js` 3)<br>🔴 **涉及本批三张表的处置数 = 0**(S4/S5 未开工 ⇒ 见 §七) | 0 |
|
|||
|
|
| **A4** | 表名对齐 | `grep -rE "account_video_analysis" . \| grep -v /node_modules/ \| wc -l`;`grep -c "hot_account_ext" lib/host/mcn/store.js` | 前者 **0** ✅;后者 **6** ✅(>0) | 0 |
|
|||
|
|
| **A5** | 三表已切 `platform` 档 | — | ⏸️ **未达成**(未切档;且 `local_id` 列未就位 ⇒ 切了会让新行没 id) | — |
|
|||
|
|
| **A6** | 移动端五条 | `08 §4-补 ④` | **免跑**(本棒改动**恰 4 件**:`dsh.data.yaml`/`lib/host/mcn/store.js`/`tools/migrate-local-to-platform.mjs`/`tests/mcn-store.test.mjs`;**`lib/client.js` 零改动** ⇒ 不动 UI) | — |
|
|||
|
|
| **A7** | 卫生(逐条) | 见 §九 | 全过 | 0 |
|
|||
|
|
| **A8** | 两机一致 | 两机共享层 `package.json` version + md5 | ⏸️ **未达成**:本棒**未出包、未投放** ⇒ 代码面/运行面**都还是旧的**(47 现役 = `0.4.0`,tgz 1,962,024 B · 09-25 13:28)⇒ ⛔ **不得写"一致"** | — |
|
|||
|
|
| **A9** | 红线零新增 | `grep -rEn "CREATE TABLE\|ALTER TABLE\|DROP TABLE\|better-sqlite3\|from 'pg'\|require\('pg'\)" . \| grep -v /node_modules/ \| wc -l` | **23** ✅(与第 27/28/29/31/32 棒同值) | 0 |
|
|||
|
|
| **A-单测** | 离线判据 | `node --test tests/mcn-store.test.mjs` | `# tests 58 / # pass 58 / # fail 0`(原 51 + 本棒新增 **7**:㊺–㊿ + 51) | 0 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四 真机读数(47 · **只读** · 零写请求)
|
|||
|
|
|
|||
|
|
### 4.1 声明迁移预演(`plan33.mjs` · `readCurrentSchema` + `diffDecl`)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
### 1) 库现状(只读)
|
|||
|
|
EXISTS=true schemaVersion=1 tables=12
|
|||
|
|
--- OLD origin=yaml-file level=ok findings=[]
|
|||
|
|
dbName=dshs_pl_dsh_plugin_mcn_suite tables=11 schemaVersion=1
|
|||
|
|
PLAN items=0 forbidden=0 summary={"tables":0,"columns":0,"indexes":0} unchanged={"tables":11,"columns":96}
|
|||
|
|
--- NEW origin=yaml-file level=ok findings=[]
|
|||
|
|
DECL hot_accounts cols=14 ← 13 ⇒ 14(+local_id)
|
|||
|
|
DECL account_videos cols=17 ← 16 ⇒ 17(+local_id)
|
|||
|
|
PLAN items=2 forbidden=0 summary={"tables":0,"columns":2,"indexes":0} unchanged={"tables":11,"columns":96}
|
|||
|
|
ITEM add_column p_dsh_plugin_mcn_suite_hot_accounts.local_id | ALTER TABLE "…hot_accounts" ADD COLUMN IF NOT EXISTS "local_id" TEXT;
|
|||
|
|
ITEM add_column p_dsh_plugin_mcn_suite_account_videos.local_id | ALTER TABLE "…account_videos" ADD COLUMN IF NOT EXISTS "local_id" TEXT;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
🔴 **读数含义(1–3 为预演结论,4 为 20:2x 复核补正)**:
|
|||
|
|
1. **OLD 与库现值完全一致**(`items=0` / `unchanged 11 表 96 列`)⇒ 现役 0.4.0 声明与真机库是同步的 ⇒ 预演基线可信;
|
|||
|
|
2. **NEW 只多两条 `add_column`、`forbidden=0`** ⇒ 状态 = `drift`(可迁移),⛔ **不是 `blocked`** ⇒ **不需要删库重建**;
|
|||
|
|
3. **`maxBytes` 改动零 diff 项**(`unchanged.columns` 仍 96)⇒ 与源码级判定一致(`diff.ts:316-334` 只比 `normalizeCurrentType(c.dataType)` vs `d.type`,**完全不读 `maxBytes`**)。
|
|||
|
|
4. 🔴 **补正(20:2x 复核得出 · 与本棒初稿口径不同)**:平台的**列级 `maxBytes` 全程没有任何强制点** —— `schema.ts:500-515` 只做「必须是正整数」+「`json` 列必填」的**声明期**校验;`runtime.ts` 写入期查的是**行级** `quota.maxRowBytes = 64 KiB`(`:316-317`)+ 行数配额(`:319` `checkRowQuota`),**从不按列读 `maxBytes`**(全仓 `grep -rn 'maxBytes' src/db/plugin-data/` 除 `schema.ts` 外零命中)。
|
|||
|
|
⇒ **列上限的唯一实际把关人 = 插件侧 `store.js` 的 `COLUMN_MAX_BYTES`**。故 A 案 ② 的**功能效果**由代码面落地;声明面改的**是「口径一致 + 文档」**,不是功能开关。这也解释了为什么声明面漏改**不会**让 A 案失效(但会让两侧口径分叉,属必须补齐的自洽项)。
|
|||
|
|
|
|||
|
|
### 4.2 A 案后半段效果现算(`account_video_source` 逐行 · 39 行)
|
|||
|
|
|
|||
|
|
| 行号 | localId | content_json | analysis_json | 整行(新口径) | ①原 30 KB 列上限 | ②新 128 KB 列上限 | ③平台单行 64 KiB |
|
|||
|
|
|---|---|---|---|---|---|---|---|
|
|||
|
|
| 0 | 1 | 78,682 | 4 | **79,867** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 2 | 3 | 73,720 | 4 | **74,658** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 4 | 5 | 5,105 | 32,436 | 38,014 | ❌ 超 | 过 | 过 |
|
|||
|
|
| 5 | 7 | 78,153 | 4 | **79,209** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 9 | 13 | 18,305 | 56,742 | **75,542** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 10 | 14 | 8,666 | 57,453 | **66,717** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 11 | 15 | 7,014 | 44,509 | 52,044 | ❌ 超 | 过 | 过 |
|
|||
|
|
| 14 | 21 | 25,361 | **94,889** | **120,641** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 15 | 22 | 13,502 | 55,847 | **69,708** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 21 | 29 | 10,219 | 59,898 | **70,457** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 22 | 30 | 13,973 | 59,898 | **74,217** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 24 | 32 | 7,627 | 33,863 | 41,812 | ❌ 超 | 过 | 过 |
|
|||
|
|
| 26 | 34 | 8,701 | 34,732 | 43,760 | ❌ 超 | 过 | 过 |
|
|||
|
|
| 30 | 38 | 10,542 | 59,698 | **70,849** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 31 | 39 | 9,175 | 63,803 | **73,602** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 33 | 41 | 7,339 | 32,221 | 40,099 | ❌ 超 | 过 | 过 |
|
|||
|
|
| 36 | 44 | 10,223 | 59,898 | **70,465** | ❌ 超 | 过 | ❌ 超 |
|
|||
|
|
| 38 | 46 | 7,627 | 33,863 | 41,812 | ❌ 超 | 过 | 过 |
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
小计:总 39|过原 30 KB 上限 21|过新 128 KB 列上限 39|列上限+单行上限都过 27
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
⇒ 🔴 **A 案后半段把可上台行数从 21 提到 27(+6 行)**;剩 **12 行**的瓶颈**不在列上限**(全过 128 KB),而在**平台单行 64 KiB**:`runtime.ts:66 maxRowBytes = 64*1024`(`DEFAULT_QUOTA`)⇒ 而 `src/web/routes/plugin-data.ts:86` 构造 `PluginDataRegistry` 时**未传 `quota`** ⇒ **恒取默认、无配置口**(47 上 `/etc/dshs.env` 与 drop-in 均无 quota 项,实测 grep 零命中)。
|
|||
|
|
|
|||
|
|
### 4.3 新口径 dry-run(三表 · 零平台写请求)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
DRY_WRITE_CALLS=0(探针的假 fetch 会抛异常 ⇒ 若发请求会立刻报错)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
⇒ 三表 dry-run **一个平台请求都没发**(`apply:false` 分支在 `writeRows` 之前 return)✅
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五 「不需要删库重建」的判定(**本棒最重要的正向结论**)
|
|||
|
|
|
|||
|
|
| 判据面 | 依据 | 结论 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 源码 | `diff.ts:49` `PlanKind` 含 `add_column`;`:52` `ForbiddenKind = drop_column / alter_type / rename / notnull_no_default / drop_table` | 加列 = **合法计划项**(可自动 `ALTER TABLE … ADD COLUMN IF NOT EXISTS`);改类型才是禁令 |
|
|||
|
|
| 源码 | `diff.ts:316-334` 只比「归一化类型」,**不读 `maxBytes`** | 改上限 ⇒ **零 DDL 差异** |
|
|||
|
|
| 真机 | §4.1 预演:`items=2×add_column` · `forbidden=0` · `status=drift` | **可迁移**,⛔ 不 `blocked` |
|
|||
|
|
|
|||
|
|
⇒ **原拍板告知的代价(不可逆删库 + 数据不可用 10~20 分钟 + 重灌)在本案不发生**。本案落地收敛为:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
出包(含新声明/新代码) → 上传门户 ⇒ 检测(预演) ⇒ 执行迁移(= 2 条 ADD COLUMN,秒级)
|
|||
|
|
→ 重跑存量迁移(幂等 skip + **回填 local_id**)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**风险面(替代原代价,须一并知道)**:上传后到迁移完成之间的**分钟级窗口**内,若该用户「停用→重新启用」插件,会因声明 ≠ 库现值(`drift`)而**启用被拒**。⇒ 三步必须**紧邻执行**;回滚 = 重新上传旧包(`business-plugins/dsh-plugin-mcn-suite.tgz` 现有 0.4.0,1,962,024 B)+ `ALTER TABLE … DROP COLUMN local_id`(或直接接受多一列空列,无害)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六 R8 自律:清单 + 备份 + 影响面(**本棒只出件,未执行**)
|
|||
|
|
|
|||
|
|
### 6.1 动作清单(有序)
|
|||
|
|
|
|||
|
|
| # | 动作 | 面 | 可逆性 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| 1 | `PKG` 出包(版本号自决,⚠️ 建议**临时版本号**避免与收口的 `0.5.0` 混淆) | 本机产物 | 可逆(删产物) |
|
|||
|
|
| 2 | 上传门户候选池(`POST /api/plugins/business`,admin 临时会话,R4 用完即删) | **生产池** | 可逆(重传旧包) |
|
|||
|
|
| 3 | 检测/预演(期望 `add_column ×2`、`forbidden=0`) | 只读 | — |
|
|||
|
|
| 4 | 执行迁移(2 条 `ADD COLUMN`) | **生产库** | 可逆(列可留空/可删) |
|
|||
|
|
| 5 | 重跑存量迁移(幂等 skip + **回填 `local_id`**) | **生产库** | 可逆(幂等) |
|
|||
|
|
| 6 | 复验:`account_videos` 362 行 / `hot_accounts` 5 行**每行 `local_id` 非空**;`account_video_source` 27 行上台 | 只读 | — |
|
|||
|
|
|
|||
|
|
### 6.2 备份现状
|
|||
|
|
|
|||
|
|
| 面 | 现状 |
|
|||
|
|
|---|---|
|
|||
|
|
| 旧库 `mcn-plugin.db` | **在位**(第 27 棒备份 `cce6d1cd-…-mcn-plugin-db-20260925-141436.tar.gz`,809,732 B;sha256 `b37f8aab51a90f74…`)⇒ ⛔ 本批不重做备份、⛔ 不删旧库 |
|
|||
|
|
| 平台库 `dshs_pl_dsh_plugin_mcn_suite` | **本棒不删、不重建**(§五)⇒ 只需"加列",无数据损失面 |
|
|||
|
|
| 现役插件包 | `/var/lib/dshs/business-plugins/dsh-plugin-mcn-suite.tgz`(0.4.0 · 1,962,024 B · 09-25 13:28)+ 共享层 `bundled-plugins/dsh-plugin-mcn-suite/` ⇒ 回滚素材 |
|
|||
|
|
|
|||
|
|
### 6.3 影响面
|
|||
|
|
|
|||
|
|
| 谁受影响 | 多久 | 代价 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 47 上该插件用户(`cce6d1cd-…`) | **分钟级窗口**(上传 → 迁移完成之间) | 该窗口内「停用→重新启用」会被拒(`drift`);**已在运行的实例不受影响**(加载的是共享层 bundle,非新包);计划任务/其它功能不受影响 |
|
|||
|
|
| 其它用户 / 其它插件 | 0 | 加列只作用于 `dshs_pl_dsh_plugin_mcn_suite` 一个库 |
|
|||
|
|
| 106 | 0 | 本步不涉及(106 属 S9–S11 收口) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七 未闭合项(⛔ 逐条不宣称通过)
|
|||
|
|
|
|||
|
|
| # | 项 | 现象 | 归属 | 下一步 |
|
|||
|
|
|---|---|---|---|---|
|
|||
|
|
| ㈠ | **S3 剩 1 处** | `index.js` 那条**三表混写 SQL**(`account_videos` + `account_video_source` + `SUM() OVER`)未改 | 插件侧 | 先改它 → 再 S4/S5(04 单 §五 已定序) |
|
|||
|
|
| ㈡ | **S4 未开工** | `account_videos` 全量切换(游标 2 处 / 窗口函数 2 处 / `IN` 逐主键 4 处 / `account_id` 类型对齐)**一处未改** | 插件侧 | 落点 = 现算 **89 处**(`data.js` 9/`imports.js` 36/`index.js` 30/`video-tools.js` 1/`workshop.js` 13)⇒ 读数 `tmp/p33/batch3-live-33.txt` |
|
|||
|
|
| ㈢ | **S5 未开工** | `hot_accounts` + `hot_account_ext` 拆表切换(4 写点 + 11 JOIN)**一处未改**;首件事仍是核实 5 处写点能否都拿到 `account_name` | 插件侧 | 同上 |
|
|||
|
|
| ㈣ | **`local_id` 列未就位** | 真机库仍是 13/16 列(无承载列)⇒ 即使接线完成**也不能切档**(切了新行没 id ⇒ 前端 6 入口失效) | 部署面 | §六 的 1–5 步(待拍板) |
|
|||
|
|
| ㈤ | **`account_video_source` 12 行仍上不去** | 瓶颈 = 平台单行 64 KiB(`DEFAULT_QUOTA` 未覆盖、**无配置口**);⚠️ 本棒⛔ 不改 `src/**` | **平台侧** | 待拍板(§八-②) |
|
|||
|
|
| ㈥ | **A2 / A5 / A8 未达成** | A2 需 47 实例重启窗口|A5 未切档|A8 未出包未投放 | 部署面 | 待拍板(§八-①) |
|
|||
|
|
| ㈦ | **D16 映射新鲜度缺口**(第 31/32 棒遗留) | `hotAccountMetaMap` / `videoLocalIdMap` 只能来自本地 ⇒ 切档后新建账号会缺席 | 插件侧 | 🔴 **本棒部分缓解**:`local_id` 就位后,映射可直接**从平台行取**(`restoreLocalId` 已让 `row.id` 在平台档可用)⇒ 下一棒可据此把映射改为"优先平台" |
|
|||
|
|
| ㈧ | 第 30 棒读数文件被覆盖(**如实记账**) | 本棒用 `tmp/p30/scan-batch3-live.py` 现算第三批落点时,该脚本**写死落盘到** `tmp/p30/batch3-live.txt` ⇒ **第 30 棒那份被现算结果覆盖** | 本机 | 已把现算副本留 `tmp/p33/batch3-live-33.txt`;⚠️ 脚本落盘路径不属本棒域锁(`tmp/` 不入库)⇒ ⛔ 未改脚本 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八 待拍板(**两项 · 一轮一问 · 逐条自包含**)
|
|||
|
|
|
|||
|
|
> ⛔ 本棒**未登记下一棒**(用户 2026-09-17 明令:要用户拍板的等拍了再新建接续会话)。
|
|||
|
|
|
|||
|
|
**① 要不要现在动生产,把 A 案的两项真正落到 47(出包 → 上池 → 加列迁移 → 回填)?**
|
|||
|
|
为什么需要你定:这一步会**改动生产环境**——具体是:把插件新版本传到门户(替换候选池里的包),随后给该插件在数据库里**加两列**。影响面:**该插件的使用者**(47 上那一个用户)在"上传完成到加列完成"的**几分钟窗口**内,如果停用再启用插件会被系统拒绝;已经在运行的实例不受影响。**数据不会丢**(不是删库,只是加列),旧数据备份已经在位。
|
|||
|
|
- **A 案**:现在就做(优点:本棒付出的代码/声明/取证立刻变成可用状态,下一棒可直接接线+切档;缺点:需要几分钟的生产窗口,且窗口内该用户若正好停用再启用会被拒)。
|
|||
|
|
- **B 案**:等下一棒把 S3/S4/S5 接线也做完,再**一次性**动生产(优点:只动一次生产、窗口只开一次;缺点:接线期间写好的平台档代码暂时无法在真机验证,"有没有接对"要靠离线单测兜)。
|
|||
|
|
- **我的倾向**:**B 案**(可推翻)。理由:切档本来就必须等接线全部完成(一处未改完就切 = 写到没人读的地方),所以"现在就迁移"并不能提前拿到任何真机收益,却要单独开一次生产窗口。
|
|||
|
|
|
|||
|
|
**② `account_video_source` 那 12 行超 64 KiB 的记录,怎么处置?**
|
|||
|
|
为什么需要你定:这 12 行是**用户真实数据**(AI 解析正文较大)。现在它们**上传不到平台**(平台单行上限 64 KiB 是硬编码默认值,且平台侧构造时没有留出可配置的接口 ⇒ 要提高必须**改平台程序**)。不处置的话,这 12 行永远是"平台档下读不到"的状态——切档后这 12 个视频的解析结果会**看不见**。
|
|||
|
|
- **A 案**:接受现状(优点:不动平台程序、不影响其它插件;缺点:这 12 个视频的解析结果切档后读不到,属功能净缺失)。
|
|||
|
|
- **B 案**:改平台程序,把单行上限提上去(优点:12 行也能上台、彻底;缺点:**改动的是平台公共组件**,影响所有插件,需要单独评估与测试,且与本棒"不改平台"的约束冲突 ⇒ 必须另立单子)。
|
|||
|
|
- **C 案**:插件侧把这 12 行的大正文**拆成多行**存放(优点:不动平台、不丢数据;缺点:要改插件表结构 + 读取逻辑,属新增改造面)。
|
|||
|
|
- **我的倾向**:**A 案**(可推翻)。理由:先让 27/39 行可用,把 12 行如实记为"未上台",等平台侧配额这件事单独拍板;⛔ 不拿"顺手改平台"当默认。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 九 卫生声明(A7 逐条)
|
|||
|
|
|
|||
|
|
⛔ 未改 `D:/github/dsh_shenxian/src/**`(只读 grep + 只读 import)|⛔ 未改 `im.ts`|⛔ 未改 `D:/dshworkspace` 以外任何别的工作区文件|⛔ 未重启任何服务|⛔ 未投放任何版本|⛔ 未出包|⛔ 未 commit·push|⛔ 未删任何库(旧库 `mcn-plugin.db` 在位)|⛔ 未删 `db.js`|⛔ 未改跨库脚本(`handoff-guard.sh` / `preflight-lock.sh` 一字未动)|✅ 真机探针 `/tmp/p33/` **已整体删除**(实测 `ls` 报 `No such file or directory`)
|
|||
|
|
**改动面恰 4 件**(全纯 LF · CR=0,逐字节验过);⚠️ `dsh.data.yaml` 是 **20:2x 补正之后**的版本(补正内容与理由见 §2.1 的「补正」段),其余三件与 20:1x 收口时逐字节一致:
|
|||
|
|
|
|||
|
|
| 文件 | sha256(前 16) | 字节 | CR/LF |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `dsh.data.yaml` | `d6627f1c647a8c27` | 8,351 | 0 / 183 |
|
|||
|
|
| `lib/host/mcn/store.js` | `ed5f25741476f0d9` | 105,150 | 0 / 2,239 |
|
|||
|
|
| `tools/migrate-local-to-platform.mjs` | `060bbf855f624fdd` | 12,886 | 0 / 254 |
|
|||
|
|
| `tests/mcn-store.test.mjs` | `befaef4aaae43e43` | 53,587 | 0 / 1,040 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 十 域锁
|
|||
|
|
|
|||
|
|
`--claim-exec "p33-exec-plugin-dataline" --domains <11 键>` ⇒ `rc=0`;复读 `.locks/p33-exec-plugin-dataline/{OWNER,DOMAINS}` **断言归属**(开始 `09-25 19:57`;会话 `ee21bf7d-0f0e-4c42-9abc-29e8e15bb774`)。
|
|||
|
|
**释放**(收口时):`ME="p33-exec-plugin-dataline" bash $DOC/07-scripts/handoff-guard.sh --release-exec` —— 🔴 **必须带 `ME=` 前缀**(不带 ⇒ `ME` 空 ⇒ 会删掉 `.locks/*` 下**所有人**的域锁,第 32 棒 09-25 19:5x 实测事故)。
|
|||
|
|
**第 1 步门禁如实记账**:`preflight-lock.sh` 对本棒 **11 个目标路径全判【D】未归类** ⇒ 按字面 `rc=1`;依第 26–32 棒**既有惯例**开工并报告,⛔ **未改那个跨库脚本**。
|
|||
|
|
|
|||
|
|
🔴 **补正周期(20:2x)再抢一次域锁**:`--claim-exec "p33-exec-plugin-dataline" --domains "dsh-plugin-mcn-suite/dsh.data.yaml" "aliyun-dsh-server/交付物"` ⇒ `rc=0`;复读 `DOMAINS`(2 键)+ `OWNER` 首行 = 本人,**断言归属通过**(`20:22`)。
|
|||
|
|
🔴 **释放读数(如实记账 · ⛔ 未作任何人工处置)**:跑 `ME="p33-exec-plugin-dataline" … --release-exec` 输出 `· 无可释放的锁(或非本人持有)`(`rc=0`);实测 `.locks/` **仅剩 `.migrations`**(== 无任何域锁目录)、`.exec-lock` 不存在 ⇒ **本棒域锁均已不在**。⚠️ 只读源码 `handoff-guard.sh:249-271`:`$_who` 非空时删除条件是 `[ "$o" = "$_who" ]`(`OWNER` 首行相等)⇒ **带 `ME=` 的释放按定义不会碰别人的锁**。⛔ 全程**未删锁、未接管**(R9)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 附 本棒探针与读数(`$WS/tmp/p33/`)
|
|||
|
|
|
|||
|
|
| 文件 | 用途 |
|
|||
|
|
|---|---|
|
|||
|
|
| `plan33.mjs` | 声明迁移预演(47 · 只读)⇒ §4.1 读数 |
|
|||
|
|
| `dry33.mjs` | 逐行字节现算 + 新口径 dry-run(47 · 只读)⇒ §4.2 / §4.3 |
|
|||
|
|
| `new/{dsh.data.yaml,package.json}` | 新声明夹具(`parseDeclFromDir` 要求 `package.json` 指路 ⇒ ⛔ 不自动探测 yaml) |
|
|||
|
|
| `batch3-live-33.txt` | 第三批落点**现算** 89 处(替换第 30 棒读数,见 §七-㈧) |
|
|||
|
|
| `scan.txt` | 现算汇总(`data.js` 9/`imports.js` 36/`index.js` 30/`video-tools.js` 1/`workshop.js` 13) |
|
|||
|
|
| `out/` | 预留(本棒未产生额外落盘) |
|