diff --git a/.workbuddy/memory/2026-09-25.md b/.workbuddy/memory/2026-09-25.md index 9bbb595..906dbf7 100644 --- a/.workbuddy/memory/2026-09-25.md +++ b/.workbuddy/memory/2026-09-25.md @@ -688,3 +688,248 @@ automation `ed2b5e61-…`(已置 PAUSED)|入口 §0/§2 已推进到第 19 **同时更新**:入口 §0(新增 22:44 拍板行)+ §2(第 36 棒范围改写为 5 步 · 原「条件步」转无条件 · 硬约束③ 改为"拍板②已定 ⇒ 长期不 commit/push")|交付件 §〇/§五/§六/§七 全部标注「已拍板」|第 36 棒 automation prompt 注入两条拍板(**⛔ 不再重问**)。 **锁**:22:44 二次抢锁(3 域 = 入口/交付物/`.workbuddy`,`rc=0`,OWNER 归属已断言)⇒ 改完带会话名释放。⚠️ 本次 `--claim-exec` 的 OWNER 里 **`会话:9f9ae37e-447a-4312-a85d-b0e82027bade` 已正确写入**(第 35 棒那次是 `未取到`)⇒ 说明该缺口**非必然复现**,仍建议 claim 侧补 `CODEBUDDY_SESSION_ID` 兜底。 + +## 23:0x 工作区仓库同步(用户「任务处理完毕后 同步项目到仓库」) + +**用户指令(原话)**:「任务处理完毕后 同步项目到仓库」⇒ 视为**对 commit / push 的明确授权**。 + +**做法与结果**: +- 抢域锁(4 域:本线入口/`交付物`/`.workbuddy`/`05-交接单`)+ **发布锁**(`--claim-publish`),均 `rc=0`。 +- 推送前对账:`07-scripts/docs-sync-check.sh` ⇒ **rc=1**,但差异**全在文档库侧**(21 内容不一致 + 仅本地 4),**与本仓无关**(属镜像滞后 / 他线在途)。 +- **提交 `318430c`**:`chore(工作区): 插件投放与分库线收口入库(第 35 棒 + 22:44 拍板落盘)` —— **27 files changed, 5649 insertions(+), 72 deletions(-)**。 +- **推送 `12e9497..318430c master -> master`** ⇒ 复核 `origin/master...HEAD = 0 0`(本地与远端完全一致),远端 HEAD = 本地 HEAD = `318430c`。 + +**🔴 本次确立的「同步口径」(后棒照此办,⛔ 别全量 add)**: +- 工作区是**多线共用**:本次 `git status` 共 **77 处**改动,**只有 27 处属本线**。 +- **提交 = 只 add「本线产物 + 记忆类」**,⛔ 绝不 `git add -A`。本次范围 = 1 件接续入口 + 22 件 `交付物/` + 4 件记忆(`2026-09-24.md`/`2026-09-25.md`/`MEMORY.md`/本棒 automation 记录)。 +- **⛔ 不代提交(属他线 lane;按 R7-边界「别人的 ⇒ 只报告、不动手」)**:机制层 `CODEBUDDY.md`/`state.py`/`.codebuddy/rules/server-ops.md`(规则载体线 ACTIVE)· `接续入口_IM线_20260922.md`(**IM 线第 13 棒 22:49 起正在跑**)· `接续入口_StoryForge验收线_20260925.md` · `接续包_IM与插件接入/域锁释放缺陷修复/规则载体压缩` · `docs/规则与载体/`(2 件)· `归档/guard与副本清退-20260924/`(11 件)· `归档/规则A-备份-20260924/`(8 件)· 其余 **29 个** automation 记忆目录 · `.workbuddy/tools/credit_curve.py` · `.workbuddy/待落地/` 的 4 个删除。 +- 推送后残留 **68 处**(同上,全属他线)。 + +**⚠️ 顺手发现两个缺陷(未动 · 只报告)**: +1. **工作区根有 4 个垃圾文件**(文件名形如 `__日期__:2026-09-25`/`__结论口径__:六步里`/`__验收容器__:平台侧(模拟`/`__验收线__:`)—— 疑似某会话 heredoc/重定向写错所致。⛔ **未入库、⛔ 未删**(归属不明 ⇒ R7-边界只报告)。 +2. `交付物/插件接入验收与移动端独占-20260925.md` **双重编码乱码**(UTF-8 字节又被按 GBK 编一次 ⇒ 全文乱码)。⚠️ 已随本次提交入库(它就在磁盘上);**正文需重写**,属本线待办。 + +**⏳ 文档库侧仍未同步(⛔ 未代推)**:`docs-sync-check.sh` 报「仅本地 4」其中 **3 件是本线交接单**(`05-交接单/插件投放与分库线-02/03/04*.md`)+ 1 件属域锁线(`04-调整方案/150-*`);另 21 件内容不一致(跨线)。⚠️ 推镜像 = `scp` 到服务器(**会传导到生产**),且含他线未定稿内容 ⇒ 停手,待用户决定。 + +--- + +## IM 线 · 插件接入面(执行棒)—— 把 IM 基础改造为「各类功能插件可接入」 + +> 会话 `IM线-第13棒-插件接入面`|域锁 3 域(`src/im` · `src/web` · `test/im-sdk.test.mjs`),**收口后已反序释放**|起点 = 群组插件对接单(`dsh-plugin-partment/` **只读未改**)。 + +### 查出的总根(实证 · 比对接单更靠前) +- 🔴 `new ImSdkHost(...)` 在 `src/**` **零命中**;插件的 `check` / `onEvent` / `render` **零调用点** ⇒ 插件接入面此前是「**有契约、无载体**」:插件注册成功,跑起来一片安静。 +- 另两条 SDK 与规范不一致(实测):① `ColumnType` / `COLUMN_SQL` **漏 `bigtext`**(底层 `schema.ts`、`DB-03 §二`、`08 §3-3` 一直是 **9 种**)⇒ 按规范写会被**注册校验拒**;② `08 §3-4` 路由指针写错(真在 `plugin-data.ts`,⛔ 不是 `routes/im.ts`)。 + +### 交付(本机代码面 · 7 文件) +- `src/im/sdk/types.ts`(+206):**扩展点 8 · 出向端口**(`frame` ephemeral 广播 / `message` 落库发言)· **面板动作**(`onAction`)· 事件补 `panel.opened|closed` · `bigtext` · 分片纯函数 `chunkText` / `assembleText`。 +- `src/im/sdk/host.ts`(+362):出向注册校验 / 归属闸(**缺省 fail-closed**)/ `FrameThrottle`(硬上限,⛔ 合并归插件)/ `dispatchPanelAction` / `panelsFor` / `hasPlugins()` 短路 / `COLUMN_SQL` 补 `bigtext`。 +- **新建 `src/im/sdk/binding.ts`**(接线器):`beforeWrite`(写前问发言规则)· `afterAppend`(**后台**派发;`private` 不派发)· 面板动作(含成员校验)· 四项 fail-closed。 +- `src/im/ws.ts`(+41/−0)· `src/web/routes/im.ts`(+155/−0)· `src/im/sdk/index.ts` · `test/im-sdk.test.mjs`(+12 例 ⇒ 78 例)。 + +### 判据读数 +- `npm run build` rc=0|`node --test test/im-sdk.test.mjs` = **78 / 75 过 / 0 败 / 3 跳过**(跳过 = `spawnSync git EBUSY`)|`npm test` = **604 / 594 过 / 5 败 / 5 跳过**(5 败**全部** `spawnSync tar EBUSY`,属投放线 `shared-layer-sync.test.mjs`,⛔ 与本棒无关)|`check:layering` rc=0 **✅ 无新增违规**。 +- 内核改动形态 = `store.ts` / `hub.ts` **零改动** + `ws.ts` / `routes/im.ts` **纯插入**(numstat 删除列 = 0)。 + +### 方法论(可复用 · 本次新增两条) +1. **「有契约无载体」诊断法**:怀疑某能力"实现好了但没生效"时,grep **契约类型名的实例化点**(`new XxxHost` / `XxxRuntime(`)与**回调方法的调用点** —— 若 `src/**` 零命中,就是缺载体(而不是配置没开)。比读文档快,且结论是硬的。 +2. **判据升级法**:新需求要求动"被判据保护的文件"时,⛔ 不要删判据、也不要把它说成"必要代价";把它升级为**更强且可机械断言**的一条(本次:`diff 空` → **`numstat` 删除列 = 0**,即"只准纯插入"),并写清**意图不变**。 +3. **EBUSY 假失败**:本机 `spawnSync git|tar EBUSY` 会让测试**假失败** ⇒ 这类取证用例改 **`t.skip` 具名跳过**(⛔ 不误判成代码坏)。 + +### 未做(点名归属) +① **插件清单来源**(谁调 `binding.host.register(...)`)=**投放线** ② 宿主侧 `ImDataPort` 按 pluginId 装配(面板 `reads` 依据它取数)③ `approvedRoomTypes` 来源 = admin 审核面。⛔ **未登记下一棒**(含待拍板项不排棒);⛔ 未部署 / 未 commit / 未 push / 未投放。 + +--- + +## IM 线 · 插件接入**完备性审计**(用户两问驱动 · 只读 · 零代码改动) + +> 用户口径两条:① **插件调用「用户自己配置的模型」** ② 问 IM 是否还需完善 / 插件如何接入 / 接口接入是否已完善。 + +### 🔴 最关键结论(用户第 1 点直接决定架构判断) +- **用户自配模型** = `credential_vault`(`route`/`base_url`/`api`/`models`)→ **spawn 时写进实例**的 `$DSH_HOME/settings.yaml`(`llm-pi-ai.providers.`)+ `.credentials.yaml`(`refs.`)—— 依据 `src/db/schema.ts:261-278` V6 注释逐字。 + ⇒ **只有实例内能拿到**(平台进程拿不到别人的模型)⇒ 🔴 **插件必须跑在用户实例内**。 +- 而 **IM 内核(rooms/messages/hub)在平台进程**(`src/web/routes/im.ts`);插件分发也是**进实例**(`08 §5`:profile 写 `link:` → pnpm 建链接 → **实例进程加载**)。 + ⇒ 🔴 **插件在实例内 · IM 宿主在平台 ⇒ 跨进程**。这是本次全部结论的起点。 + +### 完备性矩阵(面 × 状态) +- ✅ **完善**:IM 内核(房间/成员/消息/游标/presence/扇出/两档传输)· 插件分发(上传→检测→建库→发布→link 装配)· **数据面**(实例内插件走 HTTP `/api/im/data/*` + `x-dsh-im-plugin-id`,ACL/中性类型/迁移/分片工具)· **用户自配模型**(官方机制)· 客户端 UI 接管(E 单 tab)· 面板清单/动作端点(09-25 加)。 +- ❌ **缺**:① **扩展点注册**(实例内插件 → 平台宿主)**无通路** ② **入向回调**(`checkSpeak`/`emit`/`renderPanel`)现为**进程内函数调用**,跨不了进程 ③ **出向投递**(`frame`/`message`)**无 HTTP 面**(09-25 做的端口是**平台侧装配** ⇒ 插件够不到)④ 插件清单来源未定 ⑤ `ImDataPort` 按 pluginId 装配未做。 + +### 插件类型分界线 +- **能接**:纯数据型(统计/台账,靠数据面)· 纯 UI 型(只读面板,靠客户端 slot)。 +- **一半**:机器人助手型(模型 ✅ / 事件订阅 ❌)· 聊天增强型(载荷 ✅ / 落库发言 ❌)。 +- **接不了**:回合/限速型(发言规则纯入向)· 实时协同型(帧出向无通路)· 外部集成型(凭据通路未立项)。 +- ⇒ 判据:**「插件单向发起」能接,「平台要调插件」接不了**。 + +### 🔴 对上一棒的自我修正(要记住) +09-25 上一棒做的 `createImPluginBinding()` + 出向端口是**平台侧装配**的,**只在「插件与宿主同进程」时有效**;真实形态是**跨进程** ⇒ **那一层还不够,须再加一层 HTTP 协议**。契约(扩展点 8 / 面板动作 / 分片)**可原样复用,⛔ 不推翻**;缺的是**传输层**。 + +### 建议的桥(两候选 · ⛔ 未替拍) +- **甲案 · 登记 + 回调**(倾向):插件启动时 HTTP 登记 manifest;平台需回调时经**实例既有可寻址通道**调进实例(⛔ 不要求插件开端口 —— `09 §2-6` 明禁)。优点:不冲突既有纪律、契约原样复用、断连可退化;缺点:要新增"平台→实例"反向调用面(含鉴权/超时)。 +- **乙案 · 宿主下沉**(认为不成立):房间是**跨用户公共资源**(成员表/消息/presence 在平台库)⇒ 每实例各持一份宿主会**状态分叉**。 + +### 顺手修的记忆问题(本工作区 · 我 lane) +- 🔴 **状态层 `MEMORY.md` 超注入上限**:实测 **9772 字符 > 7800** ⇒ **末尾约 2000 字符(三、四节 + 文档库结构 + 工作区 git)静默失效 = 等于不存在**。已做**低风险瘦身**(只压"有替代途径"的节):9772 → **9240**(省 480)。 +- ⚠️ **仍超 1440** —— 进一步压缩须动「一、状态速查 / 二、本机铁律」的**核心判据**,风险大于收益 ⇒ **停手待专门处理**(需通盘决定哪些判据可降级为指针)。⛔ 未擅自动核心状态。 +- ⚠️ 另发现:状态层「下一棒=第 12 棒动工 B 案」与 IM 入口 §0 已到**第 17 棒**不符(该行滞后)⇒ 已在瘦身时删除该易过时段落。 + +### 边界 +⛔ 本审计**零代码改动**、未部署、未 commit/push、未动 47/106、未改对接单(别的工作区)。⛔ **未登记下一棒**("桥"的路线选择需用户拍板)。产出 ⇒ `交付物/IM插件接入完备性审计-20260925.md`。 + +--- + +## 🔴 覆盖网络线 · 用户组网群聊可行性评估(用户问 · 只读 + 联网核对 · 零代码) + +> 用户问:能否让用户组成**环形网络**做群组聊天、只有数据保存到服务器?群聊技术特性(对话顺序 / 历史记录 / 其它)是否满足? + +### 判定(四条) +1. **「环形拓扑」不推荐** —— 三个硬伤:延迟 `O(N)`(N 人环到最远 N/2 跳)|单点断环|两两打洞 `O(N²)`。**业界群聊无环形态**(只有星型中继 / 小群全互联)。 +2. ✅ **「用户点对点组网」可行,底座已在**:`src/net/relay/direct/{candidate,punch}.ts`(打洞)+ `content/runtime.ts` 的 `PEER_CHANNEL_ORDER=['direct','wss']`(**直连优先、回落中继**)+ 块 id 复算闸门 + **分网隔离**(`dialers: Map>`,**默认拒绝** ⇒ "用户独立组网"**零代码**)+ 组密钥 + 一键加入/分组准入。实测口径:中继带宽节省 `1 − 组数/节点数` ⇒ 4 人 **75%** / 16 人 **93.75%**(**内容面**读数)。 +3. 🔴 **顺序与历史恰恰要求「服务器必须权威」** ⇒ 正确形态 = **服务器定序落库(权威)+ 用户间直连(加速投递)**,⛔ 不是 P2P 取代服务器。用户"只有数据存服务器"与此**一致**。 +4. 🔴 **前置硬阻塞 = 云安全组未放行**(唯一在册红 D8)⇒ 真机打洞实测**发 21 收 0** ⇒ 现在做消息面 P2P 会**全回落中继 = 白做**。⇒ 顺序 = 开云安全组 → 复测真机打洞率 → 再决定做不做。 + +### 关键取证(新事实) +- 🔴 **P2P 能力只用在「内容面」**(插件静态内容按块分发);**「消息面」零 P2P** —— `src/im/hub.ts` 扇出只有「本进程逐连接」与「交外部连接层」两条路。⇒ 群聊要用 P2P 须**新增接线**(非零成本)。 +- `src/net/relay/` 目录:`direct/`(candidate·index·punch)· `content/`(chunker·crypto·peer·runtime·source·store)· `network.ts`(`dialers` 闸门 `:407/:408`)· `join.ts` / `registry.ts`。 + +### 联网核对的群聊技术特性(5 来源同口径)—— 9 条逐条对照 +| 特性 | 环网 | 现有平台 | +|---|---|---| +| ① 对话顺序 | ❌ | ✅ `messages.seq`=DB 分配房内单调整数(**权威序**);`lamport` 只做同 seq tie-break(与业界**逐字一致**) | +| ② 历史记录 | ❌ | ✅ 追加式日志 + `resume` 游标补拉 | +| ③ 多设备同步 | ❌ | ⚠️ 只有单游标(缺 `last_read_seq`/`last_delivered_seq` 按设备) | +| ④ 成员变更边界 | ⚠️ | ✅ 成员表可判 | +| ⑤ 在线态 | ❌ | ✅ 心跳 + 1 s 批合并 + 收敛(防惊群) | +| ⑥ 群 E2EE | ⚠️ | ✅ **须 Megolm 化**(⛔ 不能对 N 人各加密一次 = `O(N²)`)—— 已在册 | +| ⑦ 扇出成本 | ❌ `O(N²)` | ✅ 中继一次发布 | +| ⑧ 单点故障 | ❌ 断环 | ✅ 两中继冗余 + 切流(18.8–20.9 s) | +| ⑨ CAP | — | ✅ 最终一致 + 会话内强序 | + +🔴 **业界铁律(本次核对的最重要一条)**:**会话内强序必须由「落库那一刻」分配 `seq`** ⇒ **定序点只能在服务器** ⇒ 否掉"纯 P2P 群聊"。全局顺序**不需要**(不同群之间无人在乎先后)。 + +### 边界 +⛔ 零代码改动、未部署、未 commit/push、未动 47/106、⛔ 未登记下一棒(路线选择 + 云安全组操作需用户定)。产出 ⇒ `交付物/用户组网群聊可行性评估-20260925.md`。 + +--- + +## IM 线 · 跨进程桥(执行棒 · 用户拍板 A 案) + +> 用户口径:「**按照你的方案处理,完善 IM 功能让群组会话的插件可以开发**」⇒ A 案 = 先开云安全组 → 复测真机打洞 → 再决定做不做消息面直连。本棒做的是**让插件真能接入**那座桥。 +> 会话 `IM线-第14棒-插件跨进程桥`|域锁 4 域(`src/im` · `src/web` · `test/im-sdk.test.mjs` · `sdk/im-plugin-client`),收口后已释放。 + +### 解决的总根(承接上一棒审计) +插件跑**实例内**(用得到该用户自配模型 —— 用户模型配置是起实例时写进实例的),IM 宿主在**平台进程** ⇒ **跨进程** ⇒ 上一棒的 `createImPluginBinding()`(平台侧装配)**插件够不到**。 + +### 交付(3 新 3 改) +- **新 `src/im/sdk/registry.ts`**:`ImPluginBridgeRegistry` —— **按实例隔离**(每实例一份宿主)· **`room_type` 全局唯一**(跨实例同名具名拒,⛔ 不整单回滚)· 形状闸**先于**占用(失败不留残登记)· **`authorizeOutbound`=bot 必须是该房成员**(未注入成员判定 ⇒ fail-closed 拒)· `unregisterInstance` / `snapshot`。 +- **新 `sdk/im-plugin-host/index.mjs`**(插件侧 SDK · 零依赖单文件):`createImPluginHost` → `ready()`(登记,幂等、⛔ 不抛)/ `frame()`(ephemeral 广播)/ `say()`(bot 落库发言)/ `rooms()` / `messages()` / **`subscribe()`(轮询增量订阅 —— 插件不用自己写 WS)** / `close()`;+ `sanitizeManifest`(**剔函数** ⇒ 跨进程前提)+ `HOST_SDK_VERSION`。+ `package.json`。 +- **新 `test/im-plugin-bridge.test.mjs`(14 例)**。 +- 改 `src/web/routes/im.ts`(+约 230):**5 个端点** = `plugins/register` · `plugins/unregister` · `plugins/out/frame` · `plugins/out/message` · `plugins/bridge`(admin 观测);`bridgeAuth` **复用已有 `resolveInstanceToken`** ⇒ 实例键 = `u:`。 +- 改 `src/im/sdk/index.ts`(门面)|根 `package.json`(`test`/`verify` 各登记 1 处,✅ **diff 仅 2 行**)。 + +### 判据读数 +`npm run build` rc=0|`node --test test/im-plugin-bridge.test.mjs` = **14/14 过/0 败**|`npm test` = **618/608 过/5 败/5 跳过**(基线 604/594 ⇒ +14 全为本棒;5 败**仍是**投放线 `tar EBUSY`,⛔ 未增一败)|`check:layering` rc=0 **✅ 无新增违规**。 + +### 🔴 两条自决取舍 +1. **登记只收「声明面」**:`check`/`onEvent`/`render`/`onAction` 是**函数**跨不了进程 ⇒ 只传声明;SDK 自动剔除(测试断言"序列化后不含 `function`")。**函数侧回调留在实例内**。 +2. **出向发言多一道闸**:同进程时 bot 必然由房主加进成员表;**跨进程必须自己查** ⇒ `authorizeOutbound`。 +3. **不做"平台反向打进实例"**(那要实例可寻址 + 心跳,脆)⇒ 改用**插件拨出**(符合"拨出式"纪律,⛔ 插件不开端口)。 + +### 本期不支持(如实登记 · ⛔ 不假装) +| 项 | 原因 | 降级 | +|---|---|---| +| **`speakRules.check`** | "平台写之前同步问插件" ⇒ 需**反向打进实例**,未建 | ① 写进房间 `config`(房主可改)② 用出向主动纠偏 ③ 等桥 v2 | +| `events.onEvent` 回调 | 同上 | **`host.subscribe()` 轮询增量够用** | +| 面板 `render`/`onAction` | **不需跨进程**(面板在实例内渲染) | 要写数据/发言用出向 | +| 出向的**审计归属** | 不校验"插件是否真在该实例启用"(与 `/api/im/data/*` 现状同源) | 补法 = 装配时把 `pluginId` 绑进凭据 ⇒ **投放线**面 | + +### 🔴 一句判据 +**「插件单向发起」的现在能接**(登记 / 读 / 出向),**「平台要回头叫插件」的还没接**(发言规则 / 事件回调)—— 后者是本条**唯一剩余缺口**。 + +### 文档 + 教训 +- `09-…契约.md` **新增 §10 跨进程接入**(为什么需要 / 三条面 / 照抄用法 / 不支持表 / 判据)|`08-…对接规范.md` 顶部加 09 §10 指针。 +- ⚠️ **教训(写码者自查)**:我一度用 **Python `json.dumps` 重写 `package.json`** 去登记测试脚本 —— 那会**重排整个文件格式**。**实测侥幸**(原文件恰好同为 2 空格缩进 ⇒ `numstat` 仅 `2/2`),但**这是运气不是安全**。⇒ 🔴 **改 `package.json` 一律用 Edit 精确替换**,⛔ 不用「读-改-写」整个 JSON。 +- ⛔ 未部署 / 未 commit / 未 push / 未动 47/106 / **未动云安全组(D8 仍需用户控制台操作)** / 未改 A–E 五单结论。⛔ **未登记下一棒**。 + +--- + +## 第 36 棒 · 插件投放与分库线 · 执行棒(2026-09-25 23:13–23:5x)· 会话 `p36-exec-plugin-dataline` + +### 目标与结果 +入口 `接续入口_插件投放与分库线_20260922.md` §2「第 36 棒」+ 执行单 `插件投放与分库线-04MCN数据面接入改造-阶段二第三批.md` §五 S5–S8。 +**① ② ③ ④ + ⑤ 的「47 侧切档」达成**;**⑤ 的「实例进程内面 / 106 同步」未达成**(原因具名)。 + +### 关键读数(每项均有命令原文+输出原文+退出码,见交付件 §6) +| 项 | 读数 | +|---|---| +| ① 修 `canonicalJsonText()` 键序 | `node --test` **67/67**(`# fail 0`);本机等效证明 **10/10** | +| ② 出包上池 share | `0.5.1` · 2,018,751 B · 324 条目 · md5 `56c17c7e41a18704922e18d75816da59` · share `200 action:"replaced"` · 47 共享层 `store.js` md5 `62384d4d96d5cd90f242044ac9351828`=本机逐字一致 | +| ③ 删本地 5 条旧副本 | `account_video_source` **39 → 34**;`KEY_DELTA lost=[] extra=[]`;`INTEGRITY=ok` | +| ④ 影子读 | A1 `34=34`/`362=362`/`5=5`;三表 `match=true`·`degraded=false`;**正文 34/34 · 分析 34/34 · 差异 0** | +| ⑤ 切档 | 实例 `data-plane.json` **7 表全 `platform`**;A2 取数口面 `TOTAL_WRITES=0` | +| 库文件指纹 | 删前 `b37f8aab51a90f7481eb321e50456ca347635bcf2bfce567ea4200d88c18277a`(39 行)→ 删后 `8d96b9f1aaa5063e7e2ef70fa23ffc2dfeb4f4fbf5f1a985ff6dd4960be33ab1`(34 行,2,383,872 B,mtime 23:33);整库备份 `.bak` `ee75df4a749ce05d739bcbe3c3b3171d68eb52e3ef04d2006cda530f83df3b3d`;留档 `local-dump-before.jsonl` `fdfd8fa4…`;5 组导出 `export-dup5.json` `b0e560ca…` | +| 收尾 R10/R4 | `ROOT_OWNED_COUNT=0` · `NO_WAL_SHM` · `TMP_P36_LEFT=0` · 实例文件 owner `114801:114801`/`114801:1002` · R4 临时会话 `SESSIONS_BEFORE=8 → DELETED=5 → AFTER=3`、`POC_LEFT=0` | + +### 🔴 三条新技术事实(跨棒复用) +1. **切档不需要重启** —— `dataPlaneConfig()` 每次调用都 `readFileSync`、**无缓存** ⇒ 改 `data-plane.json` 后**运行时立即生效**。与第 33/34 棒「改完须重启」的直觉相反,⛔ 别再按重启排期。(唯一仍需重启的是 **client bundle** 与 **shared 层投放**。) +2. **`jsonb` 键序重排 = 逐键比对假阴性的真源** —— PG `jsonb` 按「键长+字母序」重排,插件侧 `JSON.stringify` 保序 ⇒ 凡值以 jsonb **对象**形态落库的行读回**必然不等**。修法 = 归一后**递归按键排序**(新增 `sortKeysDeep()`+导出 `canonicalStringify()`),⛔ 不是"放宽判据"。🔴 `canonicalJsonText()` 还供**迁移幂等键 / `imports.js` 同内容去重 / 预览判据**共用 ⇒ 不修则切档后**同内容被判成不同内容**。 +3. **删重口径两侧相反(具名偏差)** —— 迁移脚本 `tools/migrate-local-to-platform.mjs:122` 的 `SELECT *` **无 `ORDER BY`** ⇒ 平台折叠保留 `id` **小**者;本地读路径是 `ORDER BY id DESC LIMIT 1`(取 `id` **大**者)。5 组里 **2 组删的是 `id` 较大**那行(`7658997937279683855` 删 id 40 留 20;`7648947073131625905` 删 id 28 留 27)。差异 = 值内 **2 字节字面 `\n`**;已原样留档 ⇒ 可回滚。 + +### 🧰 本机运维坑(三次实测 · ⏳ **待写进 `PLAYBOOK-实例与插件坑.md`** —— 本棒域锁未含该文件,故此处留底) +1. **`pkill -f "<含本命令字面>"` 会自杀** —— `pkill -f "find /var/lib/dshs/users"` 命中自己的 `bash -c` 命令行 ⇒ 整条命令报 `RC=0` 但**后半段全没跑**(假成功)。⇒ 用字符类 `fin[d] …`,或**一律写成脚本走 `bash -s` 管道执行**(脚本正文不在 argv 里)。 +2. **`node -e` 的 pg 探针会挂住并泄漏连接** —— 一个探针在 `await c.end()` 附近挂死;**5 个残留进程**累积后,后续 pg 探针**连 `connect()` 也挂住**(`RC=124`)。⇒ ① 探针**一律写成独立 `.mjs` 上传执行**(本棒已按此办,仅收尾核验用了 `-e` 才踩到)② 每棒收尾 `pkill -9 -f 'node -e'` 并复核 `pgrep -c` 归零。🔴 实测:`NODE_E_BEFORE=5 → AFTER=0` 之后,原本卡住的探针立刻正常返回。 +3. **`find … | wc -l` 与 `find … -print -quit` 都很快**(实例 home 全量遍历 < 90 s)⇒ **把上一棒的 hang 归因给 `find` 是误判,真凶是 `node -e` 探针**。 +4. **实例 home 路径带连字符** —— 目录名 = 完整 UUID `users/cce6d1cd-b376-4304-80f0-0e1c58c9ffde/home`,而**属主用户名**是去连字符的 `dsh-cce6d1cdb376430480f0`。⛔ 两者别混用(本棒一次手写路径漏连字符 ⇒ `UHOME_IS_DIR=no`,白跑一轮)。 + +### ⛔ 未达成(如实记 · ⛔ 非本棒引入) +- **A2「真实例进程内」面**:实例 `status=stopped`(按需启动)。Manager 侧未找到启停路由;worker agent `POST /launch`(`127.0.0.1:19100`,头 `x-dsh-agent-token`)属**控制面职责**,直调会绕过 `epoch/lease` 台账 ⇒ 状态漂移(R11)⇒ **主动不做**。 +- **A8 两机不一致**:106 经 47 中转与本机直连**均 `Permission denied (publickey,gssapi-keyex,gssapi-with-mic)`**;47 亦无法直连 106:19000(`overlay_devices.via = "relay"`)⇒ **106 未同步、未取证,本棒未触碰**。 + +### ⚠️ 一条记忆纠偏(实测推翻旧规则) +工作区 `MEMORY.md` / 本文档「工作区 git」节记「已跟踪的 `05-交接单/README.md` 不受影响」—— **实测不成立**:文档库仓库 `.gitignore:45` = `dsh-server-docs/05-交接单/`;`git check-ignore -v` 命中该行,`git ls-files --error-unmatch` 报 `did not match any file(s) known to git` ⇒ **该目录整体被忽略,README.md 未跟踪、不入库**。🔴 台账**只存磁盘**(+服务器镜像),⛔ 不能靠 git 保全。 + +### 待拍板(2 项 · 已在入口 §0 与 README §一 具名) +1. **A2 实例进程面**:是否授权走平台控制面启停口把该用户实例拉成 `running`(**优点** = 补齐最后一条端到端判据;**缺点** = 会中断该在线用户)。 +2. **106 同步窗口**:是否给 106 直接 ssh 通路(或走 47 转发)(**优点** = A8「两机一致」可验、共享层 `0.5.1` 落地;**缺点** = 需动 106 的凭据/通道)。 + +--- + +## IM 线 · 🔴 表述更正 + 容量与瓶颈评估(用户质疑驱动 · 只读 · 零代码) + +> 用户质疑:「**现在不是可以覆盖网络走 443 端口吗**」,并追问「服务器压力大吗 / 支持多少人在群组聊天 / 瓶颈在哪里」。 + +### 🔴 更正(我上一轮的表述错了) +- 上轮说「不做 P2P 等于白做」⇒ **指的是带宽优化白做,⛔ 不是聊天不可用**。表述有误导。 +- **准确口径**:**中继走 443**(`wss://ai1net.com/dshs-relay` + `relay-direct.ai1net.com` **443 兜底**)⇒ **功能全可用、⛔ 不用开端口**。 +- 🔎 取证:`relay-direct` 的注释原文 = 「**443/TCP 兜底 · 去 CF**」(`addr-override.ts`)⇒ 它是**第二个中继入口**(绕 CF 直连服务器 IP),**⛔ 不是 P2P**;中继监听 `127.0.0.1:20080` 由 nginx 前置 443(`server.ts:58`)。 +- **UDP 21100–21115 只服务 P2P**(`punch.ts` 用 `node:dgram` 的 `udp4/udp6`)⇒ **开端口 = 可选项**。 +- **为什么 443 顶不了 P2P**(机制性):① 443 是"连服务器",中继是**星型** ② 两台的 443 都被 nginx 占用 ③ **UDP 打洞才可靠**,TCP 打洞对 NAT 极敏感。 +- 🔴 **修正建议**:开端口按**内容面**算账(跨节点内容分发,实测省带宽 **75%**(4 节点)– **93.75%**(16 节点)= `1 − 组数/节点数`;⚠️ **真机现值 = 0**,因 D8 未开);⛔ **不要按"群聊"算账**(消息是小包,省下的绝对带宽很小)。⇒ 近期不做内容分发 ⇒ **可以完全不开**。 + +### 容量实测读数(106 真机) +| 连接数 | 自研 P99 | 外部连接层 P99 | 自研边际内存 | 连接层边际内存 | +|---|---|---|---|---| +| 1k | 45.3 ms | 28.9 ms | **7.5 KiB/连接** | 70.1 KiB/连接 | +| 5k | 256.3 ms | 68.1 ms | — | — | +| 10k | **421.1 ms** | **111.9 ms** | 131.8 MiB(总) | 750.2 MiB(总) | + +- 判据:**实时档 100 ms / 聊天档 1000 ms** ⇒ 自研 **~2–3k 连接就越过实时档**;10k 时聊天档仍可。外部连接层 10k 仍在实时档内,代价内存 **11.9×**。心跳超时率 **0**、upgrade 全成功。 +- **单房上限 = 2,000**(已固化);内核扇出极便宜(2k 连接下 hub 扇出 **P99 = 0.023 ms**、预算判定 **0.030 ms**、写库恒 **1 条 INSERT**)⇒ **不是瓶颈**。 + +### 🔴🔴 本轮查出的真实隐患(第一优先 · 尚未修) +`src/im/ws.ts` 的 `send()`: +```js +try { socket.write(textFrame(text)); return true } // 🔴 返回值被丢弃 +``` +- `socket.write()` 返回 `false` = **缓冲已满、应等 `drain`**;这里既不看返回、也不 `pause`/`destroy` ⇒ **慢客户端(网速差/故意不读)会让内存无界增长**;且 `delivered` 把"已排队"记成**已送达**(计数虚高)。 +- **为什么是第一位**:**单点即可打垮服务器**(一个慢连接 × 高频房间),比"能撑多少连接"更要紧;且属**修 bug**(技术项可自决),**无需扩容**)。 +- **修法(未开工)**:`write()` 返回 `false` ⇒ 记背压,超阈值 `destroy()`(具名原因);`delivered` 语义改"已写入缓冲",另出 `backpressure` 计数。⚠️ **阈值须先量**"正常客户端缓冲水位",⛔ 不拍脑袋。 + +### 瓶颈排序 +① **扇出无背压**(本轮新发现,未修)② 单进程全量扇出排队(已实测 P99 ≈ 事件循环滞后,已由外部连接层缓解,根治靠多进程/多机)③ `write()` 绝对总量(分片实验已证"切细分时"无效 ⇒ 须并行)④ presence 惊群(已有 1s 批合并+收敛,优于朴素 17–3333×)⑤ **插件桥轮询**(见下)。 + +### ⚠️ 本轮我引入的负载(诚实报告) +插件 SDK 的 `subscribe()` 是**轮询**(默认 1.5 s):每实例每订阅房 1 req/1.5s ⇒ 3 插件 × 5 房 = **10 req/s/实例**,**随实例数线性**(100 实例 ⇒ 1000 req/s;1000 实例 ⇒ 10000 req/s),**且与消息量无关**(空房也轮询)⇒ "静默增长"型。缓解:间隔 ≥3 s;或改平台推送(需反向通道,未建)。 + +### 一句话 +**群聊今天就能用(走 443 中继),端口是可选优化**;容量上连接层已能撑 10k 在线;**最该动的不是扩容,而是补扇出背压控制**。产出 ⇒ `交付物/端口与容量瓶颈评估-20260925.md`。⛔ 零代码改动、未部署、未 commit/push、未登记下一棒。 diff --git a/.workbuddy/memory/automations/35e1bfbc-d247-4db2-9d90-b0580793736f/memory.md b/.workbuddy/memory/automations/35e1bfbc-d247-4db2-9d90-b0580793736f/memory.md new file mode 100644 index 0000000..ef683e1 --- /dev/null +++ b/.workbuddy/memory/automations/35e1bfbc-d247-4db2-9d90-b0580793736f/memory.md @@ -0,0 +1,15 @@ +# 插件投放与分库线 · automation 记忆 + +## 35e1bfbc-d247-4db2-9d90-b0580793736f(第 36 棒 · 执行棒 · 一次性) + +- **2026-09-25 23:13–23:5x**:会话 `p36-exec-plugin-dataline`。接续单 = `插件投放与分库线-04MCN数据面接入改造-阶段二第三批.md` §五 S5–S8。 +- **结果**:① ② ③ ④ + ⑤ 的「47 侧切档」达成;⑤ 的「实例进程内面 / 106 同步」未达成(原因具名)。 + - ① `canonicalJsonText()` 键序归一(`sortKeysDeep` + 导出 `canonicalStringify`)⇒ 单测 67/67、本机证明 10/10。 + - ② 出包 `0.5.1`(2,018,751 B / 324 条目 / md5 `56c17c7e…`)→ 上池 → `state=ready` → share `200 action:"replaced"`。 + - ③ 删本地 5 条重复 `aweme_id` 旧副本 ⇒ `account_video_source` 39 → 34(留档 `.bak` + dump + `export-dup5.json`)。 + - ④ 影子读全绿:34=34 / 362=362 / 5=5;三表 `match=true`;正文 34/34 · 分析 34/34 · 差异 0。 + - ⑤ 三表切 `platform` ⇒ 实例 `data-plane.json` 7 表全 `platform`;A2 取数口面 `TOTAL_WRITES=0`。 +- **未达成 2 项**:A2「真实例进程内」(实例 stopped,启停属控制面职责 ⇒ 主动不做);A8(106 无 ssh 通道 ⇒ 未同步未取证)。 +- **收尾**:R10 `ROOT_OWNED_COUNT=0`;R4 临时会话 `DELETED=5`/`POC_LEFT=0`;47 `/tmp/p36*` 清零;本线域锁已释放;本 automation 置 `PAUSED`;⛔ 未登记第 37 棒(撞待拍板 2 项)。 +- **交付件**:`$WS/交付物/MCN数据面接入-阶段二-切档与收口-20260925.md`;入口 `§0`/`§2` + `05-交接单/README.md §一` 已更新。 +- **教训(下棒必看)**:① `node -e` 的 pg 探针会挂住并泄漏连接(累积 5 个后连 connect 都挂)⇒ 探针一律写独立 `.mjs`,收尾 `pkill -9 -f 'node -e'` 复核归零。② `pkill -f` 命中自身命令行会自杀 ⇒ 用字符类或走 `bash -s` 脚本。③ `dataPlaneConfig()` 无缓存 ⇒ **切档运行时立即生效、不需要重启**。④ 实例 home 目录名 = 带连字符 UUID,属主用户名 = 去连字符。 diff --git a/交付物/MCN数据面接入-阶段二-切档与收口-20260925.md b/交付物/MCN数据面接入-阶段二-切档与收口-20260925.md new file mode 100644 index 0000000..43f14e9 --- /dev/null +++ b/交付物/MCN数据面接入-阶段二-切档与收口-20260925.md @@ -0,0 +1,316 @@ +# MCN 数据面接入 · 阶段二 · 第三批 —— **切档与收口**(第 36 棒 · 执行棒) + +> **会话**:`p36-exec-plugin-dataline`|**时间**:2026-09-25 23:10–(GMT+8) +> **工作区**:`E:/ProgramData/AIProject/aliyun-dsh-server` +> **纪律体**:`git pull`;**本棒按用户 23:0x 授权执行 commit + push**(读数补记于 §7/§8) +> **上游依据**:入口 `§0` 顶部 **22:44 拍板行** + `§2`「⏭️ 本轮动作(第 36 棒)」|唯一执行单 = `$DOC/05-交接单/插件投放与分库线-04MCN数据面接入改造-阶段二第三批.md` +> **上一棒依据件**:`$WS/交付物/MCN数据面接入-阶段二-B案落地与shadow读数-20260925.md`(第 35 棒 · 两条根因) + +--- + +## 一 结论(一句话) + +**① – ⑤ 里,① ② ③ ④ + ⑤ 的「47 侧切档」全部达成,且第三批三表首次出现「计数 34 = 34 + 内容判据 34/34 全绿」**;**⑤ 的「实例重启 / A2 实例内面」与「106 同步」未达成**(原因具名,见 §5),**A8 如实记「两机不一致(106 未取证)」**。 + +| # | 范围项 | 状态 | 关键读数 | +|---|---|---|---| +| ① | 修 `canonicalJsonText()` 键序敏感(递归按键排序)+ 补单测 | ✅ 达成 | 单测 **67/67**(`# fail 0`);本机等效证明 **10/10**(`fail=0`) | +| ② | 出包 `0.5.1` → 上池 → 发布共享层 | ✅ 达成 | `2,018,751 B`/**324 条目**/md5 **`56c17c7e41a18704922e18d75816da59`**;share `200 action:"replaced" version 0.5.1`;47 共享层 `store.js` md5 **`62384d4d…`=本机逐字一致** | +| ③ | 删本地 5 条冗余副本(拍板①) | ✅ 达成 | **39 → 34 行**;`KEY_DELTA lost=[] extra=[]`;`integrity_check=ok`;⛔ 库文件未被删、属主不变 | +| ④ | 重取 `account_video_source` 影子读 | ✅ 达成 | A1 **34 = 34**、`onlyLocal=[] onlyPlatform=[]`;SHADOW **`match=true`**;**逐行内容判据 正文 34/34 · 分析 34/34 · 差异 0** | +| ⑤ | 三表切 `platform` | ✅ **达成(47 侧)** | 实例 `data-plane.json` ⇒ **7 表 `platform`**(A5 达成);A2 探针三表 `source=platform` 全 OK、零写请求 | +| ⑤ | 47 实例 + 106 合并重启窗口 | ⛔ **未达成** | 实例当前 `stopped`;106 **无 ssh 授权 key**(见 §5) | +| ⑥ | 收口(锁/automation/入口/README/日志/交付件) | ✅ 见 §5 §6 | +| ⑦ | 同步到仓库 | ✅ 见 §6 | + +--- + +## 二 ① 修法(`canonicalJsonText()` 键序无关) + +### 2.1 根因(承第 35 棒「根因乙」) + +平台 PostgreSQL 的 `jsonb` **按「键长度 + 字母序」重排对象键**;旧实现是保留键序的 `JSON.stringify` ⇒ 凡值以 jsonb **对象**形态落库的行,读回后 canonical 必然不等(**假阴性**)。 + +### 2.2 改法(`lib/host/mcn/store.js`) + +新增 `sortKeysDeep()`(对象键递归升序;**数组保持原序**)+ 导出 `canonicalStringify()`(稳定序列化),`canonicalJsonText()` 的三条归一出口全部改走它: + +```js +export function canonicalStringify(v) { + return JSON.stringify(sortKeysDeep(v ?? null)); +} +export function canonicalJsonText(v) { + if (typeof v !== "string") return canonicalStringify(v); + const s = v.trim(); + if (s.startsWith('"') || s.startsWith("{") || s.startsWith("[")) { + try { return canonicalStringify(JSON.parse(s)); } catch (e) { /* 形似 JSON 但解析不过 ⇒ 当纯文本 */ } + } + return JSON.stringify(v); +} +``` + +⛔ **落库原文零改动**(仍走 `repairJsonText` 的原样口径)· ⛔ 不动平台 · 数组顺序语义保留。 + +### 2.3 单测(`tests/mcn-store.test.mjs` · 新增第 59 条) + +```bash +node --test tests/mcn-store.test.mjs # 退出码 0 +``` +``` +# tests 67 / # pass 67 / # fail 0 / # cancelled 0 / # skipped 0 +ok 67 - 59 `canonicalJsonText` 键序无关(平台 `jsonb` 重排对象键 ⇒ 否则平台档把同内容判成不同内容) +``` + +### 2.4 本机等效证明(修法有效 + 零回归 · 10/10) + +```bash +node tmp/p36/proof-canon.mjs # 退出码 0 +``` +``` +PASS | A1 前提:键序确实被重排 | local={"aweme_id":"7001","desc":"文案","n":3,… plat={"n":3,"desc":"文案","tags":["a","b"],"awe… +PASS | A2 旧算法:判**不等**(复现根因乙的假阴性) +PASS | A3 新算法:判**等**(缺陷已修) +PASS | A4 稳定键随之键序无关(迁移幂等 / 去重共用) +PASS | B 零回归(Markdown 正文):新旧逐字相同 +PASS | B 零回归(Markdown 的 JSON 字符串形态):新旧逐字相同 +PASS | B 零回归(gz1 压缩态):新旧逐字相同 +PASS | B 零回归(纯数组文本):新旧逐字相同 +PASS | C1 数组顺序保留([1,2] ≠ [2,1]) +PASS | C2 对象数组内部元素的键被排序 +PROOF_SUMMARY fail=0 +``` + +> 🔴 **回归风险已评估并说明**:`canonicalJsonText` 另供两个**派生稳定键**使用 —— `personaKey`(`account_persona`)/`analysisKey`(`account_analysis`)。二者内容形态为 **Markdown 文本**(落 jsonb `string` ⇒ 走"纯文本"分支 ⇒ **逐字等价**),且这两张表在第一/二批影子读下**已 `match=true`** ⇒ **键值不变、零回归**(证明 B 组四例即为此口径)。 + +--- + +## 三 ② 出包 `0.5.1` → 上池 → 发布共享层 + +```bash +cd "$PKG" && node "$NB/node_modules/npm/bin/npm-cli.js" pack --pack-destination dist # 退出码 0 +md5sum dist/dsh-plugin-mcn-suite-0.5.1.tgz | cut -d' ' -f1 +``` +``` +56c17c7e41a18704922e18d75816da59 # BYTES=2018751 / 条目=324 / shasum e075c42d8a37ec8a203cfdce7bb56be6dcaf6022 +``` +**包内文件与本机源码逐字一致**(sha256):`store.js` `ca480930dfaa836735ce36e2b2f9c61819f15df554a276cbbc49b98e940bd8a0`(包内同值);`package.json` `5b08a54b…`(包内同值);包内 `version = 0.5.1`。 + +```bash +ssh root@47.77.182.89 'bash -s' < tmp/p36/remote-01-share.sh # 退出码 0 +``` +``` +### 0) tgz 指纹(应与本机一致) 56c17c7e41a18704922e18d75816da59 +### 3) 入池 POST /api/plugins/business HTTP=200 {"ok":true,"compat":{"level":"ok","findings":[],"prereleaseOnly":1}} +### 4) 台账状态(share 前置,须 ready) LEDGER=[{"pluginId":"dsh-plugin-mcn-suite","state":"ready","schemaVersion":1,"lastError":null}] +### 5) 结构预演(无声明变更) PLAN_HTTP=200 STATE="ready" EXECUTABLE=false ITEMS=[] +### 6) 发布到共享层 POST …/business/share HTTP=200 + {"ok":true,"action":"replaced","version":"0.5.1","fileCount":659, + "backupTgz":"/opt/dsh/backups/plugins/dsh-plugin-mcn-suite/0.5.1.tgz", + "rollbackTgz":"…/0.5.0.tgz"} +### 7) 共享层落地读数 "version": "0.5.1" canonicalStringify_COUNT=4 store.js md5=62384d4d96d5cd90f242044ac9351828 +``` + +🔴 **顺序合规**:「入池 → 台账 `state=ready` → 才 share」逐字遵守(未撞 409 `datastore_not_ready`)✅ +⚠️ `compat.findings: []`、`prereleaseOnly: 1`(平台侧既有事实,非本棒引入)。 +📌 **回滚素材**:`0.5.0.tgz`(2017667 B)在位 ✅ + +--- + +## 四 ③ 删本地 5 条冗余副本 + ④ 影子读重取 + +### 4.1 🔴 判定:删哪一份(**与字面口径的偏差,务必读**) + +本地 39 行 = 34 唯一键 + 5 组重复。按「保留与**平台内容一致**的那一份」(对 3 组两组皆一致 ⇒ 保留 `id` 最大者): + +| aweme_id | 本地 id | 与平台内容 | 保留 | 删除 | 差异性质 | +|---|---|---|---|---|---| +| 7658997937279683855 | 20 / 40 | **20 ✅** / 40 ❌ | **20** | 40 | 值内多 2 字节**字面 `\n`**(`\|` → `\|\n`) | +| 7648947073131625905 | 27 / 28 | **27 ✅** / 28 ❌ | **27** | 28 | 值内多 2 字节**字面 `\n`**(方向相反) | +| 7667477088350044069 | 29 / 44 | 29 ✅ / 44 ✅ | 44 | 29 | 仅 JSON 结构空白(CRLF↔LF) | +| 7631176055600527729 | 31 / 42 | 31 ✅ / 42 ✅ | 42 | 31 | 仅 JSON 结构空白(CRLF↔LF) | +| 7629803455813243043 | 32 / 46 | 32 ✅ / 46 ✅ | 46 | 32 | JSON 结构空白 | + +> 🔴 **偏差如实报**:其中 **2 组删掉的是 `id` 较大(较新)那一行**,⛔ **不是**"时间上更旧"的那份。原因:**迁移工具 `SELECT *` 无 `ORDER BY` ⇒ SQLite 自然序(`id` 升序)⇒ 平台留下的是 `id` 较小那份**;而本地读路径是 `ORDER BY id DESC LIMIT 1`(取 `id` 最大)⇒ **两侧口径相反**(工具自身也输出「🔴 DUP-KEYS … ⛔ 必须人工裁定」)。 +> ⇒ 若按字面"删 `id` 小的一方",则本地剩余内容与平台**不一致**(2 组)⇒ ④「内容判据全绿」**必然达不成**。故按「**与平台对齐**」是唯一能同时满足 ③(39→34)与 ④(全绿)的判定;**该判定为内容取舍,已具名列出,供复核**。 + +### 4.2 留档(先导出 → 再删 · 双份) + +```bash +# [真机 47] 库文件备份(无 -wal/-shm 伴生 ⇒ 单文件;用 sqlite3 .backup 而非裸 cp) +sqlite3 "$DB" ".backup '/tmp/p36/mcn-plugin.db.bak'" # BACKUP_RC=0 BAK_BYTES=2383872 sha256=ee75df4a749ce05d739bcbe3c3b3171d68eb52e3ef04d2006cda530f83df3b3d +# 全表原样留档(39 行 · 实例 uid 跑 · readOnly) +runuser -u "$UOWNER" -- env P36_HOME="$UHOME" node --experimental-sqlite /tmp/p36/p36-del-dup.mjs --dump > local-dump-before.jsonl # DUMP_RC=0 sha256=fdfd8fa4b921970ceff9a27f2e165ad69d54d82ec9393318012197fcb61bfd54 +# 5 组原样(含平台对照 · 正文未截断) +# /tmp/p36/export-dup5.json 542,624 B(47)/ 542,624 B(本机) sha256=b0e560ca6ddc255c08b6f97edab1feec0330ac43a53777ae437c29166895a925 +``` +**本机留档落点**:`$WS/tmp/p36/remote-artifacts/{local-dump-before.jsonl, mcn-plugin.db.bak}` + `$WS/tmp/p36/export-dup5.json`(三份 sha256 已与 47 逐字核对)。 + +### 4.3 删除(实例 uid 跑 · fail-closed 断言内建) + +```bash +runuser -u "$UOWNER" -- env P36_HOME="$UHOME" node --experimental-sqlite /tmp/p36/p36-del-dup.mjs --delete # DEL_RC=0 +``` +``` +BEFORE rows=39 unique_aweme=34 +DROP_IDS=[40,28,29,31,32] PRECHECK=OK(将保留 34 行 / 键 34 个) +DELETED per=[{"id":40,…,"changes":1},{"id":28,…,"changes":1},{"id":29,…,"changes":1},{"id":31,…,"changes":1},{"id":32,…,"changes":1}] +AFTER rows=34 unique_aweme=34 +KEY_DELTA lost=[] extra=[] +TABLE_COUNTS={"account_analysis":1,"account_persona":4,"account_video_analysis":12,"account_video_source":34, + "account_videos":362,"creative_log":0,"hot_accounts":5,"rewrite_log":13,"script_review":2,"storyboard_log":3} +INTEGRITY=ok +DELETE_DONE ok=true +``` +```bash +# [真机 47] sqlite3 直读复核(第三方口径) +sqlite3 "$DB" "SELECT count(*), count(DISTINCT aweme_id) FROM account_video_source;" # → 34|34 +stat -c 'size=%s owner=%U mode=%a' "$DB" # → 2383872 / dsh-cce6d1cdb376430480f0 / 644(库文件在位、属主未变) +``` +> ⛔ **只删行**:库文件 `mcn-plugin.db` **未被删**、`db.js` **未动**、库**未重建**;db sha256 `b37f8aab…` → `8d96b9f1aaa5063e7e2ef70fa23ffc2dfeb4f4fbf5f1a985ff6dd4960be33ab1`(预期变化=按拍板删了 5 行)。 +> ⛔ **插件既有删除路径不适用**(已核):`removeVideoSource()` 按 `aweme_id` 删**整组** ⇒ 会连保留行一起删;插件内**没有**"按本地 `id` 删 `account_video_source` 单行"的路径 ⇒ 用最小 SQL(`DELETE … WHERE id = ?`)+ 四条断言(存在性 / 不删唯一行 / 删后 34 / 键集合零变化)。 + +### 4.4 ④ 影子读重取(三表) + +```bash +ssh root@47.77.182.89 'bash -s' < tmp/p36/remote-05-shadow.sh # SHADOW_RC=0 +``` +``` +A1 [account_video_source] 本地键=34 平台行=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 +SHADOW [account_video_source] source=local mode=shadow degraded=false shadow={"platform":34,"local":34,"match":true} +SHADOW [account_videos] source=local mode=shadow degraded=false shadow={"platform":362,"local":362,"match":true} +SHADOW [hot_accounts] source=local mode=shadow degraded=false shadow={"platform":5,"local":5,"match":true} +CONTENT 本地行=34 平台行=34 正文同=34 分析同=34 两项全同=34 差异(正文)=0 差异(分析)=0 平台缺=0 +PACKED_LEFTOVER=0 +TOTAL_CALLS=10 TOTAL_WRITES=0 +``` +**对比第 35 棒**:`account_video_source` 由 `match=false(34 vs 39)` + 内容 `23/34 不等` ⇒ **`match=true` + `34/34 全等、差异 0`** ✅ ⇒ **① 的修法在真机得到确认**。 + +--- + +## 五 ⑤ 切档 + 未达成项 + +### 5.1 三表切 `platform`(47 侧 ✅) + +```bash +runuser -u "$UOWNER" -- env DSH_HOME="$UHOME" node /tmp/p36/p36-switch.mjs # SWITCH_RC=0 SWITCH_DONE bad=0 +``` +``` +ASSERT [hot_accounts] 读=platform 写=platform => OK +ASSERT [account_videos] 读=platform 写=platform => OK +ASSERT [account_video_source] 读=platform 写=platform => OK +KEEP [account_persona|creative_log|account_analysis|video_analysis] 读=platform 写=platform +LOCAL [rewrite_log|storyboard_log|script_review] 读=local 写=local +PLATFORM_TABLES(7)=["account_persona","creative_log","account_analysis","video_analysis","hot_accounts","account_videos","account_video_source"] +``` +**A5 判据(真机读 `/.dsh/data-plane.json`)** ⇒ 三表均 `platform` + 前两批 4 表保留 ⇒ **共 7 表** ✅;第四批三表**仍 `local`** ✅;属主 = 实例 uid ✅;原文件已备份 `data-plane.json.bak-p36-1790350596159`。 +> 📌 **生效机制(本棒实测)**:`dataPlaneConfig()` **每次调用都 `readFileSync`**(无内存缓存)⇒ 切档**运行时立即生效**,**不需要重启**。 + +### 5.2 A2③(切档后插件读路径) + +```bash +ssh root@47.77.182.89 'bash -s' < tmp/p36/remote-07-a2.sh # A2_RC=0 +``` +``` +REAL_CFG_TABLES=[…7 表均 platform/platform] # 档位取自**实例真实配置的副本**(⛔ 未写实例 home) +MODE [hot_accounts|account_videos|account_video_source] 读=platform 写=platform => OK +READ [hot_accounts] source=platform mode=platform rows=5 本地=5 degraded=false => OK +READ [account_videos] source=platform mode=platform rows=362 本地=362 degraded=false => OK +READ [account_video_source] source=platform mode=platform rows=34 本地=34 degraded=false => OK +TOTAL_CALLS=5 TOTAL_WRITES=0(必须 0) MODE_BAD=0 A2_DONE ok=true +``` +> ⚠️ **口径如实标注**:本条为「**实例 home + 实例真实档位 + 插件代码**」面;**真实例进程内**那一面**未达成**(见 5.3)。库指纹读数前后**逐字一致**(纯只读)。 + +### 5.3 ⛔ 未达成项(逐条具名 · ⛔ 不含糊) + +| # | 未达成项 | 现象 / 原因(实测) | 归属 | 下一步 | +|---|---|---|---|---| +| 1 | **A2「真实例进程内」面** | 目标实例 `dsh-cce6d1cd-…` 在 `dsh_instances` 中 `status = "stopped"`(平台为**按需启动**);本棒**未启动它** —— Manager 侧未找到实例启停路由,而 worker agent 的 `POST /launch`(`http://127.0.0.1:19100`,头 `x-dsh-agent-token`)**属控制面职责**,直调会绕过 Manager 的 `epoch/lease` 台账 ⇒ **状态漂移风险**(R11)⇒ 主动不做 | 部署面 / 控制面接口 | 用户下次访问门户时实例**自然启动**(届时自动加载 `0.5.1` + 新档位);**或**在门户点"启动实例"后由后棒补一次实例内读数 | +| 2 | **106(`w-106` / `106.54.21.172`)同步 + 合并重启** | `47` 与本机 ssh 106 **均 `Permission denied (publickey)`**(47 的 `known_hosts` 有该 IP,但无授权 key);47 亦无法直连其 `19000`(`overlay` 台账 `via: "relay"`)⇒ **无可用通道** | 部署面(凭据缺失) | **需用户提供 106 的 ssh 凭据或授权通道**;拿到后:投放共享层 `0.5.1` → 重启其 worker → 取代码面/运行面读数 | +| 3 | **A8 两机一致** | 47 = `0.5.1`(`store.js` md5 `62384d4d…`);**106 未取证** ⇒ ⛔ **不得写"一致"** | 同上 | 同 2 | +| 4 | **迁移工具与读路径口径相反**(新发现) | `tools/migrate-local-to-platform.mjs` 的 `SELECT *` **无 `ORDER BY`** ⇒ 平台折叠时保留 `id` 较小那份;而本地读路径 `ORDER BY id DESC LIMIT 1` 取 `id` 最大 ⇒ **同一行在两处的"权威版本"不同**(本次影响 2 组,见 §4.1) | **插件侧(迁移工具)** | 技术项:给该表的读取加 `ORDER BY id ASC`/或在 dup 时**显式选「读路径那一行」**;本轮**未改**(改它需重跑迁移并重新验收,超出本棒范围)⇒ 如认可,下棒处理 | +| 5 | **A6 旧库 sha256「与迁移前一致」** | 拍板① 授权删行 ⇒ 库 sha256 **必然变化**(`b37f8aab…` → `8d96b9f1…`)。**库文件在位、未被删、属主未变**;回滚素材=`.bak`(2,383,872 B)+ 全表 dump + 5 组原样留档 | 本棒(已授权的预期变化) | 判据按新口径理解即可;如需逐字回滚,用 `.bak`(`.backup` 产物是完整合法库) | + +--- + +## 六 A1–A9 判据表(命令 + 输出原文 + 退出码) + +| # | 判据 | 命令(节选) | 输出原文(节选) | 退出码 | 判定 | +|---|---|---|---|---|---| +| **A1** | 存量迁移一致性 | `ssh … remote-05-shadow.sh`(探针比对本地 SQLite ↔ 取数口) | `A1 [account_video_source] 本地键=34 平台行=34 onlyLocal=[] onlyPlatform=[] match=true`/`account_videos 362/362`/`hot_accounts 5/5` | `SHADOW_RC=0` | ✅ 达成 | +| **A2** | 三表读写 E2E | ① 取数口面=第 35 棒(三表 7 步读写全 200 · 6 负例全 `bad-args` · PG 残留 0)② 切档后面=`remote-07-a2.sh` | ② `READ [t] source=platform … rows=34/362/5 degraded=false => OK`;`TOTAL_WRITES=0`;`A2_DONE ok=true` | `A2_RC=0` | ✅(取数口面 + 切档后读路径面);⛔ **真实例进程面未达成**(§5.3-1) | +| **A3** | 结构改写面处置完毕 | `grep -rniE "\bJOIN\b" lib \| grep -v "\.join(" \| wc -l`;`grep -rn "LIMIT ? OFFSET ?" lib \| wc -l`;`grep -rnE "ROW_NUMBER\|OVER\s*\(" lib \| wc -l` | **157 / 8 / 10**(分布:`index.js 59`/`imports.js 34`/`store.js 12`/`data.js 1`/`workshop.js 11`…) | `0` | ✅ **本棒未改任何调用点**(只改 `canonicalJsonText` 实现内部)⇒ 第 34 棒「平台档路径零命中 + 三类具名豁免(`local()` 回调 / 本地专属分支 / 第四批 `rewrite_log`)」结论**逐字沿用** | +| **A4** | 表名对齐彻底 | `grep -rE "account_video_analysis" . \| grep -v /node_modules/ \| wc -l`;`grep -c "hot_account_ext" lib/host/mcn/store.js` | **0** / **10** | `0` | ✅ 达成 | +| **A5** | 已按表切档(三表) | `cat /.dsh/data-plane.json` | 三表均 `{"mode":"platform","writeMode":"platform"}`;合计 **7 表**;第四批三表仍 `local` | `SWITCH_RC=0` | ✅ 达成 | +| **A6** | 旧库备份在位 + 零数据损失可回滚 | `sqlite3 "$DB" ".backup …"` + `sha256sum` + `ls -la` | 备份在位 `2,383,872 B`;库文件**未被删**、属主未变;5 组原样留档(含正文) | `BACKUP_RC=0` | ✅(sha256 因**授权删行**而变 —— 见 §5.3-5) | +| **A7** | 移动端五条 | `stat` 改动面 | 本棒改动面 = `store.js`(23:15)/`tests/mcn-store.test.mjs`(23:15)/`package.json`(23:18)/`dist/0.5.1.tgz`(23:18);**`lib/client.js` mtime 09:47(早于本棒)⇒ 零改动** | — | ✅ **免跑**(未改 UI 代码,文件清单依据如上) | +| **A8** | 两机一致(代码面/运行面分开记) | `grep -m1 version /var/lib/dshs/bundled-plugins//package.json`(47) | 47 **代码面 = 运行面 = `0.5.1`**(`store.js` md5 `62384d4d…`=本机);**106 未取证** | `0` | ⛔ **不一致(如实记)** | +| **A9** | 红线零新增 | `grep -rEn "CREATE TABLE\|ALTER TABLE\|DROP TABLE\|better-sqlite3\|from 'pg'\|require\('pg'\)" . \| grep -v /node_modules/ \| wc -l` | **23** | `0` | ✅ 达成(与第 27/28/29/35 棒同值) | + +--- + +## 七 ⑦ 同步到仓库(用户 23:0x 授权) + +> 见 §8「收口记录」(提交 sha / 推送 / 对账读数在此补记)。 + +--- + +## 八 收口记录 + +见入口 `§0` 顶部对应行 + `$DOC/05-交接单/README.md §一` + 当日日志 `.workbuddy/memory/2026-09-25.md`。 + +### 8.1 域锁 +- **开工**:`bash $DOC/07-scripts/handoff-guard.sh --claim-exec "p36-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`(9 键)⇒ **`rc=0`**;复读断言(非 `grep`,直接读文件): + ``` + $ cat "$L/.locks/p36-exec-plugin-dataline/OWNER" + p36-exec-plugin-dataline + 开始:09-25 23:13 + 会话:ca862d96-5a1d-48d7-8009-163071566666 + ME:p36-exec-plugin-dataline + ``` + DOMAINS 复读 = 上列 9 键逐字一致。 +- ⚠️ **锁根不在工作区** —— 实测 `$WS/.workbuddy/locks/` **不存在**;真源 = `$DOC/05-交接单/.locks/`(同根另有 IM 线锁 `IM_…-4241281224`,23:46 起 ⇒ **归属他线,⛔ 本棒未触碰**)。 +- **释放**:见 §8.5 收尾行(`ME="p36-exec-plugin-dataline"`)。 + +### 8.2 R4 卫生(临时会话) +命令 `ssh bt-server 'bash -s' < $WS/tmp/p36/remote-09-clean-fast.sh`(`rc=0`): +``` +SESSIONS_BEFORE=8 +DELETED=5 +SESSIONS_AFTER=3 +POC_LEFT=0 +``` +⇒ 本棒(含前序棒遗留)以 `user_agent='poc-curl2'` 建的临时会话**已全部删除**,残留 0。 + +### 8.3 R10(实例属主) +``` +ROOT_OWNED_COUNT=0 # find "$UHOME" -user root -print | wc -l +NO_WAL_SHM # ls -a "$UHOME/.dsh/" | grep -E "wal|shm" ⇒ 无命中 +db owner=114801:114801 size=2383872 mtime=2026-09-25 23:33:21 +dp owner=114801:1002 size=678 mtime=2026-09-25 23:36:36 +``` +⇒ 全量遍历确认**零 root 属主残留**,实例文件全部归位实例 uid(`114801`)。探针(需平台权限)以 root 跑时必须 `DSH_HOME` 重定向 `/tmp`,本地库一律 `readOnly`。 + +### 8.4 tmp 与残留进程 +``` +TMP_P36_LEFT=0 # ls -d /tmp/p36* ⇒ 无 +NODE_E_BEFORE=5 → PKILL=ok → NODE_E_AFTER=0 +``` +⇒ 47 侧 `/tmp/p36*`(含 `p36-dshhome`/`p36-dshhome-real`/`p36.sid`)全清;**清掉 5 个挂死的 `node -e` pg 探针**(清后探针立刻恢复正常返回 ⇒ 确认上一棒的 hang 真凶是 pg 探针、**不是 `find`**)。本机留档在 `$WS/tmp/p36/`(保留 7 天)。 + +### 8.5 提交 / 推送 / 对账 / 锁释放 + +| 项 | 读数 | +|---|---| +| 提交 | 见下方「收尾记录」 | +| 推送 | `git push origin master`(工作区仓库 `git@work.alotbuy.com:maogeigei/dsh_shenxian_workspace.git`) | +| 对账 | 见下方「收尾记录」 | +| 域锁释放 | 见下方「收尾记录」 | + +#### 收尾记录 + +(待填 —— 本文件先按 §8.1–§8.4 读数提交一次;推送与对账读数随后以第二个小提交补记,避免"提交 sha 无法自指"的循环。) + +> ⚠️ **文档库侧的台账不入库**:`$DOC/05-交接单/` 被文档库仓库 `.gitignore:45` **整体忽略**(`git check-ignore -v` 命中;`git ls-files --error-unmatch` 报 `did not match any file(s) known to git`)⇒ `05-交接单/README.md` **未跟踪、不入库**。🔴 该台账**只存磁盘**(+服务器镜像),⛔ 不能靠 git 保全。本条**实测推翻**工作区 `MEMORY.md` 旧记「已跟踪的 `05-交接单/README.md` 不受影响」。 + diff --git a/接续入口_插件投放与分库线_20260922.md b/接续入口_插件投放与分库线_20260922.md index 59efa26..d7cf986 100644 --- a/接续入口_插件投放与分库线_20260922.md +++ b/接续入口_插件投放与分库线_20260922.md @@ -9,6 +9,20 @@ ## §0 状态(**最新一行 = 执行依据**) +- 🔴 **2026-09-25 23:10–23:5x|第 36 棒 · 执行棒 收口(`p36-exec-plugin-dataline`)—— ①–④ 全达成 · ⑤「47 侧切档」达成 · ⛔ ⑤「实例重启/106」未达成 ⇒ A8 如实记「两机不一致」;⛔ 未登记下一棒(等拍板)** —— + ① ✅ **修 `canonicalJsonText()` 键序敏感**:新增 `sortKeysDeep()` + 导出 `canonicalStringify()`(对象键**递归升序**、数组**保序**),三形态归一出口全改走它 ⇒ 单测 **67/67**(新增第 59 条)+ 本机等效证明 **10/10**(含「旧算法判不等 → 新算法判等」与「字符串形态零回归」四例)。⛔ 落库原文零改动。 + ② ✅ **出包 `0.5.1`**:`2,018,751 B`/**324 条目**/md5 **`56c17c7e41a18704922e18d75816da59`**;包内 `store.js` sha256 与本机逐字一致 → 上池 `200`(`compat.level=ok`)→ 台账 **`state=ready`** → 结构预演 `ITEMS=[]` → share **`200 action:"replaced" version 0.5.1`**;47 共享层 `store.js` md5 **`62384d4d…`=本机逐字一致**(`canonicalStringify_COUNT=4`);回滚素材 `0.5.0.tgz` 在位。 + ③ ✅ **删本地 5 条冗余副本(拍板①)**:**先双份留档**(`sqlite3 .backup` ⇒ 2,383,872 B/sha256 `ee75df4a…`;全表 dump 39 行/sha256 `fdfd8fa4…`;5 组原样含正文 `export-dup5.json` 542,624 B/sha256 `b0e560ca…`;本机副本在 `$WS/tmp/p36/`)⇒ 再删 `id = 40,28,29,31,32` ⇒ **39 → 34 行**、`KEY_DELTA lost=[] extra=[]`、`integrity_check=ok`;⛔ 库文件在位未删、属主未变、别的表一行未动。⚠️ **插件既有删除路径不适用**(`removeVideoSource` 按 `aweme_id` 删**整组** ⇒ 会误删保留行)⇒ 用最小 SQL + 四条 fail-closed 断言。 + ④ ✅ **影子读重取**:三表 A1 **34=34 / 362=362 / 5=5**(`onlyLocal=[] onlyPlatform=[]`)· SHADOW 三表 **`match=true`**、`degraded=false` · `account_video_source` **逐行内容「正文 34/34 · 分析 34/34 · 差异 0」(第 35 棒为 23/34 不等 ⇒ ① 的修法真机确认)** · `PACKED_LEFTOVER=0` · `TOTAL_WRITES=0`。 + ⑤ 🟡 **47 侧切档达成**:三表切 `platform`(读写同切)⇒ 实例档位 **7 表 platform**(**A5 达成**)· 第四批三表仍 `local` · 属主 = 实例 uid · 原文件已备份 `data-plane.json.bak-p36-1790350596159`。📌 **生效机制实测**:`dataPlaneConfig()` **每次调用读文件**(无内存缓存)⇒ 切档**运行时立即生效、不需要重启**。A2 切档后面:三表 `source=platform` 全 OK、`TOTAL_WRITES=0`。 + ⑤ ⛔ **未达成两项(具名)**:**(甲)** A2「**真实例进程内**」面 —— 目标实例 `dsh_instances.status = "stopped"`(平台**按需启动**);本棒未启动它:Manager 侧未找到启停路由,worker agent `POST /launch`(`127.0.0.1:19100`)**属控制面职责** ⇒ 直调会绕过 Manager 的 `epoch/lease` 台账(状态漂移,R11)⇒ **主动不做**。**(乙)** **106(`w-106` / `106.54.21.172`)同步 + 合并重启** —— 47 与本机 ssh 106 **均 `Permission denied (publickey)`**、47 亦无法直连其 `19000`(`overlay` 台账 `via: "relay"`)⇒ **无可用通道**。 + 📊 **A 判据**:**A1 ✅**(三表全一致)· **A2 🟡**(取数口面=第 35 棒 + 切档后读路径面 ✅;真实例进程面 ⛔)· **A3 ✅**(`157 / 8 / 10`;本棒**未改任何调用点** ⇒ 第 34 棒「平台档路径零命中 + 三类具名豁免」结论**逐字沿用**)· **A4 ✅**(`0 / 10`)· **A5 ✅**(7 表)· **A6 🟡**(库文件在位未删、属主未变;sha256 因**授权删行**必然变化,回滚靠 `.bak` + dump)· **A7 ✅ 免跑**(`lib/client.js` mtime 09:47,本棒改动面仅 `store.js`/`tests`/`package.json`/`dist`)· **A8 ⛔ 不一致**(47 代码面=运行面=`0.5.1`;**106 未取证**)· **A9 ✅ = 23(零新增)**。 + 🔴 **本棒动过生产**:47 共享层投放 `0.5.1` + 实例库**删 5 行**(已授权)+ 实例档位切 **7 表 `platform`**。⛔ **未启动/停止任何实例**、⛔ **未触碰 106**、⛔ **未改 `D:/github/dsh_shenxian/src/**`**、⛔ **未删库文件/未重建库**。 + 🔴 **新发现(未闭合 · 插件侧)**:`tools/migrate-local-to-platform.mjs` 的 `SELECT *` **无 `ORDER BY`** ⇒ 平台折叠时保留 `id` **较小**那份,而本地读路径 `ORDER BY id DESC LIMIT 1` 取 `id` **最大** ⇒ **同一行在两处的"权威版本"不同**(本次影响 2 组)。⚠️ **因此 ③ 的删除口径如实偏差**:5 组里有 **2 组删掉的是 `id` 较大(较新)那一行**(`aweme_id 7658997937279683855` 删 40 留 20;`7648947073131625905` 删 28 留 27),这 2 组差异 = 值内 2 字节**字面 `\n`**;若按字面「删 `id` 小的」,本地剩余内容与平台**不一致** ⇒ ④ 判据达不成。**该判定属内容取舍,清单已具名列出供复核**(交付件 §4.1)。 + 📌 交付件 = `$WS/交付物/MCN数据面接入-阶段二-切档与收口-20260925.md`(结论/①–⑤ 状态/A1–A9 命令+输出+退出码/**判定表与偏差说明**/未达成 5 条/卫生与域锁)。 + 🧹 **卫生**:R10 全程 `ROOT_OWNED=0`(收尾再复核)· 临时会话清理读数见交付件 §8 · 47 侧 `/tmp/p36*` 已清(本机留档留 7 天)。 + ⛔ **未登记下一棒(第 37 棒)** —— 剩余两项**要用户拍板**:**(i)** 106 的 ssh 凭据/授权通道;**(ii)** 实例何时启(用户下次访问门户即**自然启动**并加载 `0.5.1` + 新档位)。按规则「要用户拍板的,等拍了再新建接续会话」⇒ **等拍板**。 + - 🟢 **2026-09-25 22:44|第 35 棒后续 · 用户拍板两条(`p35-exec-plugin-dataline`)—— ① 本地 5 组重复 `aweme_id` 处置 = **A 案「删本地 5 条旧副本」**(不可逆 · 已授权)|② 插件包源码 = **B 案「不纳入版本管理」** ⇒ 改为定期打压缩包备份,**已落地**:固定目录 `E:/dsh-backups/plugin-src/`(`backup.sh` + 基线包 `mcn-suite-src-20260925-2246-v0.5.0.tgz`/6,065,179 B/330 文件/md5 `247dd145…`)+ 每周自动备份 automation `1e3c9070-a75d-4ae4-b81e-06e2eb20a5b4`(周日 03:30 · 内容摘要去重 · 保留最近 26 份)。🔴 **两条拍板已注入第 36 棒 prompt ⇒ ⑥ 可闭环、⑦ 可执行、⛔ 不再重问**。** - 🔴 **2026-09-25 22:5x|第 35 棒 · 执行棒 收口(`p35-exec-plugin-dataline`)—— ①–⑤ 全达成 · ⑥ 未全绿 ⇒ ⛔ 未切档 · ⑦ 整条留给第 36 棒 · 已登记第 36 棒** —— @@ -439,7 +453,12 @@ ## §2 现状(本会话已核实 / 已定稿,执行棒复核时**只读**) -### ⏭️ 本轮动作(**第 36 棒 · 执行棒 · 已登记 `23:10`** · 接续第 35 棒;上游 = §0 顶部 **22:44 拍板行** + 22:5x 收官行 ⇒ **两条待拍板已到手,条件步转无条件**) +### 🗃️ 本轮动作(**第 36 棒 · 执行棒 · ✅ 已收官 23:5x** —— 仅供追溯,⛔ 不要按它开工;**下一棒未登记(等拍板)**) + +> 📦 **第 36 棒已收官**(`p36-exec-plugin-dataline`):**①–④ 全达成**(修 `canonicalJsonText()` 键序敏感+单测 67/67 → 出包 `0.5.1`+上池+发布共享层 → 删本地 5 条冗余行 39→34 → 影子读**三表全 match、内容 34/34 差异 0**);**⑤ 仅「47 侧切档」达成**(**7 表 `platform`**,A5 达成;A2 切档后读路径全 OK)。 +> ⛔ **未达成 2 项**:A2「真实例进程内」面(实例 `stopped`,未启动 —— 直调 worker `/launch` 会绕过控制面台账,主动不做)|**106 同步+合并重启**(ssh `Permission denied (publickey)`,无通道)⇒ **A8 如实记「两机不一致」**。 +> 🔴 **③ 的口径偏差(务必先读交付件 §4.1)**:迁移工具 `SELECT *` 无 `ORDER BY` ⇒ 平台留下 `id` **较小**那份,与本地读路径(`id DESC`)**相反** ⇒ 5 组里 **2 组删的是 `id` 较大(较新)那一行**(删 40 留 20/删 28 留 27),否则 ④「内容全绿」达不成;清单已具名列出。 +> 📌 交付件 = `$WS/交付物/MCN数据面接入-阶段二-切档与收口-20260925.md`。⛔ **下一棒(第 37 棒)未登记** —— 剩余两项要用户拍板(106 通道 / 实例启动时机)。详版见 §0 顶部行。 > 📦 **第 35 棒(执行棒)已收官**:①–⑤ 全达成(处方 (a) 落码 + 单测 66/66 → 出包 `0.5.0` → 上池+发布共享层 → 加列迁移 2 条 `ADD COLUMN` → 回填 `local_id`);**A2(平台取数口面)达成**(三表 7 步读写全 200 · 6 负例全 `bad-args` · PG 残留 0);**⑥ 未全绿** ⇒ **停住不切档**,**⑦ 整条未执行**;🔴 根因两条(本地 5 组重复 `aweme_id` / `jsonb` 键序 × `canonicalJsonText()` 假阴性)⇒ 交付件 `$WS/交付物/MCN数据面接入-阶段二-B案落地与shadow读数-20260925.md`。