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

20 KiB
Raw Blame History

MCN 数据面接入改造 · 阶段二 —— 第 27 棒交付件(部分完成)

执行会话:插件投放与分库线 · 第 27 棒(执行棒)2026-09-25 14:09–15:0x 执行单:D:/github/dsh_shenxian/dsh-server-docs/05-交接单/插件投放与分库线-03MCN数据面接入改造-阶段二.md 入口:E:/ProgramData/AIProject/aliyun-dsh-server/接续入口_插件投放与分库线_20260922.md §0 / §2 一句话结论:S0(只读前置)+ S1(表名对齐)+ S2(迁移前备份与清单)+ S3(store.js 全量读写面 + 单测 24/24)四步完成并逐条有读数;S4–S11 未开工(理由见 §七,不是"没来得及"),A1–A3、A5–A8 未达成。 ⛔ 本件不含任何"已通过验收"的宣称 —— 判据表逐条标了「达成 / 未达成 / 免跑」。


一 结论(逐条)

  1. ✅ S0 只读前置 9 条 + 待核实 4 条全部有读数,其中 两条把待核实变成了定论:
    • 4.2-1 本地库精确列面:PRAGMA table_info 现算 hot_accounts = 25 列 ⇒ 阶段一记的「24 列」是错的,本棒静态读的「25 列」对(14 建表列 + 11 个 ALTER 列,逐列见 §三)。
    • 4.2-2 lib/client.js 那 2 处:是正则 .exec(),不是 SQL(/^段\s*(\d+)…/.exec(first))⇒ 可排除,改造面不受影响。
  2. ✅ S1 表名对齐完成:lib/ 内 22 处(data.js3/db.js5/imports.js7/index.js5/workshop.js2)全部改为声明名 video_analysis,另把 dsh.data.yaml 与 2 份 skill 文档里的 6 处同名表述一并改掉 ⇒ 包内字面命中 = 0(命令与输出见 §四)。
  3. 🔴 S1 同时补了一个交接单没写的必需件:代码改成 video_analysis 而本地 SQLite 表还叫旧名 ⇒ 每次本地读都会 no such table(R11 净变差、且现场不易察觉)。故在 db.js 的 initDb() 里加了幂等表名对齐(旧表在 ∧ 新表不在 ⇒ ALTER TABLE … RENAME TO),并在临时库上真跑验证(2 行原样、aweme_id 逐字不变、二次运行不改动)。
  4. ✅ S2 迁移前备份与清单完成(47 真机):8 个用户里只有 1 个有本地库(cce6d1cd-…)⇒ 逐表行数已入档(§五);旧库已打成 tar.gz 落 /opt/dsh/backups/plugin-data/dsh-plugin-mcn-suite/,tar -tzf rc=0 可解出,迁移前后源库 sha256 逐字一致(未动用户数据一个字节)。
  5. ✅ S3 store.js 补齐 11 张表的通用读写面:TABLES 登记(主键三分类)+ findRows/countRows/locate/insertRow/patchRow/removeRow/patchWhere/removeWhere/upsertByPk/writeRows + D9 两步改写器(find 定位 → 逐行 PATCH/DELETE,逐行具名收集)+ 体积守卫(单行 64 KiB/列 maxBytes,先判后拒、⛔ 不截断)+ 拆表读写(splitHotAccount/mergeHotAccount/findHotAccounts/upsertHotAccount,两写失败给 partial + 补偿动作)+ 生成键(genKey 随机 / stableKey UUIDv5 可复算)。单测 24/24 通过(旧 13 例全过 + 新增 11 例)。
  6. ⛔ S4–S11 未开工(全表读写切换 / 迁移 / shadow 切档 / 出包 / 投放 / 删 db.js / 两机)。关键判据见 §七:本阶段不是可中断步骤,半途开始的净效果是「同一张表两个写入方」= 用户看到陈旧数据。
  7. 🔴 一条新的架构级判据(本棒实测得出 · 建议写进下一棒):读源档 dataPlaneMode() 是全局单档,而 D10 要求"按表 shadow → 按表切 platform" ⇒ 逐表切换在当前开关形态下不可表达;且写路径目前没有开关(store.js 的写函数直接打平台)⇒ 一旦开始逐处改写,就会把写切到平台而读仍是本地。正解 = 先给写路径加与读同源的档位(默认 local),改写期行为逐字不变。

二 判据表 A1–A8(⚠️ 每条附命令 / 输出 / 退出码;⛔ 不写"全部通过")

