Files
dsh_ai1net_server/.workbuddy/memory/2026-09-20.md
T
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

96 KiB
Raw Blame History

2026-09-20

04:3x · IM 群组对话 —— 需求拍板 + 规划档案 142 落盘

  • 用户拍板 B 方案(人 + Agent 一次做齐),并给出两条决定形态的语义: ① 群聊消息 = agent 的上下文(不是只看到 @ 它那一条); ② agent 两种形态 —— 能力机器人(挂功能区、占独立成员位、被 @ 才答)/ 组员代理(组员开「待应答」后 @ 组员 = 其 agent 代答;组员与 agent 是一个整体 ⇒ 不占独立成员位)。
  • 追加硬要求(平台化):IM 群组不是单一场景功能 —— 原话「不同插件都要能接入,比如协作办公插件群、mud游戏群、跑团群等」 ⇒ 形成第二个关键判断:内核只做通用地基,七个扩展点(房间类型 / 消息载荷 / 成员角色 / 发言规则 / 能力机器人 / 事件订阅 / 房间内 UI)下沉到插件; 配 三条平台保护(插件不直连 DB · 插件故障不拖垮房间 · 能力吃预算)+ 两档传输(聊天档 按 107 口径 / 实时档 按 108 游戏口径 —— 两档不是同一套参数)。 ⚠️ 跑团要回合制发言、MUD 要低延迟,均与自由并发聊天不同 ⇒ 发言规则必须是插件可定义的,否则第一类新场景进来就要改内核。
  • 产出:04-调整方案/142-IM群组对话-需求基线与方案.md(规划态)+ INDEX.md 登记行;docs-audit.py RC=0 / 无 P0;新档 CR=0(纯 LF)。
  • 取证结论(跨会话可复用,勿重查):src/ 应用层 room|chat 零命中;schema.ts 11 个迁移无消息 / 房间 / 成员表; 用户间通道故意隔离(src/net/relay/server.ts:462);可复用 = relay ×2 / DIAL / PRESENCE / 一机一钥 / 网抽象 ops·u:<id>。
  • ⏳ 未拍板(档案 §六):首批场景优先级(办公群 / MUD / 跑团)· UI 落点(门户面板 vs 独立页)· 房间上限与发言预算数值(待压测标定)· 插件扩展开放边界 · E2EE 是否本期。
  • ⛔ 本轮零代码改动、未动服务器、未 commit / push;全局执行锁已释放。⏭ 下一棒 = 规划细化(出可执行交接单),automation 一次性排 04:41。

