- 变更规模:新增 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/ 知识文件,按口径入库)
96 KiB
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.pyRC=0 / 无 P0;新档 CR=0(纯 LF)。 - 取证结论(跨会话可复用,勿重查):
src/应用层room|chat零命中;schema.ts11 个迁移无消息 / 房间 / 成员表; 用户间通道故意隔离(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.pyRC=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 注入 envDSH_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.pyRC=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.pyRC=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位为空(与记忆里那支 automation90049d4c…一致)。双证: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.pyRC=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.pyRC=0 / 无 P0;INDEX.mdCR=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.pyRC=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-专区入口.md644(README 类)。 - ⚠️
INDEX.md未推(主动跳过):远端 md58350cbc3≠ 本地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(md50b84b3bc…);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.mjs28 条全绿(新增「快照对象取 .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-entry0.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/joinHTTP 端点未实现: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 分层中继后续再考虑。
三份产物(本轮已落):
- 决策与改造方案 = 工作区
docs/覆盖网络/覆盖网络_设备登录接入_决策与改造方案_20260920.md(§2 现状取证表 + S0–S4 改造项 + 容量估算 + §6 风险)。 - 交接单(执行载体)=
dsh-server-docs/交接单/覆盖网络-序46-设备登录接入网络.md(8 段:目标/只读前置/范围/决策点/步骤/回滚/回报/依赖)+ 已登记交接单/README.md §一。 - 对接文档 = 桌面线
对接单_桌面端接入覆盖网络_给平台会话_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)✅。
三条硬发现(⛔ 后续棒直接照用,全写在 单 §⑨)
- 🔴 设备形态 = 不声明端口的拨号方(relay 判据互斥:
dialer-must-not-declare-ports/no-ports)。main.js --client强制--ports⇒ 表达不了设备形态(实测被具名拒 9 次)⇒ S1 桌面线必须用RelayClient({ports: [], dialer: true, identity})(Manager 拨号通道同款装配)。 - 🔴
dialers桶 = 拨号方名单 ⇒ S2 若用网级通配u:<tenant>/*,整张租户网都被判为拨号方 ⇒ 该网内不能有声明端口的节点。 - 🔴
derive --apply只表达一张网,且落点50-overlay-dialers.conf与手写dialers.conf抢同一个 env 键、字典序上还被压掉 ⇒ 真跑会把ops:manager丢掉且连租户网也不生效(静默失效)。本轮没用--apply,改合并写手写 drop-in。⇒ S2 的前置:先解决"多网合并派生"。 - ⚠️ 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 一致);锁已释放。
本轮两条硬发现(后续棒必须处理)
- 🔴 relay 的 HMAC 密钥表在进程启动时装载一次 ⇒ 新增设备必须重启 relay 才能拨(实测
unknown-host)。这是 §① 终态"人工零介入"的真障碍(与网级放行无关,在准入链更前面)。已排除"放开 REQUIRE_IDENTITY / 租户共享 secret"(都放宽权限);最优解 = relay 热加载relay-keys.json⇒ 归入步骤 5/6。 - 🔴
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 条(**热加载已启用**);relayActiveEnterTimestamp=16:57:30未变,17:05:20🔄 密钥表热加载:7 → 8 条(新增 1:…-faf3784f)→AUTH OK … ports=[],拨号器退出码 0;清理后 17:08:398 → 6 条(移除 2);keyReloads=3keyReloadFails=0refused=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 都不够。
本轮其他具名发现
- 🔴 新用户"零介入"的下一个真障碍:
DSHS_RELAY_DIALERS是手写 drop-in ⇒ 新租户网无人加白名单,新用户即使有有效 grant + 密钥仍会在身份闸门被拒(dialer身份拿不到 ⇒no-ports)。与"derive 多网并集口径"同源 ⇒ 步骤 6/7 硬前置。 - ⚠️
/opt/dshs/scripts/overlay-relaykey-add.cjs在 47 上不存在(MODULE_NOT_FOUND)⇒ 按该工具文档删密钥会失败;调用方吞 stderr 时 = 静默失败(我踩了一次)。与"远端 CLI 与 HEAD 不一致"同族(不止 md5 不同,还有缺失)。 - ⚠️ 取证脚本假信号(已修):复用拨号脚本
--ticks N只在N=0时排周期 ⇒--ticks 2永不退出 ⇒ 外层timeout收尾给退出码 124,而STATUS {"state":"up"}明明成功。已改为"真排 N 次"。 - ⚠️ scp 路由文件后必须重启
dshs:本轮首次漏重启 ⇒ 跑的是旧模块,现象 = 回执里grant.doc=undefined(形状仍旧)⇒ 这类"改了没生效"在回执形状上可直接辨认。 - ℹ️ 平台侧
/opt/dshs/lib/net/relay/server.js落后两代且是死代码(src/无人import)⇒ 本轮刻意不动它(避免夹带步骤 2 改动进无关文件)。 - ℹ️ 47 上
DSHS_OVERLAY_SIGNER_KEY_FILE/DSHS_RELAY_KEYS_FILE未显式配,走代码内中性缺省,且缺省值恰好 = 生产路径(实测签名者私钥读到、密钥表写对)⇒ 无需改 drop-in。 - ℹ️ 清理后密钥表 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)、FKusers(id) ON DELETE CASCADE、CKstatus∈{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'⇒ 403device-revoked)② 台账写入(失败 ⇒ 500device-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+ relayAUTH DENY … why=identity-expired retryable=false,退出码 1 - 续签通:端点
renewals=2、租约后移一天;PGgrant_renewals=2 | lease=2026-09-21 17:56:10+08;同设备再拨AUTH OK/STATUS state=up/退出码 0 - 停发/恢复:
revoke200 ⇒ 端点 403device-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;opsrefused=0、online=[manager,w-106] - 部署对账:10 个产物 scp 后 md5 10/10 与 47 逐字一致(平台侧 8 + relay 侧 2);文档两处(序46 交接单 + 117 参数表)双端 md5 一致且 CR=0(纯 LF)
本轮具名发现(要带走的)
- 🔴
u:*/*是「新用户零介入」的唯一静态表达(租户 id 运行时才生成);代价 = 租户网内身份面整网放宽(准入面一字未动)。 - ⚠️ 47 上没有
/etc/dshs/overlay-signer-public.pem;受信公钥要从/etc/dshs/overlay-signers.json的doc.signers[0]取(bad464dfd53048ef…)。⛔ 别按文件名推。 - ⚠️
schema_migrations只有(version, applied_at)两列,⛔ 无name。 - ⚠️
npm run verify在scripts/verify-platform-admin-section.mjs崩(require()+ top-levelawait混用)—— 仓库既有缺陷,本轮未修(不在范围);npm test全绿。 - ℹ️ 平台侧
/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)冒充「真机客户端」(上一棒明令禁止的形态)。
四条具名依据(桌面侧产物不存在)
- 桌面线 automation 台账 10 条,无「第 5 棒」;第 4 棒(
0142e285-…)memory 原文:「⛔ 未登记下一棒 —— 第 5 棒(接入覆盖网络)需--invite+--signer-pub,「需用户提供凭据」⇒ 边界外 ⇒ 链条暂停等拍板」。 - 对接单 §4 指定落点
E:/dsh-worker-dev/overlay/不存在。 - 桌面线工作区 13 个可执行脚本全是 S1/S1′ 起实例/起 worker 探针(
_devkit/s1-*.ps1·s1-*.mjs·s1p-verify-instance.sh),零覆盖网络拨入代码。 - 全工作区命中
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 棒直接用)
- 🔴 「登录态」=
sidcookie,无 token / 设备码通道:src/web/middleware/authn.ts:16-17只解析sidcookie;POST /api/auth/login(src/web/routes/auth.ts:282)用username+password登录并set-cookie⇒ 桌面客户端必须自维持 cookie jar。 - 🔴
/api/auth/login是「单活跃会话(last-wins)」(auth.ts:294-296:注释 +deleteUserSessions(user.id))⇒ 设备登录取会话会顶掉用户浏览器会话;反之用户再登录 ⇒ 设备会话失效 ⇒ 无法续签、租约到期掉线。触发条件:真机常驻形态落地前必须定「设备会话 / 浏览器会话」并存口径。 - ⚠️ §⑧ 风险 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 / md5f1d5da720bd9fedf1da64fe54d67d189);逐成员比对 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,基线 HEAD1242d07,⛔ 未提交):schema.tsv13 迁移(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_evicted6 行;revoke命中条 401 / 另一条 200 / 不存在 404;logout-all⇒{ok:true,revoked:19}全 401;会话列表 token 字样 0 行;test/overlay-device.test.mjs33 pass;npm test241 项 / 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-workflowR4 已改写(last-wins 理由失效 + 专用测试账号省事法 +sidcookie 坑),三处 md5 一致6a88c7daa0b1ce482d8e206e2cd02c25。 - 下一棒已登记 = 序47 执行棒 2,automation
90049d4c-8080-4a73-a1d8-908e06f91ecc(一次性 · 21:18)。 - 🔴 本轮新固化坑(会重犯):①
sidcookie 带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(md50b84b3bc485426c40307fd076b922a44,与源逐字节一致)。 - B 投放:池内
dsh-plugin-mcn-suite.tgz→ 0.3.12(md5f1d5da720bd9fedf1da64fe54d67d189· 436 条目 · 1,989,494 B ·chmod 644)。scripts/install-plugin-for-user.cjs→/opt/dsh/scripts/(两端 md51200eb75d0fd6537ac90f3c687dbbdc2一致);执行--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)以新 scopedsh-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_videos362 ·account_video_source39 ·rewrite_log16 ·account_video_analysis12 ·hot_accounts5 ·account_persona4 ·storyboard_log3 ·script_review2 ·account_analysis1 ·creative_log0);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人为置过期 ⇒ 续签表 tick21:54:20扫=1 续签=1 失败=0; 台账grant_renewals1→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)。 - 质量门:
tsc0 错 |npm test248 / 247 过 / 0 败 / 1 skip(较棒1 基线 241/240 增 7)|check-layering.mjs无新增违规(现存 5 条在基线内)。 - 部署:6 个 lib 文件 scp(1 新增 + 5 覆盖)⇒ 备份
/opt/dshs/lib-bak-seq47e2-20260920-214248;dshsMainPID → 989967、dshs-workerMainPID → 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.json0.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.tgz436 条目 / 1.90 MB / md5f47a07b9a2a889770e35315e22156e90; 包内自检:package.json0.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(md5f1d5da72…· 与池内逐字节一致)→ 池内换新(chmod 644· md5f47a07b9…两端一致)→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 次)—— 打印版本首次与实装一致,误判源消除。scopedsh-114801-75139b0a22: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,均列出 livedsh-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 · md5eeda97f443c3e3ab802588dadd50dea8)——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、镜像 600root: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 段;md527f9f7ae40577116ea21c8e5c3213ed2) - 入口
aliyun-dsh-server/接续入口_carbon插件线_20260920.md(md5bb172619974bcb51b0616e7fba3554b7) - 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/ sha7286497e;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_PRESETS17 条 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;version0.1.0;inject=[];dshCompat=">=0.1.5 <0.2";hostapply()只写.dsh/.dsh-storyforge.log;主 bundle md59dd5ab9855f8cdd8fdabefa901c1e09d) - 入口
接续入口_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 断言(非本人则拒绝释放)。