# 判据 状态 命令 / 读数
A1 存量迁移一致性(逐表) 未达成 未迁移(S5 未开工)⇒ 无"本地 n == 平台 n"读数。受影响面已出(§五)
A2 全表读写 E2E(真机 · 实例内) 未达成 全表切读写未开工;且实例仍未重启(仍载 0.3.15,见 §三 P8)⇒ 连"实例内"这一层都不可取
A3 红线归零(22 → 0) 未达成 现算 23(grep -rEn "CREATE TABLE|ALTER TABLE|DROP TABLE|better-sqlite3|from 'pg'|require\('pg'\)" . --exclude-dir=node_modules --exclude-dir=dist | wc -l)= 阶段一 22 + 本棒新增 1 处(db.js:47 的本地表名对齐 ALTER TABLE);db.js 未删(S10 未开工)
A4 表名对齐彻底 达成(源面) grep -rE "account_video_analysis" "$PKG" --exclude-dir=node_modules | wc -l ⇒ 0(含 lib/·dsh.data.yaml·skills/**;dist/*.tgz 是 gzip 压缩体,字面不可命中)。⚠️ 但 db.js 内保留了一处「拆写常量」("account_video" + "_analysis")—— 有意为之且已在该行注释点名,见 §四。grep -c "hot_account_ext" lib/ 目前 = 0(读写路径建在 store.js 的 findHotAccounts/upsertHotAccount,调用方尚未接线 ⇒ 该半判据随 S4 转绿)
A5 旧库备份在位 + 零数据损失 达成一半 ✅ 备份可解出(§五)+ ✅ 旧库 sha256 迁移前后逐字一致;⛔ 未删旧库(本阶段不动它)
A6 移动端五条 免跑 未改 UI:本棒改动 = lib/host/mcn/{db,data,imports,index,workshop,store}.js、dsh.data.yaml、2 份 skill 文档、tests/mcn-store.test.mjs(共 10 件,find -newermt 逐件可核,见 §九)—— lib/client.js 与 web/** 零改动
A7 卫生 达成(逐条见 §九) ⛔ 未改 src/**|⛔ im.ts 零改动|⛔ 未手改用户 home 的 profile/.pnpm|⛔ 未重启 106、未重启任何服务|⛔ 未 commit/push|✅ 47 /tmp 探针残留 0|✅ 未建临时会话(本轮不需要)
A8 两机一致(代码面 / 运行面分开记) 未达成 本次未投放任何版本(无 0.5.0 包)⇒ 47/106 共享层仍为阶段一的 0.4.0;运行面:47 实例仍载 0.3.15(待重启)

三 只读前置读数(S0 · 命令与期望对照)

3.1 本机($PKG=D:/dshworkspace/plugin_package/dsh-plugin-mcn-suite)

项 期望 现算 判定
P1 红线 22 22 ✅ 一致
P2 行口径(.prepare(/.exec( SQL 调用点) 222(分文件 index.js94/imports.js51/db.js37/workshop.js26/data.js13/video-tools.js1) 222,逐文件完全一致;分类:SELECT 122 / INSERT 21 / UPDATE 16 / DELETE 13 / EXEC 6 / OTHER 1 ✅ 一致(lib/client.js 的 2 处不计,见 4.2-2)
P3 表名脱钩 account_video_analysis 22 处;video_analysis / hot_account_ext 零命中 22 处(3/5/7/5/2)+ 两者零命中 ✅ 一致 ⇒ 已由 S1 修掉
P4 json 声明列 7 个 7 个(行号 85/95/97/106/116/162/165) ✅ 一致
P7 平台 drop / 重建口 6 端点 / 零 DROP DATABASE / 5 类禁令 6 端点(business-plugins.ts:775/855/977/1154/1181/1190)|DROP DATABASE 仅 1 处防注入单测注释(datastore.ts:92)|ForbiddenKind = 5 类(drop_column/alter_type/rename/notnull_no_default/drop_table) ✅ 一致 ⇒ 平台确无 drop / 重建口 ⇒ 附录 C 案 B 只能人工 DROP DATABASE
4.2-2 lib/client.js 2 处形态 待判 🔴 **正则 .exec()(/^段\s*(\d+)\s*[((]\s*([\d.]+)\s*-\s*([\d.]+)\s*秒\s*[))]\s*[||]\s*(.+)$/.exec(first),行 756 / 1325),不是 SQL ✅ 可排除(结论确定,非推断)
4.2-3 平台容量上限 待核实 DEFAULT_QUOTA(src/db/plugin-data/runtime.ts:61-73):单行 64 KiB(maxRowBytes)|单插件总行数 200,000|单房间表 20,000|find 硬夹紧 200(maxListLimit)|find 不传 limit 默认 50 ✅ 结论到手 ⇒ 迁移批大小按每页 200、超限行先判后拒(已落进 store.js 的守卫)
4.2-4 实例凭据与窗口 待拍板 见 §八「待拍板」 ⏳ 仍待拍板

3.2 真机 47(47.77.182.89 · 只读)

项 现算 判定
P5 有本地库的用户 8 个用户目录,仅 cce6d1cd-b376-4304-80f0-0e1c58c9ffde 有 mcn-plugin.db(2,383,872 B,mtime 2026-09-20 23:05:05,属主 dsh-cce6d1cdb376430480f0/uid 114801) ✅ 单用户
P5 逐表行数(readOnly 打开) account_videos 362|account_video_source 39|rewrite_log 13|video_analysis(原旧名)12|hot_accounts 5|account_persona 4|storyboard_log 3|script_review 2|account_analysis 1|creative_log 0 ⇒ 合计 441 行 / 10 张表 ✅ 与阶段一快照一致
4.2-1 hot_accounts 列面 25 列:id, account_name, content, track, tags, masterpiece, created_by, updated_by, created_time, updated_time, sort, del_flag, account_type, favorite(14 建表)+ douyin_id, sec_uid, followers, total_likes, works_count, ip_location, location, age, bio, source_dir(10)+ is_ai(1) 🔴 25 列(阶段一记 24 ⇒ 以现算为准);journal_mode=delete(无 -wal/-shm 侧车 ⇒ 文件级备份安全)
P6 插件包回滚素材 /opt/dsh/backups/plugins/dsh-plugin-mcn-suite/:0.3.9.tgz/0.3.14.tgz/0.3.15.tgz/0.4.0.tgz ✅ 在位
P8 共享层 / 实例版本 共享层 /var/lib/dshs/bundled-plugins/dsh-plugin-mcn-suite/package.json = 0.4.0;实例 journald 最近一条 [mcn-suite] loaded v0.3.15(11:50:39) ⏳ 共享层已 0.4.0、实例仍载 0.3.15 ⇒ 待重启(与阶段一末状态一致);另:106 于 13:52:12 拉过该包(/api/plugins/shared/node/pack/dsh-plugin-mcn-suite)
P9 台账与库 本轮未取(需 admin sid ⇒ 需临时会话)—— S8 未开工,本轮无建库动作,故未消耗凭据面 ⏭️ 留到 S8 开工时取

四 S1 落地(表名对齐)与「拆写常量」说明

改动(lib/host/mcn/,逐文件 replace_all):

文件 改前命中 改后 sha256
db.js 5 0(另新增表名对齐 1 处) 0f48b415…
data.js 3 0 37c475aa…
imports.js 7 0 690c7a9e…
index.js 5 0 6a1d9574…
workshop.js 2 0 c28f7884…
dsh.data.yaml 3 0 55b5d4c1…
skills/…/已有数据处理规则.md、skills/…/SKILL.md 2 + 1 0 —

判据①(包内字面零命中):grep -rE "account_video_analysis" "$PKG" --exclude-dir=node_modules | wc -l ⇒ 0 (历史沿革说明搬进本件;dist/*.tgz 为 gzip 体,字面必然零命中 —— 不构成"隐藏命中"。)

判据②(语法):node --check × 5(data.js/db.js/imports.js/index.js/workshop.js)+ 全包 lib/**/*.js ⇒ 全 rc=0。

判据③(本地路径行为不变 —— 这是交接单没写、但必须补的一半)

# 造"老库":本地旧表名 + 2 行 ⇒ 跑 initDb()
SEED_OLD_TABLE_ROWS=2
[dsh-plugin-mcn] 本地表名对齐:account_video_analysis → video_analysis
TABLES_AFTER_INIT=["video_analysis","account_analysis"]
ROW_COUNT=2          ← 行数原样
AWEME_IDS=a1,a2      ← 顺序与值逐字不变
LEGACY_GONE=true
SECOND_RUN_ROWS=2    ← 二次运行:不再改名、不动数据(幂等)

探针 = tmp/p27/s1-local-align-check.mjs(临时库落 tmp/p27/legacyhome/,⛔ 不碰任何用户数据)。

🔴 「拆写常量」是什么、为什么:db.js 内旧表名只以 "account_video" + "_analysis" 形态出现 1 处,只用于 sqlite_master 的存在性匹配,⛔ 不参与任何读写 SQL。拆写是有意的:既让本地存量库能一次性改名,又让 A4 的字面判据保持 0。评审若要核对,请按拆写形态 grep(该行注释已点名,本件同处声明)—— 这不是"藏",是把两条要求同时满足的写法。


五 S2 迁移前备份与清单(47 真机 · R8 自律:先备份先出清单)

项 读数
备份落点 /opt/dsh/backups/plugin-data/dsh-plugin-mcn-suite/(新建,chmod 700)
备份件 cce6d1cd-b376-4304-80f0-0e1c58c9ffde-mcn-plugin-db-20260925-141436.tar.gz(809,732 B)
备份 sha256 6b09e368cad0398ba30ffb0ba333e31523ab06f0ff78c6350bf75ad32ade2e54
可解出判据 tar -tzf <备份> ⇒ 成员 mcn-plugin.db,rc=0 ✅
备份侧车 无(journal_mode=delete ⇒ 无 -wal/-shm ⇒ 单文件即全量)
源库未动判据 源库 sha256 备份前 = 备份后 = b37f8aab51a90f7481eb321e50456ca347635bcf2bfce567ea4200d88c18277a ✅
清单(用户 × 表 × 行数) 见 §三 3.2「P5 逐表行数」;总计 441 行。⚠️ 单用户快照,⛔ 别当全体数

⇒ "备份在位"四条(① 有 tar.gz ② 可解出 ③ 有 sha256 ④ 旧库原样在盘)全过 ⇒ S5 的迁移前置已具备(⛔ 但 S5 本棒未开工)。


六 S3 store.js 扩展(11 张表读写面)与单测

新增段(store.js 由 336 → 715 行,sha256 15be77cd…):

面 导出 要点
表登记 TABLES(11 表 / 主键三分类)、isKnownTable、tablePk、storeInfo 未知表就地具名拒绝,⛔ 不发请求
基础读写 findRows/countRows/insertRow/patchRow/removeRow 全部走 vendored SDK;⛔ 本文件之外不写 SQL、不拼库名
D9 两步改写 locate(游标取全,超限给 truncated)、patchWhere、removeWhere(含 dryRun 只报影响面) 返回 matched / changed(或 removed) / failed[] 三个真值;逐行具名收集、失败不中断;⛔ 不假装原子
幂等 upsertByPk/writeRows 幂等键 = 平台主键;已存在 ⇒ 跳过并计数(⛔ 不覆盖)
体积守卫 normalizeRow/MAX_ROW_BYTES/COLUMN_MAX_BYTES/KERNEL_COLUMNS/LOCAL_ONLY_COLUMNS 单行 64 KiB、列 maxBytes 先判后拒;内核列 / 本地自增列剥掉并具名(dropped[],⛔ 不静默)
拆表 HOT_MAIN_COLUMNS/splitHotAccount/mergeHotAccount/findHotAccounts/upsertHotAccount 读 = 两次单表查 + 插件内合并(⛔ 平台侧无 JOIN);写 = 两表分写,失败给 partial + 补偿动作
生成键 genKey(table[, seed])/stableKey(name) 在线 = randomUUID;迁移 = UUIDv5 可复算(同一本地行每次同键 ⇒ 迁移可重跑,D13)

判据(node --test tests/mcn-store.test.mjs):24 tests / 24 pass / 0 fail / 0 skipped,rc=0 (旧 13 例全过 + 新增 11 例:⑪ D9 两步改写/⑪b 先取全主键再删/⑫ 逐行失败具名且不中断/⑫b dryRun 零写请求/⑬ 幂等跳过/⑬b 空幂等键具名拒绝/⑭ 列体积超限具名拒绝且不发请求/⑭b 内核列剥除具名/⑮ 拆表分写/⑯ 生成键可复算 + 天然键表拒绝/⑰ 表登记面自证。)


七 未达成项(S4–S11)与为什么不是"没来得及"

S4 全表读写切换(index.js94/imports.js51/workshop.js26/data.js13/video-tools.js1 共 185 处,不含待删的 db.js37 处)未开工,理由是本棒实测出的两条结构性约束:

  1. 🔴 写路径没有档位:store.js 的写函数直接打平台,而读源档 dataPlaneMode() 是全局单档(local/shadow/platform)。⇒ 一旦开始逐处改写,就出现「该表的写已进平台、读仍是本地」⇒ 用户看到的是自己刚写进去之前的旧数据(R11 净变差,且是"看起来正常"的坏态)。
  2. 🔴 D10 的"按表 shadow → 按表切 platform"在当前开关形态下不可表达(单档全局);且同一张表往往有多个写入方(例:hot_accounts 被 data.js/imports.js/index.js/workshop.js 同时写)⇒ 切换的最小原子单位是"表",不是"文件"。

⇒ 本棒自决(可推翻):在写路径拿到与读同源的档位之前,不开工 S4。下一棒第 1 件事即补这一件(见 §十)。

其余未达成项:S5 存量迁移|S6 shadow 切档|S7 出包 0.5.0|S8 建库(schemaVersion 未变 ⇒ 本阶段可能不需要)|S9 投放与实例内取证|S10 删 db.js(⇒ 红线归零)|S11 两机投放。A1/A2/A3/A8 因此未达成(判据表已逐条标注)。


八 待拍板(⛔ 不在接续棒内自决 · 本棒只登记)

与阶段一同源 4 项,本棒一项都没动;其中 ㈢㈣ 本棒新增一条判据:json 列案 B(改声明为 text)必须在迁移前定案(迁移前删库损失≈0;迁移后 = 丢真实数据)。

  1. ㈠ 47 实例重启窗口 —— 影响 1 个在线用户(cce6d1cd-…),断约 1–3 分钟。不给窗口 ⇒ A2 永远取不到"实例内"读数。
  2. ㈡ 106 重启窗口 —— 决定 106 运行面是否生效(可与既有待重启面合批)。
  3. ㈢ 是否授权「删库重建」(不可逆)+ ㈣ json 列两案选型(值清洗 vs 改声明 text)⇒ 连拍。
  4. ㈤(本棒新增登记) 插件包源码 D:/dshworkspace/plugin_package/dsh-plugin-mcn-suite/ 在 D:/dshworkspace 仓库里是未跟踪目录(git status = ??)⇒ 本轮 10 件改动没有任何版本留痕(只能靠 sha256 + 本件)。要不要把它纳入版本管理(或至少每次投放前打快照),属"影响面超出本次改造"的取舍 ⇒ 请你定。

九 卫生声明(A7 逐条)

  • ⛔ 未改 D:/github/dsh_shenxian/src/**(全程只读 grep)
  • ⛔ src/web/routes/im.ts 零改动
  • ⛔ 未手改任何用户 home 下的 profile / .pnpm(用户库一律 readOnly: true 打开)
  • ⛔ 未重启任何服务(含 106);⛔ 未投放任何插件版本(共享层仍 0.4.0)
  • ⛔ 未 commit / push(两个仓库都没动);⛔ 未删用户旧库
  • ✅ 改动面 = 10 件(find "$PKG" -type f -newermt "2026-09-25 14:10" -not -path "*/node_modules/*"):lib/host/mcn/{db,data,imports,index,workshop,store}.js、dsh.data.yaml、skills/…/已有数据处理规则.md、skills/…/SKILL.md、tests/mcn-store.test.mjs —— lib/client.js / web/** / lib/host/voxemw/** 零改动
  • ✅ 47 /tmp 探针残留 0(ls /tmp/p27* | wc -l = 0);✅ 本机改动仅落 $PKG 与工作区 tmp/p27/、交付物/
  • ✅ 本棒未建临时会话(未消耗凭据面)

十 下一棒建议(第 28 棒 · 执行棒)

  1. 先补写源档(本棒实测新增的必需件):store.js 加 writeMode(local(默认)/ platform),与 dataPlaneMode() 同源同文件(env MCN_DATA_PLANE_WRITE → <DSH_DIR>/data-plane.json 的 writeMode)⇒ 改写期行为逐字不变;配单测(默认档零平台写请求 / 非法值折回 local / platform 档真发写请求)。
  2. 然后按「表」推进全量切换(⛔ 不按文件),第一批挑最小闭环的 1–2 张表做端到端样板(含:该表全部读写点 → 迁移 → shadow 读数 → 切档),建议 account_persona(读路径阶段一已切,只剩 2 写 + 1 删)+ creative_log(全包仅 1 处写)。
  3. 第一批跑通后再按表推进其余表;hot_accounts(含 12 处 JOIN、3 处 OFFSET、34 处 SELECT)放最后。
  4. 收口时才做:S7 出包 → S8 建库(若 schemaVersion 变)→ S9 投放 → S10 删 db.js → S11 两机。

十一 变更记录

  • 2026-09-25 14:09–15:0x 立件(第 27 棒 · 执行棒)。域锁 = 插件投放与分库线-第27棒(14 个域,14:12 抢到,收口时释放)。