04:5x · IM 群组对话 —— 规划细化:五份可执行交接单落盘

  • 产出(全部在 dsh-server-docs/交接单/,纯 LF): IM群组-A-房间内核与DB.md(10,024 B)· IM群组-B-平台API与用户端IM-WS通道.md(8,409 B)· IM群组-C-Agent接入插件.md(9,283 B)· IM群组-D-插件SDK与扩展点契约.md(10,052 B)· IM群组-E-会话界面UI.md(8,033 B); 交接单/README.md §一 增补 5 行 + 1 段串行建议(git diff --numstat = 8 / 0,未触碰既有行)。
  • 验收:docs-audit.py RC=0 / 无 P0(改前基线同为 RC=0;对比无新增问题);新档 CR=0。
  • 串行建议:A → B →(C / D 可并行)→ E;除 A/B 前置于后三者外,C/D/E 文件不重叠,但并行度仍受全局执行锁约束。
  • 本轮新取到的锚点(跨会话可复用,勿重查): ① src/db/schema.ts 迁移到 v11 为止(MIGRATIONS 第 476 行起)⇒ IM 新表 = v12,且双后端(sqlite + pg 各一份 SQL); ② 平台路由 = src/web/server.ts:58–68 集中 import 11 个 routes/*.ts,无 im.ts; ③ 全仓只有 1 处 WS upgrade 处理点 = src/supervisor/proxy.ts:718(per-user 子域隧道)⇒ B 单必须在同一 upgrade 回调里按路径分流,⛔ 不改隧道语义; ④ 🔴 前置缺口(C 单第一件事):poc/business-plugins/lib/index.js 仅 21 行、server 侧不触达平台;能力全在 lib/client.js(4,001 行)且靠浏览器 session cookie 跨子域 ⇒ 实例内「服务端拨出」缺一条凭据通道(已在单里定为「平台生成 per-instance token → orchestrator 注入 env DSH_IM_INSTANCE_TOKEN + DSH_PLATFORM_IM_URL」,实例重启即换); ⑤ 实例 env 注入块 = src/supervisor/orchestrator.ts 第 760–780 行一带;测试布局 = test/*.test.mjs + node --test(构建产物 lib/)。
  • 未决项(⛔ 未替用户决定,已逐单显式标注):UI 落点(门户新面板 vs 独立页,档案 142 §六-1)· 首批场景优先级(办公群 / MUD / 跑团,§六-5)· E2EE 是否本期做(§六-3,会改协议形态;我在 D 单给了倾向 A=本期不做并列了优缺点)· 房间上限与发言预算数值待压测标定(§六-2,单内先给保守初值:房 2000 / 每 agent 每 10 分钟 10 条 / 静默 30 s)。
  • ⛔ 本轮零代码改动、未动 47/106、未 commit / push;全局执行锁已释放。⏭ next = 用户拍板(UI 落点 / 首批场景)后按 A 单开工。

08:1x · IM 数据面追加裁决(DB 选型 · 缓存队列 · 对象存储)→ 已写进 A 单 §九

  • 用户追加四问(多插件接入表名隔离 / 多领域·项目·会话组 / 主从与负载均衡 / 缓存与队列预留),随后再补一问(IM 会话产出各类文件 ⇒ 考虑接对象存储桶)。
  • 实测取数(本轮新取,跨会话可复用):package.json 依赖里与数据相关的只有 better-sqlite3 + pg;src/ 里 0 处 redis / ioredis / bullmq / amqplib/ cache 抽象 ⇒ 缓存与队列是空地,不是"已有待改造"。
  • 裁决(全为技术项,自决 · 已落 交接单/IM群组-A-房间内核与DB.md §九,标注为「设计约束,冲突时以本节为准」): ① 插件表隔离 = 命名空间前缀 p_<pluginId>_* + 内核 API,⛔ 不给插件直连 DB(前缀只解决物理隔离与归属); ② 多领域/项目/会话组 = 同一套房间/消息模型 + 维度字段(owner_scope · room_type · thread_id · project_id),⛔ 不按领域分表(否则新场景就要改内核); ③ 主从/负载均衡 = 保留 PostgreSQL(既有 PG13 + src/db/ 双后端可切)> MySQL > SQLite(不支持主从);⚠️ 读写分离按数据分类:元信息读可走只读副本,消息读必须走主/sticky(否则"刚发的消息看不见",违反游标补拉语义); ④ 缓存与队列 = 预留端口、不预置进程:队列先用 PG(SKIP LOCKED + LISTEN/NOTIFY),Redis 仅在「跨机扇出/presence 广播」实测成热点后引入(单实例 ≤128–256 MiB 硬前提,宿主内存本就紧:实例硬顶 1024 MiB / 宿主 1870 MiB);端口 = src/im/cache.ts / src/im/queue.ts; ⑤ 文件与对象存储 = 桶存内容、PG 存元数据(files 表带 sha256/bucket_key,⛔ 大对象不进 PG);S3 兼容(MinIO 自建或 OSS/COS 兼容端点)不锁厂商,端点/密钥走档案 140 的 config/platform.env(不入库、默认中性);上传 = 预签名直传桶(平台不经中转),下载 = 短 TTL 预签名(≤5 min)、需审计时走 /api/im/files/:id 代理;消息 payload 只存 file_id(保住扇出与两档传输口径);生命周期 = 内容寻址去重 + 桶 lifecycle(⛔ 不做同步删);权限复用房间 ACL;插件产出物走同一 files 面(⛔ 不各自建桶/凭据);转码抽帧不在内核(走后处理队列);端口 = src/im/files.ts,首版单桶多前缀 rooms/<roomId>/<yyyymm>/<sha256>。
  • 立单时机:文件面依赖 A 的 ACL 与 B 的接口面 ⇒ 等 IM 主链 A→B 跑通后另立 F 单;本轮只把约束写死(⛔ 未在 A 单里顺手实现,未改五单范围)。
  • 验收:docs-audit.py RC=0 / 无 P0;A 单 15,828 B / CR=0。⛔ 仍零代码改动、未动服务器、未 commit。

08:2x · IM 数据面追加(插件如何建表与管数据)→ 已写进 D 单 §九

  • 用户追问「插件如何建表以及访问和管理数据」⇒ 契约落在 交接单/IM群组-D-插件SDK与扩展点契约.md 新增 §九「插件数据面契约」(A 单 §九-1 加了指针互指;D 单那行台账也补了说明)。
  • 裁决(技术项,自决): ① 先判落点再建表(沿用集群化分层判据):跟房间走的业务数据 ⇒ 平台库 p_<pluginId>_*;只属于某用户自己的 ⇒ 实例 home(走 UserFs);本机运维 ⇒ Worker 本地库。⛔ 同一份数据不双写(双写=脑裂)。 ② 建表 = 声明式,⛔ 零裸 SQL:中性类型集合(text/integer/bigint/real/boolean/json/timestamp/uuid)由内核翻译成 sqlite + pg 两套 DDL ⇒ 同时保住「双后端可切」与「插件不直连 DB」;内核建表时自动补前缀/room_id/审计列。⛔ 禁自定义类型、触发器、存储过程、跨前缀 JOIN。 ③ 迁移只增不减:只允许加列(带默认值)/加表/加索引;在启用插件时执行,任一步失败 ⇒ 拒绝启用(不做半迁移)。 ④ 访问走内核数据 API(table().insert/update/delete/find/count + 游标分页):room-scoped 表自动注入房间 ACL(复用 canSee);跨前缀/内核表只能经内核 API;事务仅限同前缀多表;写与"刚写就读"走主库。 ⑤ 管理:三重配额(总行数/单行字节/每房行数,超限给明确错误码,⛔ 不静默丢/截断)· 插件写吃与 agent 同族预算 · DDL 与超阈值写记入内核表 plugin_data_audit · 卸载默认保留 30 天,DROP TABLE 属不可逆 ⇒ 必须 admin 显式确认 + 先出清单(⛔ 不做"卸载即删")。 ⑥ 数据面异常(超时/超配额/迁移失败)⇒ 降级为普通聊天,房间消息流不受影响(142 §3.6 保护 2)。
  • D 单新增验收断言 8–13:零裸 SQL / 双后端可建 / 越权拒绝 / 房间 ACL 为 0 行 / 迁移拒绝删列改类型 / 卸载保留与 DROP 确认。
  • 验收:docs-audit.py RC=0 / 无 P0;A 单 16,017 B、D 单 14,468 B、README 164 CR(均为纯 LF 新档 / 既有 CRLF 未变)。⛔ 仍零代码改动、未动 47/106、未 commit / push。

21:5x · 「数据库专区」成稿(抢锁失败 ⇒ 未落文档库,全部暂存工作区)

  • 🔴 抢锁失败(R9 停手,未碰文档库任何文件):交接单/.exec-lock/OWNER = 序47执行棒2,开始 09-20 21:27、doing 位为空(与记忆里那支 automation 90049d4c… 一致)。双证:handoff-guard.sh --claim-exec 退出码 1 + Python 直读锁目录。 ⚠️ 环境坑(新增):本轮 bash …/handoff-guard.sh 的中文输出变乱码(UTF-8/GBK),且 coreutils 间歇缺失(head/tail/grep 报 command not found,PATH 被重置 ⇒ 会误触 WSL 阻断)⇒ 判锁改用 Python 直读 .exec-lock/OWNER(ls/python 正常,管道到 head 不可靠)。
  • 🔴🔴 本轮最重要的实测发现(会误导后续执行者,必须优先修正):src/db/schema.ts 迁移已到 v13 —— v12 = overlay device ledger (序㊻ 步骤6 / S3)(新表 overlay_devices)、v13 = session/device kind + session cap index (序㊼ S0)。 ⇒ IM 房间三表必须取 v14;而 交接单/IM群组-A-房间内核与DB.md 现写「新增 v12」,且其「只读前置 #1」原写「若已 >11 ⇒ 说明别人先做了 ⇒ 停手回报」——这句现在会误判(v12/v13 是覆盖网络线占的,不是 IM)。
  • 成稿(暂存,未入文档库):E:/ProgramData/AIProject/ai1net-dsh-server/tmp/数据库专区_草案_20260920/ 00-专区入口.md(入口 + 五条判据 + 迁入清单)· 01-接入指南-各种情形.md(六种接入情形:内核表 / 插件表 / 实例 home / Worker 本地库 / 大对象桶 / 缓存队列)· 02-表结构台账与迭代机制.md(v1–v13 台账 + 内核表全集 + 三条硬纪律 + 四步跨版本 + 评审门禁 + 台账维护规则); 另 tmp/插件数据面规范_草案_20260920.md(= 专区 03-插件数据面规范.md 的成稿)。
  • 迁入清单(锁释放后一次做完,见 00-专区入口.md §🔴):抢锁 → mkdir dsh-server-docs/数据库/ 落 4 档(新目录不需占号)→ INDEX.md 登记 → 修正 A 单(v12→v14 + 改掉"停手回报"那句误判判据 + §五-1 的 SQLITE_V12/PG_V12→V14) → D 单 §九 加指针 → 删 tmp 草案(不留双源) → docs-audit.py RC=0 + 纯 LF。
  • ⛔ 本轮仍零代码改动、未动 47/106、未 commit / push;未登记后续 automation(锁在别人手里 + 排 automation 属花钱项,等用户口径)。
  • 🔑 跨会话可复用取数:grep -nE "^ \{ version:" src/db/schema.ts = 版本清单(现有 13 条);grep -noE "CREATE TABLE( IF NOT EXISTS)? [a-z_]+" src/db/schema.ts | sort -u = 表全集(内核保留名 13 张 + schema_migrations)。

22:4x · 迁入做成「一条命令」(锁仍未释放,文档库零改动)

  • 🔴 锁竞争第 3 次:MCN线0.3.13棒 自 22:10 持锁至今(22:39 仍在,已 30 分钟)⇒ 两轮 --claim-exec 与一次实跑均抢不到 ⇒ 按 R9 停手,文档库零改动。
  • ✅ 本轮产出 = 可一键执行的迁入脚本:tmp/迁入数据库专区.py(已三次 --dry 全绿,实测抢不到锁时零改动退出并打印 OWNER)。 它一次做完 7 件事:① mkdir 数据库/ 落 4 档(去掉草案头、加「已落文档库」状态行、LF)② 把 A 单 v12→v14(含改掉「若已 >11 ⇒ 停手回报」那句误判,改为「v14 被占 ⇒ 取下一个空号继续,不要停手」,并写明 v12/v13 = 序㊻/序㊼ 的 overlay_devices 等、与 IM 无关)③ D 单 §九 加专区指针 ④ INDEX.md 二、全量清单 字节级插入一行 ⑤ 打印各档字节数与 CR 数 ⑥ 跑 docs-audit.py 打印 RC ⑦ finally 释放锁。
  • 两个"防坑"设计(写脚本时踩到/主动做的):① 版本号现算(schema.ts 里 max+1)而不是写死 —— 序47 正在改代码,写死必然过时;② INDEX.md 用字节级插入(该文件 CR=5/LF=269 混排)而不是整文件 Write 覆盖 —— 遵循 README §三「共享文件禁整文件覆盖」。
  • ⚠️ 脚本首个版本曾断言失败:\bV12\b 匹配不到 SQLITE_V12(_ 是 word 字符 ⇒ 无词边界)⇒ 改为无边界 V12。教训:跨 _ 的标识符不能用 \b。
  • ⏭ 下一步 = 等窗口跑那一条命令:"<py>" "E:/ProgramData/AIProject/ai1net-dsh-server/tmp/迁入数据库专区.py"(跑前可先 --dry)。最佳窗口 = MCN 棒收口后 5~8 分钟(其下一棒按纪律才排)。⛔ 仍零代码改动、未动 47/106、未 commit。

22:5x · ✅ 数据库专区已迁入文档库(锁一释放就执行完毕)

  • 锁窗口:22:46 复查发现 MCN线0.3.13棒 已释放 ⇒ 一键脚本抢到锁 → 落库 → 释放。
  • 产物(文档库新目录 dsh-server-docs/数据库/,全部纯 LF): DB-00-专区入口.md(4,835 B) · DB-01-接入指南.md(6,588 B) · DB-02-表结构台账与迭代.md(6,737 B) · DB-03-插件数据面规范.md(9,625 B); A 单已改 v12 → v14(含改掉会误判的"停手回报"判据、写明 v12/v13 = 序㊻/序㊼ 的 overlay_devices 等);D 单 §九 已加专区指针;INDEX.md 二、全量清单 字节级 +1 行。
  • 最终验收:docs-audit.py RC=0 / 无 P0;INDEX.md CR=5 未变(行尾零扰动);专区四档 CR=0;全局锁已释放。
  • 🔴 两条新踩坑(跨会话必记): ① 新目录下的文档不能用 NN- 文件名前缀 —— docs-audit.py 的正则是 ^(\d+[a-z]?)-(只看文件名),01-/02-/03- 会被当档案编号 ⇒ 与 04-调整方案/01..03 撞号 ⇒ RC=1。正解 = 用非数字前缀(本次改 DB-00…DB-03),标题里可继续保留序号(标题号 ≠ 文件名号 只在两边都解析出编号时才比较)。 ② \b 匹配不到 SQLITE_V12(_ 是 word 字符 ⇒ 无词边界)⇒ 跨下划线的标识符替换不要用 \b。
  • 清双源:tmp/数据库专区_草案_20260920/ 与 tmp/插件数据面规范_草案_20260920.md 已移入 D:/github/dsh_shenxian/_中间产物_待清理/(⛔ 未直接删,可回溯);工作区 tmp/ 只留两个脚本:迁入数据库专区.py(⚠️ 一次性,重跑会在 A 单断言处安全中止)、修正数据库专区命名.py。
  • ⏳ 残留小项(未做,等用户口径):① scripts/docs-manifest.py 机读清单未刷新;② 未 scp 到服务器镜像 /opt/dsh/docs;③ 未 commit / push(红线:未明确要求不提交)。⛔ 全程未动 47/106 服务、未改一行代码。
  • 📌 技能待补(下一步做):把上述坑①(新目录文档命名与 audit 编号规则)补进技能 dsh-knowledge-upkeep(本轮上下文已近上限,未做)。

22:5x · 收尾收官(清单 / 技能 / 服务器镜像)

  • ✅ 机读清单:scripts/docs-manifest.py RC=0 —— 文档 213 份 / 2,234,077 字符(档案 142 份;分层 hot 6 / warm 61 / cold 50;根级文档 71)⇒ 已含新 数据库/ 目录。
  • ✅ 技能补坑(三处 md5 一致 = 53d73df7):dsh-knowledge-upkeep 追加「新增目录的文档命名 vs docs-audit 编号」一节 —— 本机 .workbuddy/skills/ + 文档库 skills/ + 服务器镜像 /opt/dsh/docs/skills/(第三处本轮已 scp 并对账)。
  • ✅ 服务器镜像:/opt/dsh/docs/数据库/(新建目录 700)落 DB-00~DB-03 四档 + 交接单/IM群组-A-房间内核与DB.md、IM群组-D-插件SDK与扩展点契约.md 共 6 档 scp 全部 ✓;权限 = 文档 600 / DB-00-专区入口.md 644(README 类)。
  • ⚠️ INDEX.md 未推(主动跳过):远端 md5 8350cbc3 ≠ 本地 b9c7b056 ⇒ 按 README §三-0-⑤「防迟到的推送覆盖别人的新版本」只报不动,留人工对账(这是本轮唯一残留动作项)。
  • ⏳ 仍未 commit / push(红线:未明确要求不提交)。⛔ 全程未动 47 的生产服务(只写了 /opt/dsh/docs 镜像目录)、未改一行代码。

08:0x–08:5x · MCN 工作台「点了没反应」+ 用户设置切语言「没反应」→ 已修复并真机验收(档案 143)

  • 用户:「admin账号dsh会话 点击左侧 MCN工作台入口 没有反应,找到原因修复后把MCN任务会话入口的位置改到MCN工作台 header区域(用图标即可)」+「设置选项中 用户设置里面切换 语言还是不起做用」。
  • 两条同源:dsh 0.1.5-rc.1 改了插件依赖的老契约(不是用户操作、也不是"改不生效"): ① 对话区槽位名 conversation → main.conversation ⇒ 插件 querySelector('[data-slot="conversation"]') 实测恒 null ⇒ 面板宿主 pageHost 一直 null、portal 不渲染 = 点了没反应。 ② ctx.locale.getLocale() 由「返回 id 字符串」变成「返回快照对象 {active,locales,revision}」⇒ String(obj).split("-")[0] = "[object Object]" ⇒ 受控 <select> 匹配不上任何 option(浏览器显示第一项 English,而界面本来就是中文)⇒ 选「中文」看着无变化。
  • 改动:插件源 dsh-plugin-mcn/lib/client.js(槽位双名自适应 + 配套 CSS;「MCN任务会话」从左侧栏搬进工作台 header 图标、V1_ICON_INNER 加 chat、面包屑补名)⇒ 重打包 0.3.11(md5 0b84b3bc…);poc/portal-entry/lib/client.js 的 currentLocale() 兼容两形态 ⇒ 0.5.6。
  • 顺手修掉构建链三处「一构建就负向回归」:CLIENT_SOURCES/HOST_SYNC 仍列着已摘除的 dsh-plugin-mcn-schedule(会把计划任务装回来)|源文件里 v0.3.10 的「自动任务」菜单摘除编辑丢了(死链)|pack 未排除 tgz(1.9MB 历史包会被打进新包)。⇒ 重打包后与线上旧产物 diff = 9 hunk / 51 行,全部为本次改动。
  • 真机验收(agent-browser + mksess 临时会话):点工作台 ⇒ #/mcn + [data-mcn-split]/[data-mcn-panel] 出现;header 会话图标 ⇒ 切到 MCN任务会话 视图;左侧栏已无独立会话行;语言行初始 zh(修前显示 en)、往返切换 lang 跟随、settings.yaml 落 locale: preference: zh。平台侧 verify-portal-entry.mjs 28 条全绿(新增「快照对象取 .active」断言)。
  • 未覆盖:guest 实例在 w-106 ⇒ 其 portal-entry 仍 0.5.5、mcn-suite 未装(47 侧 NO_PROFILE 跳过;档案 120/138 的集群化通用缺口)。
  • 收尾:测试会话已删、服务器 /tmp 暂存件已清、档案 143 + INDEX 登记 + 镜像 md5 对账一致;未 commit / push。
  • 工具坑(已记进档案):agent-browser 前台会被沙箱 SIGTERM(run_in_background 起一次守护后,后续加 > 文件 2>&1 不走管道即可前台);set headers 注 Cookie 无效 ⇒ 用 eval document.cookie 注入;手动跑平台脚本须带 /etc/dshs.env + dshs.service.d/*.conf 的 Environment=。

09:1x–09:5x · guest 补齐 + 规模化方案(档案 144)

  • 用户:「guest的也处理,后续几百上千个用户的时候,遇到种类问题如何处理,有没有更好的方案」。
  • guest 已补齐:106 上 @dsh-local/portal-entry 0.5.5 → 0.5.6(pnpm add 成功、dsh.profile.bundles 含本包、包内 client.js 命中 v = v.active), 实例停过后由平台按需拉起;浏览器实测:初始态 lang=zh-CN 且下拉 zh(修前恒显示 en)、切 English ⇒ lang=en + 下拉 en(一致)。 ⚠️ dsh-plugin-mcn-suite 在 106 仍未装(本来只有 admin 装,非遗漏)。
  • 新基元:scripts/install-plugin-for-user.cjs —— 机器无关 / 幂等 / 包名与版本从 tgz 里读 / --expect 不符退 2 / 失败不静默; 已用于 guest。它是「几百上千用户」编排的原子动作(Manager 侧按 Worker 分组后逐机跑同一条命令)。
  • 方案(档案 144,待拍板选档):真实瓶颈不是耗时 —— 单用户 pnpm add 秒级 ⇒ 1000 用户串行约 1 小时级、按 Worker 分组并行是分钟级; 真正的问题是四条:① 编排脚本在本机枚举用户 ⇒ 迁到别的 Worker 的用户被静默跳过(guest 就是这么被漏的)② 无版本台账 ③ 每用户一份拷贝 + 无版本协商 ④ 契约失效零报错(档案 143 两条真因都不抛异常)。 推荐:必装插件从「每用户安装」→「Worker 共享只读插件层 + 每用户只留开关」(与 bundled-skills 同族,沙箱 scope 里已有 --ro-bind-try 先例)⇒ 升级从 O(N) 降到 O(1) 份内容; 顺序 = P0 台账(PG)+按 Worker 分组编排+三态输出(半天~1 天,立刻止血)→ P1 契约哨兵 + 失效可见化 → P2 共享只读层(需先 PoC:dsh 能否解析 node_modules/<pkg> 软链 / ro-bind 白名单 / 多版本策略)→ P3。 未建接续棒(要用户选档,选完再出孪生交接单)。
  • 收尾:测试会话已删;106 与 47 的临时件已清;档案 143/144 + INDEX + 镜像 md5 对账一致;未 commit / push。

09:3x–09:5x · 用户追问「插件共享是否让不同用户互访数据、安全性下降」→ 已答并写入档案 144 §五

  • 结论:共享的是「不可变代码」,不是数据;隔离靠 uid + bwrap,不靠"每人一份拷贝"。 实测(47):users/<uuid> = drwx------ + 属主=该用户 uid(/var/lib/dshs 与 users/ = 711 root)⇒ 用户 A 的进程连 B 的目录都进不去; 实例 scope 里 --tmpfs /var/lib/dshs + --tmpfs /var/lib/dshs/users(别人的路径在沙箱内根本不存在)+ --bind <uuid>/tmp /tmp(/tmp 每用户私有)
    • --ro-bind-try /var/lib/dshs/bundled-skills(已有"共享只读层"先例,就是 ro 挂的)。 插件自有数据(MCN 的 home/.dsh/mcn-plugin.db)属主即该 uid ⇒ 共享代码后仍每用户一份。 另:不是"一个共享进程服务所有用户" —— 每用户仍是各自 uid 的独立实例进程,只是同一份文件被多个进程只读加载。
  • 共享化真正的利弊:更好 = 今天插件副本在用户 home 里用户可写(能改自己的插件代码),共享层 root:root 0755 + ro-bind ⇒ 插件代码用户不可篡改(增强); 更差 = 爆炸半径 1 → N(共享层被投毒/误投会同时影响所有用户)——这是唯一真实新增风险。
  • 四条红线(写进 144 §五):① 只放不可变代码/静态资源,⛔ 绝不放 DB/缓存/日志/上传物;② 沙箱内必须 ro-bind + 宿主 0755 root、用户不可写(rw 绑定比互读数据更严重); ③ 版本目录不可变 + 只走 admin 投放 + sha256 清单 + 定期完整性校验;④ 插件数据落点仍按既有判据(跟用户走 ⇒ 实例 home 且 host 半边走 UserFs;平台级 ⇒ 平台库带前缀+ACL),⛔ 禁写宿主机固定路径。 附带:共享后灰度/回滚更容易(改指向即可,不必再跑 N 次 pnpm)。
  • 同步:144 §五 已入文档库 + 镜像(md5 一致);原「待拍板」顺延为 §六。

11:0x–11:2x · 用户追问「共享插件用起来产生的数据 / 生成文件 / 日志 / 临时文件放哪」→ 写入档案 144 §六

  • 一句话:共享层只放永不变的东西(代码 + 随包静态资源);一切运行期可变状态按判据落到每用户自己的目录或平台库,⛔ 不进共享层。
  • 先分"三个半边"(决定能写什么):client(浏览器,只有 localStorage,要落盘必须调 host)/host(实例进程,uid=该用户 ⇒ 能写自己 home / ws / /tmp;⛔ 写不进共享层与别人 home)/平台(dshs 进程 ⇒ 写平台库与 /opt/dsh/{state,backups},⛔ 直写用户 home 必须走 UserFs)。
  • 五问判据:① 跟用户走 ⇒ <userRoot>/home(权威)② 用户要看/下载 ⇒ <userRoot>/ws(可见;⚠️ 可见面≠权威面,不双写)③ 全平台一份 ⇒ 平台库 PG(插件前缀 + ACL)④ 可丢的中间物 ⇒ 实例 /tmp(每用户私有)或 home 下 cache ⑤ 运维/审计 ⇒ /opt/dsh/{state,backups}。
  • 实测(47 admin 实例):home/.dsh/mcn-plugin.db(正确落点=DSH_HOME)· home/mcn-plugin.db(旧写法残留,正是"落点没按判据"的历史垃圾)· home/.dsh-biz-plugins.log + home/.dsh-poc-portal-entry.log(平台 ensure 脚本的 per-user 日志,同族做法)· home/.node-compile-cache/(可丢缓存)· ws/{mcntimo,mcnworkspace}(产物)· 沙箱 /tmp = --bind <userRoot>/tmp /tmp(每用户私有)· 实例进程 stdout/stderr 归 systemd/journal 按 unit 采集。
  • 两条铁律:① 可变状态一律不进共享层(ro 绑定写不进,设计也不允许);② ⛔ 不写宿主机固定路径(/var/lib/dshs/logs/x.log、/tmp/plugin-y)——多用户写同一路径 ⇒ 互见 + 冲突 + 无法按用户清理。
  • 迁移/备份连带:权威数据(home)+产物(ws) 都在 <userRoot> ⇒ 只搬它(档案 120 流程不变);缓存与 tmp 不参与语义;共享层只是"代码",用户数据一个字不用碰。
  • 同步:144 §六 已入文档库 + 镜像(md5 b48c97b5… 本地/远端一致);原「待拍板」顺延为 §七。

14:3x 桌面线对接单核对(覆盖网络线 · 平台侧只读取证,未改任何文件)

来单:E:\ProgramData\AIProject\ai1net-dsh-desktop\docs\对接单_桌面端接入覆盖网络_给平台会话_20260920.md(桌面线第 5 棒卡"凭据签发")。 问的事:加入覆盖网络是否必须凭证、为什么。答:必须,且绕不过;但"凭据 ≠ 账号/注册/登录"。

实测取证(47 · 只读):

  • 🔴 纠正来单 §2.3 ①:47 的 relay/dshs env 都没有 DSHS_OVERLAY_SIGNER_PUBKEYS;现网走的是 DSHS_OVERLAY_SIGNER_SET_FILE=/etc/dshs/overlay-signers.json(SignerSet,由 DSHS_OVERLAY_ROOT_PUBKEYS 验)。
  • 签名者公钥 hex = bad464dfd53048efe7b8531029b3030eda49bc12930703d3a2a8f60e3a7daddf(来源 /etc/dshs/overlay-signer-key.pem.pub,64 hex)= SignerSet 里那一把。
  • relay 运行参数:DSHS_OVERLAY_REQUIRE_IDENTITY=1(强制身份,配不全就起动即抛)· DSHS_RELAY_DIALERS=ops:manager(白名单只有 manager)· 吊销清单 /etc/dshs/revocations.json。
  • 注册表 /var/lib/dshs/overlay/nodes.json = {"version":1,"nodes":{}} 空 ⇒ invite/join 通道从未被走过,现网 manager 走的是老的手工 grant(/etc/dshs/node-manager.grant.json + dialer drop-in)。
  • 网名 = ops(DSHS_OVERLAY_NETWORK_ID=ops);目录键与签名者键是两把,⛔ 不合并。
  • 平台仓 HEAD 1242d07,lib/net/relay/ 与 scripts/overlay-node-*.cjs 干净(未提交改动全在 dsh-server-docs/ 与 poc/portal-entry/,与本次无关)⇒ 桌面线可直接用。

为什么必须凭证(三条技术判据):① 接入面在公网 ⇒ 默认拒绝,否则中继可被白用/当跳板;② 节点无账号体系(一机一钥、私钥不出机)⇒ "谁被允许"只能由控制面在签发时写进可离线验证的凭据;③ 短时效 30min + nonce 原子占位(O_CREAT|O_EXCL)⇒ 一张邀请恰一台机器可用。

待办:桌面线喊跑即签 issue-invite(签方私钥 0600 在 47);derive --apply + relay 重启属平台侧窗口动作。

15:0x 追问「50w 客户端怎么发凭证 / 登录注册留着干啥」(同一条线)

三条新实测(都影响结论):

  • 🔴 invite/join 通道不产 grant:grep grant scripts/overlay-node-admit.cjs = 空;registry 只产 DSHS_RELAY_DIALERS 白名单(hostId 粒度)。而 47 relay 是 REQUIRE_IDENTITY=1,服务端走 verifyPeerGrant(grant, grantSig) ⇒ invite 通道的节点即使进了白名单也会被拒,必须再补一张 overlay-keyring.cjs issue-grant 签的 grant。桌面线对接单缺这一步。
  • 🔴 /dshs-overlay/join HTTP 端点未实现:grep "overlay/join" src/ = 空。GET /dshs-overlay/bootstrap 是地址目录(可轮换种子),不是签发入口;overlay 管理端点全 requireAdmin。⇒ 现状只有"离线申请单 + 人工 CLI"。
  • 🔴 两条机制不可扩展到 50w:① 白名单落点是单个 systemd drop-in 的 Environment=DSHS_RELAY_DIALERS=网/host,…(deriveDropIn)⇒ 逐设备列名字不可行;② 注册表 nodes.json 是单 JSON 全量读写(loadRegistry/saveRegistry)。

结论(口径):invite 是"几十台服务器节点"的运维通道;每设备的真实凭据是 grant({network,hostId,nodeKey,issuedAt,expiresAt},可设过期、可按 hostId/nodeKey 吊销)⇒ 规模化形态 = 设备首次上线带用户登录态调平台签发接口 → 在线签名者当场签 grant;50w 张凭证全靠自动签发,无人工。登录注册正是这条链的鉴权与授权来源(哪台设备属于谁、进哪张网 ops/u:<租户>/具名网、额度),所以要留,且是规模化前提。真正瓶颈不在签发(一次 Ed25519 签名),而在 relay 连接承载(50w 长连接 ⇒ 必须集群分片,量级估算待实测)。

15:1x 拍板落地:中继路线 C+A + 设备登录接入(序46 已立单)

用户拍板原话:「c + a 后续再考虑b 沉淀文档,改造功能支持设备登录接入网络,然后再对接文档中写明接入方式」⇒ 路线 C(直连打洞优先、中继兜底)+ A(relay 集群横扩);B 分层中继后续再考虑。

三份产物(本轮已落):

  1. 决策与改造方案 = 工作区 docs/覆盖网络/覆盖网络_设备登录接入_决策与改造方案_20260920.md(§2 现状取证表 + S0–S4 改造项 + 容量估算 + §6 风险)。
  2. 交接单(执行载体)= dsh-server-docs/交接单/覆盖网络-序46-设备登录接入网络.md(8 段:目标/只读前置/范围/决策点/步骤/回滚/回报/依赖)+ 已登记 交接单/README.md §一。
  3. 对接文档 = 桌面线 对接单_桌面端接入覆盖网络_给平台会话_20260920.md —— §8 五项回填 + 追加 §10(10.1 签名者来源纠正 · 10.2 补 grant 步骤 · 10.3 设备登录接入方式 · 10.4 路线口径)。

本轮新增硬事实(都写进方案 §2,⛔ 别凭记忆复用):

  • 🔴 relay 准入 = 两道独立证明:grant+grantSig(受信签名者签)且 nodeSig(证明握有私钥);REQUIRE_IDENTITY=1 时缺任一即具名拒。
  • 🔴 invite 通道不产 grant(grep grant scripts/overlay-node-admit.cjs 空)⇒ 桌面线第 5 棒必须补 overlay-keyring.cjs issue-grant,否则只进白名单也会被拒。
  • 🔴 /dshs-overlay/join 端点未实现(grep 空)⇒ 只能走离线 --out 申请单;GET /dshs-overlay/bootstrap 是地址目录、不是签发入口。
  • 🔴 不可扩展的两处:dialers 逐 hostId 名单(构造时定型、落 drop-in 的单个 Environment=)+ nodes.json 单 JSON 全量读写 ⇒ 设备不进 registry,台账进 PG;准入改「网级放行 + 逐设备 grant」。⛔ 不把 dialers 改成运行期可变(那是刻意的安全判据)。
  • 短租约(默认 24h,键 DESKTOP_GRANT_TTL_HOURS)+ 停发即失效 ⇒ 吊销清单只装紧急封禁。

接续:已按「收口 + 5~8 分钟、同一时刻只挂一个」登记 序46 执行棒(一次性)。⛔ 本轮未动 47 任何配置、未签 invite、未跑改造。


序46 执行棒 · 步骤 1 收口(15:18–15:37 · 验收通过)

做了:把 u:<tenant> 租户网端到端跑通(离线通道)+ 原始读数落盘 tmp/seq46/readings/01–18*.txt。租户取 users.id = u:2ade6411-927c-49cb-b811-391356263dcd,设备 hostId = d-<userId>-<pubkey 前 8 位> = d-2ade6411-…-a59404a2(§④ 规则)。 改了 47 的三处(均有备份/可回滚):① relay 手写 drop-in dialers.conf 改成「ops:manager + u:<tid>/<hostId>」并 restart dshs-relay(备份 dialers.conf.bak-seq46-20260920-152837)② /etc/dshs/relay-keys.json 加一条(3→4)③ 控制面注册表 nodes.json 加 1 条 approved(原为空表)。⛔ 未动 relay 的 REQUIRE_IDENTITY/签名者/吊销,⛔ 未动官方 dsh 主程序。 验收:derive 打印含该网桶 ✅|relay 日志 AUTH OK host=u:…/d-… session=24b0eab0741edaeb ports=[] ✅|ops 网零影响(重启后 manager/w-106 自动重连,refused=0)✅。

三条硬发现(⛔ 后续棒直接照用,全写在 单 §⑨)

  1. 🔴 设备形态 = 不声明端口的拨号方(relay 判据互斥:dialer-must-not-declare-ports / no-ports)。main.js --client 强制 --ports ⇒ 表达不了设备形态(实测被具名拒 9 次)⇒ S1 桌面线必须用 RelayClient({ports: [], dialer: true, identity})(Manager 拨号通道同款装配)。
  2. 🔴 dialers 桶 = 拨号方名单 ⇒ S2 若用网级通配 u:<tenant>/*,整张租户网都被判为拨号方 ⇒ 该网内不能有声明端口的节点。
  3. 🔴 derive --apply 只表达一张网,且落点 50-overlay-dialers.conf 与手写 dialers.conf 抢同一个 env 键、字典序上还被压掉 ⇒ 真跑会把 ops:manager 丢掉且连租户网也不生效(静默失效)。本轮没用 --apply,改合并写手写 drop-in。⇒ S2 的前置:先解决"多网合并派生"。
  4. ⚠️ 47 上 /opt/dshs/scripts/overlay-node-admit.cjs、overlay-keyring.cjs 与仓库 HEAD md5 不一致 ⇒ 本轮用「HEAD 脚本 + 临时根 /tmp/seq46/cp(lib 软链 /opt/dshs/lib)」跑,⛔ 未改 /opt/dshs;S0 上线前建议同步。

停手位置:步骤 2–7 未做(本轮明令"做完即停")⇒ §① 终态未达成(现网租户网 dialers 仍是逐设备一条,"不依赖逐设备名单"未验)。测试拨号器已停(不留常驻进程);重拨命令在 单 §⑨。

15:46 用户令「继续任务直到功能完成」+ 上下文预算告警(184k token)⇒ 按钩子处置:开新会话接续

没在本会话继续改代码(理由:本会话历史已 184k、每轮全量重发;此刻开新会话起点约 5 万 ⇒ 单轮成本降到约 1/12;且半成品留在工作树会给下一棒制造脏基线)。⇒ 按 dsh-auto-handoff-chain §3.1.1 登记唯一一个接续棒:

  • 执行棒 2(一次性,id 8be929a7-24d8-4b6f-ae97-63f13766a024,scheduledAt 2026-09-20T15:52 = 收口 + 6 分钟):只做 §⑤ 步骤 2(S2 网级放行),可顺带跑步骤 3/4 反证;⛔ 不碰步骤 5/6/7。prompt 已自带四条既成事实 + 三条硬约束 + 单内 §⑦ 收口要求,并写明"收官再登记下一棒"(同一时刻只挂一个)。
  • 登记前已 automation_update list 确认无待跑棒(全部旧棒 scheduledAt 均已过 ⇒ 按判据不算待跑)。
  • 链的剩余:步骤 2(S2)→ 3/4(反证)→ 5(S0 端点)→ 6(S3 台账)→ 7(真机零介入)。

16:2x 序46 执行棒 2 收口 —— §⑤ 步骤 2(S2 网级放行)+ 反证 验收通过

所选实现:§④ 首选 = dialers 网级通配 u:<tenant>/*(退路"新增整机开关"未采用)。判据 = 改动面最小 + 判据显式。 生效范围:只对写 /* 的那张网;ops 与未列网络照旧默认拒绝;通配只放宽"进来后算哪种身份",准入仍靠 REQUIRE_IDENTITY=1 + 逐设备 grant(身份闸门 server.ts:1146 在注册闸门 :1187 之前);⛔ 通配只准用在租户网(ops/* ⇒ 装载时抛)。

核心判据成立:全新 hostId d-2ade6411-…-78c24c49 + 有效 grant ⇒ AUTH OK … ports=[](拨号器 state:up)。A/B 唯一变量 = 白名单:旧配置 ⇒ 拒 no-ports(证"确实不在名单里"+新代码本身不放宽);新配置 ⇒ 通。/status 租户网桶 = ["*"],无任何逐设备条目;refused=0 dialDenied=0,manager/w-106 两次重启均自动重连。

反证四条全成立:无 grant ⇒ identity-incomplete;真签名绑别的 hostId ⇒ identity-host-mismatch;绑别的 nodeKey ⇒ identity-key-mismatch;改 grant 文档 ⇒ identity-signature-mismatch。 ⚠️ 教训:改文档只能拿到 signature-mismatch;要 host-/key-mismatch 必须造真签过但与对端出示物不一致的 grant(顺序:签名→网络→host→key)。

改动 4 文件:network.ts(DIALER_WILDCARD + splitEntry 抽成唯一一份 + parseDialerEntry + isAllowedDialer 通配分支)|server.ts(两处闸门由内联 dialers.get(...)?.has(...) 改为调用唯一出口)|index.ts(导出)|两个 test(通配用例 + T4 守卫升级:等价性从"逐字相同"变"同一份代码",并反证 server.ts 里不再有内联形态)。交付门禁:npm test 226/225 通过 0 失败;check:layering 无新增违规;单已同步 /opt/dsh/docs 镜像(md5 一致);锁已释放。

本轮两条硬发现(后续棒必须处理)

  1. 🔴 relay 的 HMAC 密钥表在进程启动时装载一次 ⇒ 新增设备必须重启 relay 才能拨(实测 unknown-host)。这是 §① 终态"人工零介入"的真障碍(与网级放行无关,在准入链更前面)。已排除"放开 REQUIRE_IDENTITY / 租户共享 secret"(都放宽权限);最优解 = relay 热加载 relay-keys.json ⇒ 归入步骤 5/6。
  2. 🔴 derive 口径仍未定(步骤1 发现3 延续):deriveDialers 本就支持多网并集(单网限制只在 deriveDropIn);且通配一旦启用,"派生该产出 /* 还是逐设备枚举"会牵动 auditDerivation 三判据(现有实现会把两者判为不等 ⇒ 自审红)。⇒ 动 derive 前先定这两条。

停手位置:⛔ 步骤 5(S0 端点)/ 6(S3 台账)/ 7(真机端到端)未做(本轮明令不碰)。下一棒 = 步骤 5 + 遗留2(密钥表热加载),已登记一次性自动化 99936fdd-f597-4669-a823-a052e07abcaf(scheduledAt 2026-09-20T16:28 = 收口 + 7 分钟);登记前已 list 确认无待跑棒。原始读数(20 文件)落盘 tmp/seq46/readings-s2/。


收口 · 16:28–17:1x 序46 执行棒 3 —— §⑤ 步骤 5(S0 用户态签发端点)+ 遗留2(密钥表热加载) 验收通过

基线 HEAD 1242d07(与步骤 1/2 同);改动 7 文件(代码 5:src/web/routes/overlay-device.ts🆕|src/net/relay/server.ts|src/net/relay/main.ts|src/web/server.ts|test/overlay-device.test.mjs🆕;配置 3:config/*)。§④ 补齐项实测:签发导出名 = signNodeGrant(grant, signerPrivateKeyPem)(identity.ts:396);u:<tenant> 网已被 relay 服务。

真跑读数(全部成立)

  • S0:不带会话 401|tenant 会话 200(network=u:2ade6411-…|hostId=d-…-faf3784f|grant={doc,sig}|expiresAt=+24h|密钥表 7→8)|admin 会话 200 但落在自己的网 u:cce6d1cd-…(配额各自算)|连发到上限第 9 发 429 device-quota-exceeded(used=10 max=10)。
  • 遗留2 热加载:启动日志 密钥表:5 条(**热加载已启用**);relay ActiveEnterTimestamp=16:57:30 未变,17:05:20 🔄 密钥表热加载:7 → 8 条(新增 1:…-faf3784f) → AUTH OK … ports=[],拨号器退出码 0;清理后 17:08:39 8 → 6 条(移除 2);keyReloads=3 keyReloadFails=0 refused=0。
  • 单测 7/7;全量回归 233 项 0 失败。交付门禁:4/4 md5 一致(/opt/dsh-relay/lib/net/relay/{server,main}.js + /opt/dshs/lib/web/{server.js,routes/overlay-device.js})|文档镜像 md5 一致 2960626c…|锁已释放。

🔴 本轮事故(已完全修复,必记):清理探针时我按归一化名(ops/w-47)匹配 /etc/dshs/relay-keys.json 的原始键(文件里是裸 hostId w-47)⇒ 误删 ops 三把密钥(17→3 条)。处置 = 停手 → 从自建备份整份恢复(17 条,装载后 ops/w-47,w-106,manager 俱在)→ 按原始键名重删 → 补三条硬断言(保留名单含 ops 三把/删除名单⛔ 不得含/装载后用产品自己的装载器验三条仍在)。复验 online=w-106,manager refused=0 authFailed=0 ⇒ 现网无残留影响。 ⇒ 纪律:删/改共享表文件,判据必须是文件里的原始键;改完立刻用产品装载器自校验 + 断言关键条目仍在 —— md5/count 都不够。

本轮其他具名发现

  1. 🔴 新用户"零介入"的下一个真障碍:DSHS_RELAY_DIALERS 是手写 drop-in ⇒ 新租户网无人加白名单,新用户即使有有效 grant + 密钥仍会在身份闸门被拒(dialer 身份拿不到 ⇒ no-ports)。与"derive 多网并集口径"同源 ⇒ 步骤 6/7 硬前置。
  2. ⚠️ /opt/dshs/scripts/overlay-relaykey-add.cjs 在 47 上不存在(MODULE_NOT_FOUND)⇒ 按该工具文档删密钥会失败;调用方吞 stderr 时 = 静默失败(我踩了一次)。与"远端 CLI 与 HEAD 不一致"同族(不止 md5 不同,还有缺失)。
  3. ⚠️ 取证脚本假信号(已修):复用拨号脚本 --ticks N 只在 N=0 时排周期 ⇒ --ticks 2 永不退出 ⇒ 外层 timeout 收尾给退出码 124,而 STATUS {"state":"up"} 明明成功。已改为"真排 N 次"。
  4. ⚠️ scp 路由文件后必须重启 dshs:本轮首次漏重启 ⇒ 跑的是旧模块,现象 = 回执里 grant.doc=undefined(形状仍旧)⇒ 这类"改了没生效"在回执形状上可直接辨认。
  5. ℹ️ 平台侧 /opt/dshs/lib/net/relay/server.js 落后两代且是死代码(src/ 无人 import)⇒ 本轮刻意不动它(避免夹带步骤 2 改动进无关文件)。
  6. ℹ️ 47 上 DSHS_OVERLAY_SIGNER_KEY_FILE/DSHS_RELAY_KEYS_FILE 未显式配,走代码内中性缺省,且缺省值恰好 = 生产路径(实测签名者私钥读到、密钥表写对)⇒ 无需改 drop-in。
  7. ℹ️ 清理后密钥表 6 条(ops 三把 + tenant 三台),台账 approved = 3(全 tenant);探针会话已删。真跑成功的 -faf3784f 留册供步骤 6/7 复用(密钥/grant 在 47 /tmp/seq46/s0/)。

停手位置:⛔ 步骤 6(S3 台账)/ 7(真机端到端)未做(本轮明令一棒一步)。下一棒 = 步骤 6(S3 台账落 PG + 续签语义),并把**遗留 1(新租户网的白名单来源)**纳入(步骤 7 终态硬前置)。原始读数(17 文件)落盘 tmp/seq46/readings-s0/(47 侧 /tmp/seq46/s0/readings/);复用脚本 tmp/seq46/s0-*.sh。


收口 · 17:20–18:05 序46 执行棒 4 —— §⑤ 步骤 6(S3 设备台账落 PG + 续签语义) + §⑪ 遗留 1(新租户网白名单来源) 验收通过

基线:1242d07(09-19 16:18)|锁:序46-棒4-设备台账落PG(17:20 起持有)→ 收官已 handoff-guard.sh --release-exec

做了什么(15 文件 · 代码 14 + 文档 1)

  • S3 台账(v12 · 双方言):overlay_devices 落 47 的 PG13(PK (network,host_id)、FK users(id) ON DELETE CASCADE、CK status∈{active,revoked} 默认 active、grant_renewals 默认 1、索引 idx_overlay_devices_user)。续签语义 = grant_renewals = overlay_devices.grant_renewals + 1(引旧行,⛔ 不用 excluded.);status 刻意不进 DO UPDATE SET ⇒ 停发黏性、续签不复活。
  • 端点接线:src/web/routes/overlay-device.ts 新增 ① 停发闸门(status='revoked' ⇒ 403 device-revoked)② 台账写入(失败 ⇒ 500 device-ledger-write-failed,且密钥表未写);回执加 renewals + ledger{…}。管理面新增 GET /api/admin/overlay-devices(只回 nodeKeyFingerprint)+ POST …/revoke(非法 400 / 不存在 404)。
  • 遗留 1:TENANT_NETWORK_WILDCARD='u:*' + isAllowedDialer 三分支取并集;只放宽身份面、⛔ 不放宽准入面;只对 networkKindOf()==='tenant' 生效;半通配 u:*/<hostId> 与非租户网通配装载即抛;status() 跳过 u:*(⛔ 不产幽灵网)。

核心读数(全部真跑)

  • 到期拒(短租约 +25 s):客户端 HELLO rejected reason=identity-expired + relay AUTH DENY … why=identity-expired retryable=false,退出码 1
  • 续签通:端点 renewals=2、租约后移一天;PG grant_renewals=2 | lease=2026-09-21 17:56:10+08;同设备再拨 AUTH OK/STATUS state=up/退出码 0
  • 停发/恢复:revoke 200 ⇒ 端点 403 device-revoked,grant_renewals 停在 2(⛔ 不复活);传 status=active ⇒ 200、renewals=3
  • 遗留 1 A/B/C(同设备同 grant,唯一变量 = 白名单):A(只列 u:2ade6411…/*)DENY why=no-ports / B(u:*/*)AUTH OK session=d5f508f2483ee4bd 退出码 0 / C(不带 grant)DENY why=identity-incomplete;ops refused=0、online=[manager,w-106]
  • 部署对账:10 个产物 scp 后 md5 10/10 与 47 逐字一致(平台侧 8 + relay 侧 2);文档两处(序46 交接单 + 117 参数表)双端 md5 一致且 CR=0(纯 LF)

本轮具名发现(要带走的)

  1. 🔴 u:*/* 是「新用户零介入」的唯一静态表达(租户 id 运行时才生成);代价 = 租户网内身份面整网放宽(准入面一字未动)。
  2. ⚠️ 47 上没有 /etc/dshs/overlay-signer-public.pem;受信公钥要从 /etc/dshs/overlay-signers.json 的 doc.signers[0] 取(bad464dfd53048ef…)。⛔ 别按文件名推。
  3. ⚠️ schema_migrations 只有 (version, applied_at) 两列,⛔ 无 name。
  4. ⚠️ npm run verify 在 scripts/verify-platform-admin-section.mjs 崩(require() + top-level await 混用)—— 仓库既有缺陷,本轮未修(不在范围);npm test 全绿。
  5. ℹ️ 平台侧 /opt/dshs/lib/net/relay/server.js 落后 relay 侧(死代码),本轮刻意不动(同 §⑪ 发现 6)。

清场:探针会话 user_agent='seq46-s3-probe' 2 → 0 已删;测试设备 d-cce6d1cd-…-2836b9b8 留册(registry approved 4 台、密钥表 7 条);本机归档副本已脱敏(删 sid.txt/secret-1/2.txt/device.key 四个凭据文件 + 3 处 hmacSecret 置占位),原文只在 47 /tmp/seq46/s3/。

停手位置:⛔ 步骤 7(真机端到端 · 零介入)未做(本轮一棒一步)。下一棒 = §⑤ 步骤 7(automation 9f29374c-4cc0-4a04-99e9-462c2491afca,定 18:12)。⚠️ 步骤 7 依赖桌面线「第 5 棒」客户端脚本,若未就绪须按 §⑤「失败一律具名」停在步。原始读数 41 文件落盘 tmp/seq46/readings-s3/;脚本 tmp/seq46/s3-01-prep-ab.sh · s3-02-expiry-renew.sh · s3-dialers.sh · redact-s3-readings.py。


18:12–18:2x 序46 执行棒 5 —— §⑤ 步骤 7 受阻 · 具名停在步(原因码 client-artifact-missing)

基线:HEAD 1242d07|锁:序46-步骤7-真机端到端-20260920-1812 → 收官已 --release-exec(复核空闲)

判定:§⑤ 步骤 7 未执行 —— 缺的是载体(真机客户端脚本),不是"网络不通"。⛔ 本轮未拿测试拨号脚本(tmp/seq46-s2-dialer.cjs)冒充「真机客户端」(上一棒明令禁止的形态)。

四条具名依据(桌面侧产物不存在)

  1. 桌面线 automation 台账 10 条,无「第 5 棒」;第 4 棒(0142e285-…)memory 原文:「⛔ 未登记下一棒 —— 第 5 棒(接入覆盖网络)需 --invite + --signer-pub,「需用户提供凭据」⇒ 边界外 ⇒ 链条暂停等拍板」。
  2. 对接单 §4 指定落点 E:/dsh-worker-dev/overlay/ 不存在。
  3. 桌面线工作区 13 个可执行脚本全是 S1/S1′ 起实例/起 worker 探针(_devkit/s1-*.ps1 · s1-*.mjs · s1p-verify-instance.sh),零覆盖网络拨入代码。
  4. 全工作区命中 RelayClient/dialer/nodeSig/dshs-relay 的 = 3 份,全是文档(对账/实现方案/对接单),无代码。

反向结论(重要):桌面线第 5 棒"自停"的两条前置 —— ① 需控制面签 --invite+--signer-pub ② 只进白名单会被拒(还缺 grant)—— 已被序46 步骤 5/6 全部解除(S0 登录态换 grant + u:*/* 全域 + 密钥表热加载)⇒ 桌面线现在具备自恢复条件。其恢复属桌面线 lane:本线⛔ 不代其登记棒次、⛔ 不写其工作区(对接单头部明文「平台会话只读本单」;跨线通知需用户指令)。

平台半边就绪核对(只读):relay active/running(ActiveEnterTimestamp=17:52:31)· DSHS_RELAY_DIALERS=ops:manager,u:*/* · DSHS_OVERLAY_REQUIRE_IDENTITY=1 · online=[manager(ports=[]), w-106(ports=19000/21000)] · refused=0 · keyReloads=1 keyReloadFails=0 · 密钥表 7 条 · 租户网 sessions=[](无遗留拨号)· S0 端点在线(不带会话 401)。authFailed=2 两条都具名:17:52:39 why=identity-incomplete + 17:56:07 why=identity-expired = 步骤 6 的故意反证 ⇒ ⛔ 无未解释增量(曾误判为异常,journal 原文已澄清)。

本轮具名发现(写进单 §⑬ ④,供桌面线第 5 棒直接用)

  1. 🔴 「登录态」= sid cookie,无 token / 设备码通道:src/web/middleware/authn.ts:16-17 只解析 sid cookie;POST /api/auth/login(src/web/routes/auth.ts:282)用 username+password 登录并 set-cookie ⇒ 桌面客户端必须自维持 cookie jar。
  2. 🔴 /api/auth/login 是「单活跃会话(last-wins)」(auth.ts:294-296:注释 + deleteUserSessions(user.id))⇒ 设备登录取会话会顶掉用户浏览器会话;反之用户再登录 ⇒ 设备会话失效 ⇒ 无法续签、租约到期掉线。触发条件:真机常驻形态落地前必须定「设备会话 / 浏览器会话」并存口径。
  3. ⚠️ §⑧ 风险 3 的 nodeSig 时钟窗口本轮未实测 ⇒ 已登记为下一棒事项。

交付 / 收口:单新增 §⑬(受阻具名 + 平台就绪 + 发现 + 遗留 + 接续)|原始读数 3 文件落 tmp/seq46/readings-s7/(00-platform-readiness.txt · 01-authfail-forensics.txt · 02-desktop-side-evidence.txt);脚本 tmp/seq46/s7-0{0,1,2}-*.sh|单双端 md5 8cf8bfedcdb47ccc9ab6f83fd82c4ff6 一致(镜像 600 root:root、CR=0)|全库对账 一致 243 / 不一致 1 / 仅本地 7 / 仅服务器 5 ⇒ 与上棒同、无新增差异|交接单/README.md §一 序46 行本机已回填(⚠️ 未推镜像,同 §⑫ 先例避免夹带)|零改动面(未改代码/配置/凭据/服务)⇒ 无需回滚。

下一棒:5abcac0b-c91a-415c-ae1f-246ac0819807(一次性 · 18:28 = 收口 + ~7 分钟)= 步骤 7 前置口径 + 存在性门禁:门禁先查桌面线客户端脚本(到 ⇒ 直接跑步骤 7)⇒ 未到则 ① 实测 nodeSig 的 ts|nonce 容忍窗口 ② 出「设备会话 vs 浏览器会话」并存口径草案(⛔ 不动代码、⛔ 不放权限)。

⚠️ 待用户口径(陈述句,非征询):桌面线第 5 棒是其链条自停的棒次,原阻塞已由序46解除 ⇒ 恢复它只需在桌面线工作区开一棒(属桌面线 lane);本线不代其登记。另:若要本线把「凭据阻塞已解除 + 客户端所需全部口径」正式通知桌面线,需用户指令 —— 其对接单头部写明「平台会话只读本单」,上次追加(§10)是用户指令授权的。


收口 · 18:28–18:45 序46 执行棒 6 —— §⑤ 步骤 7 前置(门禁复现 + 时钟窗口实测 + 会话并存草案)|验收:前置已补齐,步骤 7 仍未过(无载体)

① 门禁(§⑬ 遗留 1 复现)= client-artifact-missing 未变:落点 E:/dsh-worker-dev/overlay/ 不存在;桌面线工作区顶层只剩 .workbuddy/+CODEBUDDY.md+docs/(§⑬ 清点出的 13 个脚本已不在);关键词 .cjs/.js/.ts/.mjs/.sh/.py 全量命中 0。⇒ 仍不用自造拨号脚本冒充真机客户端。

② 时钟窗口 —— 真跑实测(本棒核心):探针 tmp/seq46/s7-10-window-probe.cjs,只读零凭据 —— 靠「clock-skew 判在 bad-mac 之前」这一校验顺序,用 dummy MAC 量边界(36 次真实 WS 握手)。

  • 窗口 windowMs=60000(取自 HELLO_ERR,⛔ 非读常量)
  • 正向(客户端快):内 +60009ms | 超 +60010ms;负向(客户端慢):内 -59991ms | 超 -59992ms
  • 两侧不对称 ≈ ±10ms 可解释:服务端取收帧时刻 ⇒ 反推 47 回环单向时延 δ ≈ 8–10ms
  • 超窗原因码 = clock-skew(retryable=true)|窗口内 = bad-mac(retryable=false)
  • 自愈实测:ts=+7200000ms → clock-skew(帧内带 serverTime)⇒ 按 client.ts:1248 公式校正后第 2 次已越过 clock-skew(终点 bad-mac,因 dummy MAC)⇒ 漂移只决定"多不多一次握手",不决定"连不连得上"

③ 🔴 口径更正(重要,供后续直接引用):§⑧ 风险 3 说"时钟漂移会导致 identity-bad-proof" —— 与实现不符。判据顺序(src/net/relay/server.ts:1249-1257)= unknown-host → network-mismatch → bad-nonce → bad-mac-length → **clock-skew** → nonce-replay → bad-mac → 身份两关;clock-skew 早于 MAC、更早于身份 ⇒ 超窗必然拿 clock-skew。真机上见 identity-bad-proof 一律不是对时问题,排查方向 = nodeSig 挑战串 / 密钥 / grant 三者不一致。(§①–⑧ 一字未改,只在单 §⑭ §⑤ 留记录。)

④ 会话并存草案(§⑬ 遗留 3)—— 已出 · 落待拍板:真读代码得事实表(sessions 无 kind 列 · auth.ts:294-296 无条件 deleteUserSessions · 无续签端点 · 会话 TTL 7 天 vs grant 24h · role 无细粒度 · 续签要 sid)。量化更正:设备被迫重 login 的频率 = 7 天一次,⛔ 不是 24h 一次。候选 A(独立账号·零改码)/ B(sessions.kind 分桶·我倾向)/ C(独立续签通道)/ D(维持现状=降级)—— B 落"放宽权限"、C 落"新增长期凭据面" ⇒ 列待拍板、停手,⛔ 未改一行代码、⛔ 未放开任何权限。

⑤ 具名发现(增量零未解释):authFailed 2→40(+38 = 探针 36 + 补测 2),journal AUTH DENY 38 条 = 20 clock-skew + 18 bad-mac 逐条对上;authed(5)/identityOk(5) 零增量;used=2/7515、ops 在线面、dialers 前后一致 ⇒ 对 ops 零影响。⚠️ 热加载是「懒」的:keyReloads 1→2 唯一来源 = 密钥表 mtime 17:56:13 后一直没有新 HELLO,直到我探针首连才触发(7 → 7 条 新增0移除0,内容零变化)⇒ 设备首次拨入本身就是那个 HELLO,不会因表没跟上被拒。

⑥ 落盘 / 对账:读数 6 文件 ⇒ tmp/seq46/readings-s7b/(含 05-index.md);脚本 s7-10/11/12;47 侧探针 /tmp/(md5 与本机一致 5b9a3f0d0df61fe3eac93bea94b23fd4,⛔ 无持久写)。单 §⑭ 已回填;scp 镜像后 md5 一致 c5d1f5d9963760a95f0d93238916ef6c、CR=0 / 683 LF / 73331 B、600 root:root。零回滚(未改码 / 未改配置 / 未签凭据 / 未重启单元)。全局执行锁已释放。

⑦ 未登记下一棒(决定 + 理由):下一棒实质内容被三件全部边界外的事卡住 —— ① 载体属桌面线 lane(且其工作区脚本已消失)② 会话路线属待拍板 ③ 跨线通知需用户指令。⇒ 按用户铁律「要用户拍板的,等拍了再新建接续会话」不登记(登记 = 8 分钟后空转一小时复述同一结论)。拍板或载体一到,立即登记。


拍板落档 · 20:03——20:1x · 会话并存方向已定(用户口径)

用户原话:「应该使用方案A 一个账号可以支持多种设备登录包括服务器实例」。

🔴 语义对齐(必须记牢,否则会做反):草案 §③ 候选 A 的字面 = 「每台设备绑一个独立平台账号」= 多账号;用户口径 = 「一个账号支持多种设备登录」= 多设备·单账号 ⇒ 两者在"账号数"这一维上方向相反。用户口径与候选 B 的内核(解除 last-wins、允许多会话并存)一致。⇒ 一律以用户原话那句为执行判据,字母只作称谓。

需求判据:① 同账号可同时持 ≥2 会话、互不顶替 ② 覆盖浏览器 / 桌面设备 / 服务器实例三类来源 ③ 保留主动"登出全部"(把原"登录即隐式驱逐"改成显式操作)④ ⛔ 不引入子账号。

已定路径(我定·可推翻):真多会话 + 每用户会话上限 + 显式「登出全部」入口;sessions 加来源维度列。⚠️ 涉 sessions 表迁移(schema v12)⇒ 必须带回滚。⚠️ 待取证(下一棒第一步):「服务器实例」到底怎么以用户身份登录 —— 复用 /api/auth/login 拿 cookie,还是需独立设备码/令牌?(§⑬ 发现1 已证本版只有 cookie 会话)。⛔ 本轮未改代码 / 未动 schema / 未放权限。

落档:单 §⑮(拍板记录 + 需求判据 + 已定路径 + 待取证 + 接续)。scp 镜像后 md5 一致 a9bf350c90e8e9722c1a772f3643aa82、CR=0 / 726 LF / 76088 B、600 root:root。下一棒 = 规划棒(出改造单,含取证 + 档案占号 + 步骤/验收/回滚)。

🔴 本机环境新事实(会误导判断,务必记住):本轮bash 的 shim 环境坏了 —— shell-runtime-bash-env.sh: line 3: dirname: command not found / cd: null directory ⇒ 凡走 shell 的脚本(含 handoff-guard.sh)会假报"已有执行会话在跑"(实为脚本第 33 行 dirname 找不到 ⇒ 抢锁逻辑崩)。 ⚠️ 差点因此误判"有会话占用、停手" —— 实测 .exec-lock 根本不存在(锁是空的)。 ✅ 绕过写法 = 在命令最前面显式给 PATH: PATH="/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/bin:/c/Windows/System32"; export PATH; … ⇒ 加完 dirname 有解析、抢锁即成功。⚠️ 另:head 等同理不可用;PowerShell 工具本轮的 stdout 也采不到(且重定向出 UTF-16)⇒ 大输出一律先落盘再用 python 读。(判据:报"抢锁失败"时先查 .exec-lock 是否真存在,⛔ 别直接信脚本的退出话术。)


20:19–20:3x · 序47 规划棒(单账号多设备会话并存 · 出改造单)

本棒 = 用户 19:5x 拍板(序46 §⑮)后的第一棒,只出单、⛔ 零代码零生产改动。 会话 plan-session-order47-multidevice-0920;抢锁 → 取证 → 出单 → 对账 → 释放锁 → 登记下一棒,全程一次走完。

🔴 第一步取证结论(本棒核心,⛔ 决定了整单的实现边界): 本版平台不存在任何「实例 → 平台」身份通道 ⇒ 「服务器实例」不能复用 POST /api/auth/login,必须走平台侧代签。三条证据: ① src/web/middleware/authn.ts:16-28 —— requireAuth 只从 sid cookie 取会话,⛔ 无任何 header/token/设备码分支; ② src/web/routes/auth.ts:281-300 —— login 只收 username+password 并 set-cookie sid ⇒ 实例要登录必须持有用户口令,而平台不存明文口令、也从不把口令投给实例; ③ src/supervisor/orchestrator.ts:821-827 + :776 + remote-spawner.ts:263 —— 实例 spawn 只投 DSHS_ROLE/DSHS_HANDOFF_PATH/DSHS_PORT(+DEEPSEEK_API_KEY),远程 payload = {userId, folder, patch, apiKey, uid, epoch} ⇒ 平台侧已知实例归属的 userId,却从不给实例任何平台身份凭据。

产出:交接单/覆盖网络-序47-单账号多设备会话并存.md(8 段模板 + §⑪ 规划棒记录) + 交接单/README.md §一 新增一行(只加自己那行)。编号 = 47(交接单/ 既有最大序 46 ⇒ +1;mkdir .lock-47 原子占号首次成功)。

⚠️ 编号撞名(已在单头部写消歧):主覆盖网络线的棒编号里「序㊼ 部署棒」已存在(记在 交接单/覆盖网络-序45-….md §14)⇒ 那是棒编号、不是本目录序号。⛔ 引用本单只用文件名。

🔴 单的核心设计(可推翻项已写明优缺点):

  • v13 迁移一次两列:sessions.kind(browser|desktop)+ overlay_devices.kind(desktop|instance); ⛔ 不给 sessions 加 instance 值 —— 取证的直接后果:实例拿的是设备凭据、不是平台会话(⛔ 不留废值);⛔ 不加 DB CHECK(SQLite ALTER 不支持,双后端 DDL 必须同形)。
  • 实例凭据 = 平台侧代签(复用 signNodeGrant + relay 密钥表 + overlay_devices 台账),私钥由平台生成、只写实例 home(走 UserFs)、平台不留副本;⛔ 不新增公开 token/设备码通道。
  • 续签 = 平台侧定时重签,读台账 node_key(⛔ 不读实例文件);⛔ 不用长租约(与"停发即拒"冲突)。
  • 保留 E2「登出全部」+ E4「按来源单条吊销」+ E3「每用户上限」(DSHS_MAX_SESSIONS_PER_USER 中性默认 20)作为放宽权限的补偿控制(写明"本单确实放宽了会话语义")。

🔴 本轮新增具名发现(会误导判断):交接单/README.md 本身是 CRLF —— HEAD blob 即含 156 个 CR(工作副本 166),而同目录 序45/序46/序47 单均为 CR=0 ⇒ 同目录两种行尾。 ⚠️ 本棒用 Edit 精确插入(工具自动沿用该文件既有 CRLF ⇒ 文件内部未混合),⛔ 未做全文件 LF 转换(属批量换行转换,须先出受影响清单并取得确认)。

收官四件套读数:① 读数落盘 tmp/seq47/readings-plan/(00-code-facts.txt / 01-docs-audit.txt / 02-numbering.txt / 03-final-sync.txt) ② 回填 = 单 §⑪ + README 行 ③ 镜像对账 = 两文件 md5 与本机逐字节一致(单 CR=0 / 600 root:root;README 644 root:root);docs-audit.py 退出码 0 ④ --release-exec 已释放(已核 .exec-lock 不存在)。⛔ 未 commit / 未 push(按提交边界)。

遗留(带触发条件):① 交接单/README.md 行尾统一 ⇒ 等用户点头(须先出清单);② 技能 dsh-change-workflow R4 的"last-wins 会踢掉用户会话"理由 ⇒ 序47 落地后必须同步改(SKILL.md:146 与 :395;落地前仍正确,⛔ 本棒未改)。

下一棒已登记 = 序47 执行棒 1,automation 5e881487-4a68-4d87-b9fd-06334c9f1e5c(一次性 · 2026-09-20 20:36 = 收口 +6 min,落在用户明令的 58 分钟窗口内)。 ⚠️ 登记前已 automation_update list:列表里 status 全为 ACTIVE,但全部 scheduledAt 均已过 ⇒ 无待跑棒(判据 = 看 scheduledAt + 编号,⛔ 不看 status)。

20:20–20:50 · MCN 数据同步落位 + 站点图标

  • MCN 库迁到权威落点:源 C:/Users/Administrator/.dsh/mcn-plugin.db(444 行/10 表)→ 47 的 admin(cce6d1cd…, uid 114801)<userRoot>/home/.dsh/mcn-plugin.db(2,383,872 B)。保留远端权威 schema(按列名合并;两侧列序不同 ⇒ 丢列 0)。备份 /opt/dsh/backups/mcn-plugin.db.admin-20260920-2029。原子改名(.new → mv -f)+属主 114801 +重启 scope。
  • 暴露真因:插件 lib/host/mcn/config.js:6 用 join(homedir(), ".dsh", "mcn-plugin.db"),而实例 HOME=<userRoot>/ws ⇒ 指向不存在的 ws/.dsh/ ⇒ unable to open database file(先于本次改动即存在)。档案 144 §6.3 明定落点 = <userRoot>/home/.dsh/<plugin>.db(由 DSH_HOME 决定)。
  • 已出补丁(未部署):dsh-plugin-mcn/lib/config.js 新增 DSH_DIR = process.env.DSH_HOME ? join(DSH_HOME,".dsh") : join(homedir(),".dsh"),DB_PATH / RANK_META_FILE 改用它并导出。⚠️ 权威源是 dsh-plugin-mcn/lib/ —— suite 的 lib/host/mcn/ 由 scripts/build-mcn-suite.cjs 的 HOST_SYNC 覆盖同步(先改副本会被冲掉)。
  • 产物:plugin_package/dist/dsh-plugin-mcn-suite-0.3.12.tgz(436 条目 / 1.90 MB / md5 f1d5da720bd9fedf1da64fe54d67d189);逐成员比对 0.3.11 ⇒ 仅 3 处差异 = config.js、package.json(版本)、skills/.manifest.json(仅 generatedAt)。
  • 站点图标:剪贴板图 748×748(AI 六边形网络)⇒ tmp/mcn-sync/icon-out/ 出 favicon.{png,ico} + 180/192。4 个 web/*.html 的 <link rel="icon"> 待改。
  • ⛔ 未执行(R9 锁被占):全局执行锁被 序47执行棒(automation 5e881487) 于 20:36 起占用,三次 --claim-exec 均失败 ⇒ 平台仓 web/*.html 改动、图标 scp 到 /opt/dshs/web/、插件 0.3.12 投放 全部挂起(远端步骤已固化在 tmp/mcn-sync/deploy-remaining.sh)。
  • 顺带发现(按"先报告后动手"未改):dsh-plugin-mcn/lib/index.js 有 13 处 join(homedir(), ".dsh", "sessions")(应为 $DSH_HOME/sessions = <userRoot>/home/sessions)—— 同一根因的另一半。

20:36–21:12 · 序47 执行棒 1(单账号多设备会话并存 · 步 1–5 落地 + 真跑验收)

  • 本棒 = 自动化 5e881487,按单 交接单/覆盖网络-序47-单账号多设备会话并存.md 从 §⑤ 步骤 1 起,步 1–5 全部落地并真跑验收;步 6–9 未做(实例凭据代签与投递 / 续签 / 门户 UI / 端到端)。
  • 代码改动(D:\github\dsh_shenxian,基线 HEAD 1242d07,⛔ 未提交):schema.ts v13 迁移(sessions.kind + overlay_devices.kind + idx_sessions_user_created)|types.ts/repo.ts/pg.ts/adapter.ts/sqlite.ts 加 kind + 新增 listUserSessions(升序是判据的一部分)|config.ts 加 maxSessionsPerUser(默认 20)|web/auth.ts 加 sessionPublicId() = sha256(token_hash) 前 16 hex|web/routes/auth.ts 删 deleteUserSessions 改「并存 + 上限具名淘汰 + 审计 session_evicted」|新增 web/routes/sessions.ts(4 端点)+ net/relay/device-grant.ts(共用签发,五道门)|overlay-device.ts 降为薄路由(具名导出原样再导出,⛔ 不留两处判据)。
  • 关键实测:PG max(version)=13;迁移前旧 sid 迁移后 /api/auth/me 仍 200;E1 两客户端各 200(旧行为必 401);client=instance/非法 ⇒ 400 bad-client;26 次登录 ⇒ 会话恒 20 + session_evicted 6 行;revoke 命中条 401 / 另一条 200 / 不存在 404;logout-all ⇒ {ok:true,revoked:19} 全 401;会话列表 token 字样 0 行;test/overlay-device.test.mjs 33 pass;npm test 241 项 / 240 过 / 0 败 / 1 skip。
  • 部署:tsc → scp 38 个 lib 文件(⛔ 不整目录覆盖)→ 备份 /opt/dshs/lib-bak-seq47-20260920-205248 → restart dshs = active;本机/远端 38 文件 md5 全一致。
  • 收口:读数落 tmp/seq47/readings-exec/(18 个文件);回填单 §⑫ + 交接单 README + INDEX + 档案 145(原子占号 .lock-145);文档 6 文件同步 /opt/dsh/docs(md5 逐条一致 · 600 root:root);测试账号/审计已清;全局执行锁已释放(21:1x 复核 ✓ 无锁)。技能 dsh-change-workflow R4 已改写(last-wins 理由失效 + 专用测试账号省事法 + sid cookie 坑),三处 md5 一致 6a88c7daa0b1ce482d8e206e2cd02c25。
  • 下一棒已登记 = 序47 执行棒 2,automation 90049d4c-8080-4a73-a1d8-908e06f91ecc(一次性 · 21:18)。
  • 🔴 本轮新固化坑(会重犯):① sid cookie 带 Secure + Domain=.ai1net.com ⇒ curl 不会为 127.0.0.1 存这条 cookie(jar 不生成)⇒ 验证必须从 set-cookie 响应头取值再 -b "sid=$VAL"(曾致首版全部 401 的"功能没生效"假象)。② 按 kind 找会话会命中残留行 ⇒ 用 -A 打唯一 userAgent 反查 sid_public。③ scp 中文目的路径不落地 ⇒ 先 ASCII 暂存名,远端再 install。④ 本机 md5sum 出 hash *path、远端 hash path ⇒ 直接 diff = 假"全不同"。
  • 顺带观测(与本单无关):systemctl restart dshs 停机走 stop-sigterm 超 90s 被 SIGKILL(既有停机路径残留 relay 流)。

21:14–21:3x · MCN 线收尾棒 —— 站点图标上线 + mcn-suite 0.3.12 投放(本轮)

  • 锁:21:14 抢到全局执行锁(--claim-exec "MCN线收尾棒")。⚠️ 简报里的 PATH 法在本机有害:前置 /c/Windows/System32 会让 bash 命中 WSL 启动器 ⇒ 输出只剩乱码(用于 Linux 的 Windows 子系统没有已安装的分发)、退出码 1,看起来像"抢锁失败 / 脚本静默错判"(旧提示里的 PortableGit 段路径本机已不存在)⇒ 直接跑 bash … 即可。已改写 MEMORY.md §二。
  • A 站点图标(已上线):web/{admin,login,portal,register}.html 的 <link rel="icon" href="/favicon.svg" /> → 三行(favicon.ico sizes=any + favicon.png + apple-touch-icon=favicon-180.png);⛔ portal.html:130 顶栏 <img class="logo" src="/favicon.svg"> 未动。scp 4 图标(ico/png/180/192)+ 4 个 html 到 /opt/dshs/web/(chmod 644)。行尾按服务器既有内容逐文件对齐(实测服务器侧 admin/portal 是 CRLF、login/register 是 LF ⇒ admin.html 上传前转 CRLF,其余原样)。
  • A 线上复验:/favicon.ico、/favicon.png、/favicon-180.png、/favicon-192.png 全 200(Content-Type 分别 image/vnd.microsoft.icon、image/png ×3);四页线上 HTML 均带三条新 tag,portal 顶栏仍指 /favicon.svg ✅。
  • B 备份:/opt/dsh/backups/dsh-plugin-mcn-suite-0.3.11-20260920-211901.tgz(md5 0b84b3bc485426c40307fd076b922a44,与源逐字节一致)。
  • B 投放:池内 dsh-plugin-mcn-suite.tgz → 0.3.12(md5 f1d5da720bd9fedf1da64fe54d67d189 · 436 条目 · 1,989,494 B · chmod 644)。scripts/install-plugin-for-user.cjs → /opt/dsh/scripts/(两端 md5 1200eb75d0fd6537ac90f3c687dbbdc2 一致);执行 --home …/cce6d1cd…/home --uid 114801 --profile web --expect 0.3.12 ⇒ ⑤ 实装版本 = 0.3.12 / OK(脚本内部 setpriv 降权,⛔ 全程未用 root 装)。
  • ⚠️ 脚本 ④ 报「已停 0 个实例 scope」,但实例在 21:20:53 被 Manager 的 crash-restart(restartsInWindow:1)以新 scope dsh-114801-6d982d03.scope 拉起 ⇒ 新包被加载。结论有效,但"停实例"本轮不是脚本完成的(待观察:④ 的 scope 匹配在 47 上是否可靠 —— 若不可靠,未来运行时新 bundle 不会生效)。
  • 根因实证:0.3.11 lib/host/mcn/config.js = const DB_PATH = join(homedir(), ".dsh", "mcn-plugin.db");实例 HOME=<userRoot>/ws ⇒ 落 ws/.dsh/(实测该目录不存在)⇒ unable to open database file。0.3.12 = const DSH_DIR = process.env.DSH_HOME ? join(DSH_HOME, ".dsh") : join(homedir(), ".dsh") ⇒ home/.dsh/mcn-plugin.db ✅。
  • B ④ 校验(全部通过):实装 package.json = 0.3.12;实装 config.js:10 含 DSH_DIR 修正;以 uid 114801 读 home/.dsh/mcn-plugin.db = 10 表 / 444 行(account_videos 362 · account_video_source 39 · rewrite_log 16 · account_video_analysis 12 · hot_accounts 5 · account_persona 4 · storyboard_log 3 · script_review 2 · account_analysis 1 · creative_log 0);bundles 含 dsh-plugin-mcn-suite;日志侧 21:19 起 unable to open database file 计数 0,改为 [dsh-plugin-mcn] 数据就绪: cached=true count=5(21:21:05)。
  • 🔴 判版本别信日志:lib/index.js:92 的 loaded v${VERSION} 取自常量,0.3.12 仍打印 loaded v0.3.10(本轮一度据此误判成"没装上新版")⇒ 判版本只看 node_modules/<pkg>/package.json(已写入 MEMORY.md)。
  • 回滚:cp /opt/dsh/backups/dsh-plugin-mcn-suite-0.3.11-20260920-211901.tgz /var/lib/dshs/business-plugins/dsh-plugin-mcn-suite.tgz → 重跑同一条安装命令(不带 --expect)。
  • 新发现:home/mcn-plugin.db(77,824 B · 09-12)= 同样 10 表的历史残留,与权威库 home/.dsh/mcn-plugin.db 并存 ⇒ 是否清理需拍板(破坏性)。MCN 线剩余尾巴 = 插件 VERSION 常量滞后(要出 0.3.13)。
  • 权限/边界:本轮只改 web/*.html(4 个)、池内 tgz、/opt/dsh/{web,scripts,backups};⛔ 未 commit/push、⛔ 未动官方 dsh、⛔ 未碰覆盖网络线 relay/nginx/控制面、⛔ 未动云安全组。

21:26–22:1x · 序47 执行棒 2(步 6–7 落地并真跑验收 + 收口)

  • 步 6(S4 实例凭据平台侧代签)✅:新增 src/net/relay/instance-credential.ts(投递+续签唯一实现)+ onInstanceStart 钩子(LocalSpawner / RemoteSpawner 两路都挂,⛔ 失败只记日志不阻断起实例); 真跑 issued hostId=d-<uid>-c5b31dd9,文件 /var/lib/dshs/users/<uid>/home/.overlay-device.json = 0600 属主=实例 uid(⛔ 非 root),relay 密钥表 7→8、registry 4→5, verifyPeerGrant / verifyProof 双 ok。
  • 步 7(S5 续签)✅:把凭据 expiresAt 人为置过期 ⇒ 续签表 tick 21:54:20 扫=1 续签=1 失败=0; 台账 grant_renewals 1→2;nodeKey / hmacSecret 复用不变(不轮换 ⇒ 不制造 MAC 拒收窗口); 密钥表 8→8 键零写(*.bak-* 仍 29)⇒ 复验"可再次准入"通过。
  • 🔴 新坑 1:src/fs/user-fs.ts 的 HOME_FILE_NAMES 是控制面 + worker agent 两端共用白名单 ⇒ 改它必须两个单元都重启(只重启 dshs 时真跑报 reason=credential-file-unreadable … bad_path)。
  • 🔴 新坑 2:PG users 表的身份键是 id(text uuid),同表另有一个 uid(bigint)—— 按"优先 user_id"的自动选列会误选 uid(报 operator does not exist: bigint = text); audit_log 的用户身份列名是 actor(⛔ 不是 user_id)。
  • 质量门:tsc 0 错 | npm test 248 / 247 过 / 0 败 / 1 skip(较棒1 基线 241/240 增 7)| check-layering.mjs 无新增违规(现存 5 条在基线内)。
  • 部署:6 个 lib 文件 scp(1 新增 + 5 覆盖)⇒ 备份 /opt/dshs/lib-bak-seq47e2-20260920-214248; dshs MainPID → 989967、dshs-worker MainPID → 990718。
  • 收口四件套全过:① 读数落盘 tmp/seq47/readings-exec2/(含 30/31/32-* 清理读数)② 回填 序47 单 §⑬ 执行棒 2 记录 + 交接单/README.md §一 本行 + 档案 145(新增 §3.4 / §4.3,改 §五 回滚点 · §六 未做表 · §七 遗留) ③ md5 三方一致、CR=0 纯 LF(README 166 为既有 CRLF)、镜像 600/644 root:root ④ --release-exec 已释放(复查无全局锁)。
  • 测试账号 seq47inst2 用完即删:users / sessions / audit_log / overlay_devices / dsh_instances 五表对该 uid 复查全 0(⛔ 实例 home 内凭据文件按 §⑦.6 保留)。
  • 下一棒:序47 执行棒 3(步 8–9)automation 4ef4fc56-1f37-4d57-8980-d00ccadffae9 @ 2026-09-20 22:15。

22:07–22:2x · MCN 线 0.3.13 棒 —— 消除「日志版本号不可信」+ 核实安装脚本 ④

  • 锁:22:07 / 22:09 / 22:10 三次 --claim-exec 被「序47执行棒2补记」(覆盖网络线,22:07 起)占住 ⇒ 按 R9 不接管,先做只读取证(读码 + 47 上只读实测);22:10:56 抢到(对方已释放)。收口后释放。
  • A 版本真源统一(三处,全部改为从 package.json 读): ① dsh-plugin-mcn-suite/lib/index.js:26 的 VERSION = "0.3.10" → IIFE 读 ../package.json(这是 :92 日志打印源,本轮主目标); ② 同包 lib/skills-installer.js:30 的 SUITE_VERSION = "0.3.0" → 读 PKG_ROOT/package.json(仅当 skills/.manifest.json 未给 version 时的兜底); ③ scripts/build-mcn-suite.cjs 生成 manifest 处原为硬编码 suite: "…@0.3.0" / version: "0.3.0" → 改用 pkg.version。 package.json 0.3.12 → 0.3.13(description 补 0.3.13 段)。⚠️ lib/host/* 由 build 覆盖同步,与本次改动无关。
  • 构建:node scripts/build-mcn-suite.cjs all(⚠️ 默认 mode = client ⇒ 不打 tgz、不生成 manifest,必须显式带 all)。 产出 dist/dsh-plugin-mcn-suite-0.3.13.tgz 436 条目 / 1.90 MB / md5 f47a07b9a2a889770e35315e22156e90; 包内自检:package.json 0.3.13、skills/.manifest.json @0.3.13、index.js 与 skills-installer.js 两处版本段均为读 package.json 写法。
  • 投放:备份 /opt/dsh/backups/dsh-plugin-mcn-suite-0.3.12-20260920-221319.tgz(md5 f1d5da72… · 与池内逐字节一致)→ 池内换新(chmod 644 · md5 f47a07b9… 两端一致)→ install-plugin-for-user.cjs --home …/cce6d1cd…/home --uid 114801 --profile web --tgz … --expect 0.3.13 ⇒ ⑤ 实装 = 0.3.13 / OK(脚本自身 md5 仍 1200eb75…,⛔ 未改)。
  • 🎯 核心验收(目标达成):实例日志(22:00 起)[mcn-suite] loaded v 只出现 v0.3.13(1 次)—— 打印版本首次与实装一致,误判源消除。scope dsh-114801-75139b0a 22:14:11 起 active;unable to open database file 计数 0;数据就绪: cached=true count=5。
  • 其它校验:实装 node_modules/dsh-plugin-mcn-suite/package.json = 0.3.13;以 uid 114801 读 home/.dsh/mcn-plugin.db(属主 114801)= 10 表 / 444 行(逐表与上一棒一致:account_videos 362 · account_video_source 39 · rewrite_log 16 · account_video_analysis 12 · hot_accounts 5 · account_persona 4 · storyboard_log 3 · script_review 2 · account_analysis 1 · creative_log 0);bundles 含该包。
  • B 安装脚本 ④ 判「非缺陷」—— 已核实:47 上以 root 与 setpriv --reuid 114801 两种身份跑 systemctl list-units --type=scope --all --no-legend --plain,均列出 live dsh-114801-*.scope(rc=0),unit 名与 install-plugin-for-user.cjs:183 判据(indexOf('dsh-'+UID_+'-')===0 + slice(-6)==='.scope')吻合 ⇒ 匹配逻辑在 47 上能命中。⚠️ 脚本 setpriv 只包裹 pnpm add(:141),④ 由 root 执行 ⇒ 权限不是障碍。本轮 ④ 报「已停 0 个」=时序:④ 执行瞬间旧 scope 已停、新 scope(22:14:11)未起;新包最终仍生效(Manager 重启路径)。⛔ 未改脚本(判据要求"能命中即非缺陷")。
  • 回滚:cp /opt/dsh/backups/dsh-plugin-mcn-suite-0.3.12-20260920-221319.tgz /var/lib/dshs/business-plugins/dsh-plugin-mcn-suite.tgz → 重跑同一条安装命令(不带 --expect)。
  • 待拍板(未动手 · 破坏性):home/mcn-plugin.db(77,824 B · Sep 12 · 与权威库同 10 表)是否清理 —— 与本轮无关,仍挂。
  • 收口:MCN 线本轮收官(VERSION 尾巴已清)⇒ ⛔ 不排下一棒(唯一剩余 = 上述待拍板项);为"抢不到锁"预排的 22:18 接续棒已撤销。
  • 边界:改动 = dsh-plugin-mcn-suite(3 文件)+ scripts/build-mcn-suite.cjs + 池内 tgz + /opt/dsh/backups/。⛔ 未 commit/push、⛔ 未动官方 dsh、⛔ 未碰云安全组与覆盖网络线 relay/nginx/控制面。
  • 两条小教训:① build-mcn-suite.cjs 默认只跑 client 段 ⇒ 要 tgz 必须带 all。② mktemp -d 给的是 Git Bash 虚拟 /tmp,原生 node.exe 不认(解析成 D:\tmp\… ⇒ MODULE_NOT_FOUND)⇒ 传给原生程序的临时路径必须用真实 Windows 路径。
  • 🔴 收尾新坑(环境,下次必再遇):会话中途裸 bash 会被解析到 WSL 启动器(C:\Windows\System32\bash.exe)⇒ 被安全策略拦、退出码 1、输出乱码(襜輣繈顣…)⇒ 看起来像"脚本没执行 / 锁还在";本机可行路径 = 绝对路径 E:/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/bin/bash.exe(本次的 --status 与 --release-exec 都是用它跑通的)。⚠️ 该 bash 的 PATH 极简(head/sed/dirname 均缺)⇒ 调用时⛔ 别加管道(本次 | head 就把命令整条搞挂,rm 也走了 safe-delete shim 而失败)。
  • 锁:22:4x 已 --release-exec ⇒ 脚本回显「✓ 已释放全局执行锁」,复查通过;22:5x 为清理项 B 二次抢锁(--claim-exec "MCN线待清理项B")。

22:5x · MCN 线待清理项 B —— 孤儿库按拍板移出(用户选 B)

  • 拍板:用户明确选 B(先移出,可逆)。

  • 取证(先只读,再动手):<home>/mcn-plugin.db(77,824 B · Sep 12 23:20 · 属主 114801 · md5 eeda97f443c3e3ab802588dadd50dea8)—— lsof 无持有;已装插件里对该文件名的唯一引用是 lib/host/mcn/config.js:11 = join(DSH_DIR, "mcn-plugin.db")(指向 $DSH_HOME/.dsh/),没有任何代码指向裸 home/ 根 ⇒ 判为 0.3.11 之前 homedir() 口径遗留的孤儿库。

  • 动作:mv <home>/mcn-plugin.db → /opt/dsh/backups/mcn-plugin.db.bak-20260920(属主 114801 / mtime Sep 12 23:20 均保留,md5 一致);原位置已空;权威库 home/.dsh/mcn-plugin.db(2,383,872 B)未受影响;home/ 根已无 .db。

  • 回滚:mv /opt/dsh/backups/mcn-plugin.db.bak-20260920 <home>/mcn-plugin.db(无读者 ⇒ 回滚不影响运行)。

  • 🔴 本机环境坑(补充上一段):shim 环境里本地管道工具(grep / head / sed)同样不可用 —— 不只是 bash 解析错。本次一条 ssh … | grep -v "post-quantum…" 因本地 grep 不存在而整条命令失败(退出码 127、ssh 根本没执行),看着像"取证成功但无输出" ⇒ 本机命令⛔ 一律不加管道;远端管道(Linux 侧 grep)不受影响,可正常用。

  • 🗣 拍板(22:0x):交接单/README.md 行尾 CRLF 整理 → 用户选 B = 不做,继续挂遗留、等该文件下次有实质改动时顺带做(⛔ 后续棒不再上抛)。已回填 序47 单 §⑬④.4 + 档案 145 §七.3;两份文档重同步,md5 本机=镜像(cb7226dc… / a3b8ee91…)、CR=0、镜像 600 root:root。

22:45–23:0x · 读「数据库专区」规范 + 自审落点合规

  • 新规范已立:文档库新增 dsh-server-docs/数据库/(DB-00 入口 / DB-01 接入指南 / DB-02 表结构台账与迭代 / DB-03 插件数据面规范,2026-09-20 22:46–22:49 落,起草会话 IM插件数据面-专门文档;INDEX.md 同步已改;草案双源已清)。
  • 判据五条:① 先判落点(平台/房间 ⇒ 平台库;某用户自己的 ⇒ 实例 home;本机运维 ⇒ Worker 库;大对象 ⇒ 桶)② 表名即归属(无前缀 = 内核;p_<pluginId>_* = 插件)③ 不直连(房间维度插件不持连接、不写 SQL,走 im.data)④ 双后端(sqlite + pg 都要建得起来)⑤ 只增不减(加表/加列带默认值/加索引;改类型删列走四步跨版本)。
  • DB-01 情形 3(实例侧)原文:落点 = <userRoot>/home(由 DSH_HOME 决定);平台侧若要写它必须走 UserFs——直接 fs 写 = 静默空操作;实例内插件可直接用实例本地文件 / 本地 SQLite(不进平台库);坑 = ⛔ homedir() 拼路径必错,正解 = process.env.DSH_HOME ? join(DSH_HOME,'.dsh') : join(homedir(),'.dsh')。
  • 自审今日 MCN 工作:✅ 落点与路径公式与规范正解逐字一致(<userRoot>/home/.dsh/mcn-plugin.db,未进平台库);✅ 插件属情形 3,用本地 SQLite 不违规(DB-03 红线针对房间维度)。❌ 方式违规:平台侧写实例 home 我是以 root 直接 cp/mv(未走 UserFs);❌ 双写风险未收口:本机 C:/Users/Administrator/.dsh/mcn-plugin.db 与服务器那份是同一逻辑数据(规范:⛔ 同一份数据不双写 = 脑裂);❌ 流程违规:未先对该专区判据即动手。
  • DB-02 权威:src/db/schema.ts 已到 v13(v12 = 序㊻ overlay_devices;v13 = 序㊼ 会话/设备类型 + 上限索引);v14(规划)= IM 房间内核 rooms/room_members/messages,只需"双后端 + 只增不减 + 幂等 + 台账同一提交加行"。
  • 专区自身遗留:🔴 DB-03 头部三行过时未改 —— 仍自称"草案 / 暂存工作区 / 待落点 04-调整方案/143-…",实际已落 数据库/DB-03-…;D 单 §九 顶部指针未核;python scripts/docs-audit.py(RC=0 / 无 P0)与四档纯 LF 校验均未跑;数据库/ 与 INDEX.md 未提交(git ?? 数据库/ + M INDEX.md)。

规划棒① · carbon插件线(23:32–23:4x)—— 立项 + 出 M1a 交接单 + 建入口 + 登记执行棒①

这是什么:按用户要求「使用接续会话的方式,按照决策方法,把 carbon 项目改造为 dsh 插件」,把既有的可行性方案落成一条自动接力链。本棒 = 规划棒(只出单,不改码、不部署)。

决策方法自决项(§4.4 逐条走完,均未上抛):

  • D1 第一棒不部署 Carbon,只用 stub MCP server 验证契约 —— 依据「更小改动达同一效果」+「失败代价对称」:先起 Supabase 全栈要 Docker + 数 GB,而它并不回答本棒的问题。失败代价从「一整套栈」降到「两个临时文件」。
  • D2 stub 用托管 Node(22.22.2)单文件零依赖;D3 若 dsh 只支持 stdio ⇒ 自研 stdio↔HTTP 桥(约 50 行,不引第三方包,避免新增供应链面);D4 测试用 mksess.cjs 一次性用户(role=active),R4 口径。
  • 拒绝上抛:方案里那三个待拍板项(是否长期投入 / Carbon 部署归属 / 是否开放普通用户)本棒不需要 ⇒ 条件式非阻塞,照常接力;门禁写在入口 §2.1,走到「部署 Carbon」那一步才停。

关键设计洞察:M1 的第一步不是「把 Carbon 跑起来」,而是「用 stub 回答 dsh 的 MCP 挂载契约」。MCN 插件的 lib/mcp.js 是 stdio 形态(npx myai-mcp),而 Carbon 的 /api/mcp 是 HTTP streamable —— 两者传输不同 ⇒ 若 dsh 只支持 stdio,需要一个极薄的桥,形态 A 仍成立。

核实到的路径事实(省掉下一棒探索):平台仓根 = D:/github/dsh_shenxian;poc/ 在该仓根下(poc/business-plugins/lib/index.js 恰 21 行、client.js 4001 行 —— 与 C 单取数一致,交叉验证通过);MCP 先例实际在 D:/dshworkspace/plugin_package/dsh-plugin-mcn/lib/mcp.js(另有整包版 dsh-plugin-mcn-suite/lib/host/mcn/mcp.js)。

本棒产出:

  • 交接单 dsh-server-docs/交接单/交接单_carbon插件-M1a-MCP挂载契约验证_20260920.md(8 段;md5 27f9f7ae40577116ea21c8e5c3213ed2)
  • 入口 aliyun-dsh-server/接续入口_carbon插件线_20260920.md(md5 bb172619974bcb51b0616e7fba3554b7)
  • automation 770f10d1-b227-458f-ac4f-9d8c7e2ba743 @ 2026-09-20 23:47(执行棒①)

环境坑(本棒新踩,别再犯):用 bash -c "python -c \"...\"" 双引号包含反引号的文本时,bash 会把反引号里的内容当命令替换执行 —— 本次日志里 4 处路径/md5 被替换成空,且 mcp.js 被 bash 当脚本执行了一遍(报 //: Is a directory)。⇒ 写含反引号/$ 的长文本一律改用 Edit/Write 工具,或 heredoc 用单引号定界符(<<'PY')。

链路状态:规划棒① ✅ → 执行棒① ⏳ 已登记(23:47) → 规划棒②(按 A/A′ 出 Carbon 对接单)→ …

StoryForge 插件线 · 序1 执行棒(2026-09-20 23:45 → 09-21 00:05)

线名:StoryForge 插件线 | 入口 = 接续入口_StoryForge插件线_20260920.md(§0 最新行 + §2 首行 = 唯一执行依据)| 方案 v2 = E:\ProgramData\AIProject\dsh-plugin-forge\StoryForge改造为dsh插件_可行性与落地方案_20260920.md

做了什么:把 StoryForge v3.9.1 上游改造成「不依赖 dev server 代理、可同源静态托管」的产物 + 产出 dsh 插件三件套骨架(形态 = 候选 A 同源 iframe 内嵌,不 fork 上游)。

关键事实(省掉下一棒探索):

  • 上游副本 E:\github\storyforge @ v3.9.1 / sha 7286497e;9 改 1 删 2 新增(+103 / −245)。
  • 🔴 单子口径被实测推翻:本机 coreutils 不缺失 —— dirname/ls/mkdir/md5sum/readlink/realpath/basename/sed/awk 全在 /bin,bash scripts/handoff-guard.sh 可正常运行(其 【1c】 能直接告诉你全局锁在谁手上)。序1 单子 §二 前置 5 的「全部缺失」已就地证伪并修正;⛔ 别再据此写新单。
  • 🔴 dev 代理实测 12 条(deepseek / openai / kimi / claude / nvidia / doubao / agnes / longcat / opencode / siliconflow / qwen / glm),不是单子写的 15 条。
  • 🔴 PWA 不是「只删 vite 插件」就完:SW 是应用主动注册的(src/lib/pwa/register-service-worker.ts 注册 /storyforge/sw.js)⇒ 必须同时改注册器;index.html 里「注销 SW + 清 Cache Storage」原本只在 localhost 执行,托管形态必须改成全主机执行(否则旧 SW 跨部署继续喂旧缓存)。⚠️ 同文件有回归测试 tests/regression/R-CF20260702-local-pwa.test.ts 断言 shouldRegisterStoryForgeServiceWorker 的返回值 ⇒ 改它要保语义。
  • 🔴 同源取址口径:新增零依赖模块 src/lib/ai/same-origin-llm.ts(/storyforge/api/llm/<provider>/<sub> + /storyforge/api/gist),src/lib/types/ai.ts 的 PROVIDER_PRESETS 17 条 baseUrl 全收敛(含 ollama 的 http://localhost:11434/v1);src/lib/ai/proxy-endpoints.ts 整文件删除;provider 收敛 = 18 条中仅 deepseek 启用、其余「待启用」(白名单模块 src/lib/ai/provider-allowlist.ts)。
  • 🔴 假阴性坑:grep "/storyforge/api/llm/" dist/ = 0 命中不代表常量没进产物 —— 压缩后是 prefix + '/' + provider 三段拼接,带尾斜杠的整串本就不存在;要 grep 不带尾斜杠的前缀。
  • 上游 package.json 一个字未动(npm ci 在 Node 22.22.2 直接成功,未触发 engine 报错,降级路径没用上)。
  • ⚠️ 本机行尾/编码坑(再次踩到):Python 里写 b'含中文' = 语法错,脚本编译期就失败(一步都不会执行,且 stdout 只有一行 rc=1,极易被读成"跑过了")⇒ 二进制模式下判子串一律用 '…'.encode('utf-8')。

验收六条(全绿):tag v3.9.1 | dist/ 内 https?://api\. + 127.0.0.1: 0 命中 | dist/index.html OK | 静态冒烟 /storyforge/=200 / sw.js=404 | node --check lib/client.js rc=0 |脱管导出 0 命中。 ⚠️ 未做浏览器渲染验证(本机无 Chromium)⇒ 验收只到 L2(命令)+ L3(对账),不得说成「页面正常渲染」。

本棒产出:

  • 单子 dsh-server-docs/交接单/StoryForge插件线-序1-上游改造与插件骨架.md(已回填 §八 + 新增 §九「交给序2/序3 的输入」;30,573 B/纯 LF);同步副本 = E:\ProgramData\AIProject\dsh-plugin-forge\交接单_StoryForge插件线-序1_上游改造与插件骨架_20260920.md
  • 插件包 E:\ProgramData\AIProject\dsh-plugin-forge\dsh-plugin-storyforge\(6,510,823 B;version 0.1.0;inject=[];dshCompat=">=0.1.5 <0.2";host apply() 只写 .dsh/.dsh-storyforge.log;主 bundle md5 9dd5ab9855f8cdd8fdabefa901c1e09d)
  • 入口 接续入口_StoryForge插件线_20260920.md(§2 已推进到序2)

链路状态:规划棒 ✅ → 序1 执行棒 ✅ → 序2 规划棒(部署与实例验收立单,automation 4e42c296-ab86-47b7-aa79-74b520460565)→ …|🔴 未触发拍板项(模型 KEY 来源 / 每用户配额 / 平台是否承担调用成本 —— 三项全属序3)。 下一步:序2 规划棒 —— 内容 = 「实现 host 半区 /storyforge/* 静态 + SPA 回退 → 打包 → 候选池 → 用户实例启用 → 真机验收」,⚠️ 含 host 半区代码实现,不是纯部署。

🔴 锁协议缺陷(本棒实测 · 值得单独立项修):本棒持有的全局执行锁在 00:0x 被外部释放(我并未释放;随后 rmtree 抛 FileNotFoundError ⇒ 那时锁已不在)。取证结论(事实,非推测):dsh-server-docs/scripts/handoff-guard.sh:56-58 的 --release-exec 分支是裸 rm -rf "$LOCKEXEC",不校验 OWNER 归属 ⇒ 任何会话只要跑一次收尾,就会无条件删掉别人的锁。本棒期间确有并行会话(carbon 插件线执行棒①,automation 注册于 23:47),其收尾极可能命中该路径。⚠️ 影响:R9「不得接管 / 不得删锁」在脚本层没有任何强制,全局互斥可被无声破坏。✅ 本棒未观察到内容冲突(交接单/README.md 与序1 单的 mtime 均 = 本棒写入时刻,无第三方痕迹),但机制缺陷成立。⇒ 建议:给 --release-exec 加 OWNER 校验(非本人 ⇒ 拒绝并打印占用者)。⚠️ 本棒未改该脚本(不在序1 范围);本棒释放锁前已自行加 OWNER 断言(非本人则拒绝释放)。