回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复: - 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录) - tmp/(32.4 M,按接续棒命名的过程临时区) - .workbuddy/tmp/(39.5 M) - 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物) - tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留 入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与 接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、 .workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。 排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、 打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
48 KiB
工作日志 · 2026-09(第 23 片)
⚠️ 本目录日志已按【月】分片(2026-09-15 用户定,单文件 ≤50 KB):同月续片
2026-09.md/2026-09-下.md/2026-09-下2.md/2026-09-下3.md/2026-09-下4.md/2026-09-下5.md/2026-09-下6.md/2026-09-下7.md/2026-09-下8.md/2026-09-下9.md/2026-09-下10.md/2026-09-下11.md/2026-09-下12.md/2026-09-下13.md/2026-09-下14.md/2026-09-下15.md/2026-09-下16.md/2026-09-下17.md/2026-09-下18.md/2026-09-下19.md/2026-09-下20.md/2026-09-下21.md/2026-09-下22.md/2026-09-下23.md写入约定:一律 append 到当月最后一片;该片超 50 KB ⇒ 新建2026-09-下N.md;⛔ 不要再按日新建2026-09-DD.md。 覆盖来源:2026-09-15.md 上一片:2026-09-下21.md下一片:2026-09-下23.md
11:1x–12:0x — T08 执行:S0 ✅ + S1 ✅(用户「确认执行」)
- 锁纪律:
--claim-exec "exec-cluster-1a"→mkdir 交接单/.doing-T08+OWNER→ 台账行改 🔄 执行中(同一动作完成)。中途停 ⇒ 已按纪律释放全局锁(.doing-T08+ OWNER 保留作续做凭证)。 - S0 基线冻结:47 =
/opt/dshs(无 .git ⇒ 用文件 hash:lib/=8423ed1c…、web/=7e001f0a…、package.json=878f7e9a…、unit=545ebe09…、DB 131072 B、用户 6);106 =/root/dsh-users-platform(另起/opt/dshs-cluster隔离)。 - S0 三处修正:① 不在 47 跑 smoke(47 的
/opt/dshs/scripts/无fake-dsh.mjs,且不必生产留痕)② 不建分支(工作树与其他会话共享 + 已有 11 个别人的未提交改动,建分支会把它们带走)③ 106 测试环境独立(端口 13080/13081)。 - 查清 smoke 性质:
ci.sh明说不纳入;实测自包含(进程内dbPath:':memory:'+ stand-infake-dsh.mjs);DSHS_DB_URL优先于 dbPath ⇒ 同一组 smoke 可跑 PG(S1.4 的可行基础)。 - S1 完成:106 装 PG 15.19(独立数据目录 +
pg_ctl手动起 + 端口 15432 + 只绑 127.0.0.1,未建 systemd 单元,回滚=rm -rf)。- S1.4 结论:SQLite 6/8 == PG 6/8,失败项完全相同(
smoke-domain/smoke-isolation)⇒ 两后端行为一致。 - 假阳性已排除:PG 侧
dshs_smoke有 10 张平台表 +schema_migrations有记录 + smoke 数据真的落库。 - S1.5 回滚演练 ✅:加/去
DSHS_DB_URL各起一次服务,login.html均 200。 - S1.2 迁移脚本:新增
scripts/migrate-sqlite-to-pg.mjs(md515fb990a68aaae918128faf0801e9464)。设计=列清单不写死(PG information_schema ∩ SQLite PRAGMA)+ PG 结构由平台自己的迁移建 + identity 列OVERRIDING SYSTEM VALUE+ 搬完setval。实测:逐表行数一致|uid 原样(100001/100002)|audit id 原样|迁移后新用户得 100003 不撞主键。踩坑 1:schema_migrations不能再搬(与平台迁移冲突,事务已回滚)⇒ 已加SKIP。
- S1.4 结论:SQLite 6/8 == PG 6/8,失败项完全相同(
- 验收三关:① smoke 两后端一致 6/8 ② 单机形态可跑 ✅ ③ 47 四个 hash 逐字一致 ✅(DB 字节一致;用户 6→7 是平台在正常服务,非本单动作)。
- 🔴 新发现(P0 阻塞,影响 S3/S6):106 上 account 隔离模式实例起不来 ——
node: OpenSSL configuration error: Permission denied fopen(/etc/ssl/openssl.cnf)exitCode 13。根因(已复现+硬证据):bwrap 为绑定/etc/pki/tls/certs自建的中间目录/etc/pki、/etc/pki/tls权限是drwx------(0700) ⇒ uid 无法穿越 ⇒ 而 106 的/etc/ssl/openssl.cnf是指向/etc/pki/tls/openssl.cnf的符号链接 ⇒ EACCES(非 ENOENT)。47 无此文件故不触发 ⇒ 设计 §14.3「机器基线不能跟着迁移」的首个真实实例。- 已验证修法 D(采纳):绑定前加
--perms 0755 --dir /etc/pki+--perms 0755 --dir /etc/pki/tls—— 零权限扩大。 - E 被否决:整绑
/etc/pki也能过,但/etc/pki/tls/private/postfix.key会暴露给所有实例(违反 R5)。 - C 不可行:
bind 不能覆盖悬空符号链接(Can't create file at /etc/ssl/openssl.cnf)。 - ⚠️ 该问题普适:任何"宿主配置文件放 X、用符号链接从 Y 指过去"的发行版都会踩。
- 已验证修法 D(采纳):绑定前加
- 发现 2(待定性):
smoke-domain打印proxy redirect -> /somewhere,断言期望/u/u1/dsh/somewhere⇒ legacy 子路径 Location 重写未生效;两侧一致 + 我只读未改码 ⇒ 既有行为,需单独立项。 - 载荷清单教训:第一版漏
assets/⇒proxy.js因缺assets/inject/recovery.js硬失败(档案 81 R1)。S6 的 join 脚本必须列全:lib scripts package.json assets web poc cordis.patch.yml ensure-role-profile-patch.cjs mksess.cjs README.md STANDARD.md。 - 改动文件:
scripts/migrate-sqlite-to-pg.mjs(本机代码仓,未 commit,§4 提交边界);交接单/T08-*.md §9执行记录;.doing-T08/OWNER;台账 T08 行。 - 下一步:S1.6 先修 bwrap 中间目录 0700(涉及
orchestrator.ts隔离层,需 47 回归 + 106 上smoke-isolation转绿)→ 再回 S2(schema v7 +lease.ts)。
12:1x–13:2x — S1.6 完成:bwrap 中间挂载点 0700 已修(双机验证)
- 用户「确定执行」 ⇒ 抢锁 → 改
src/supervisor/orchestrator.ts(只增不减):- 新增模块级
mountParentDirList(dest, stopAt)+mountParentDirArgs(dest, stopAt) - 调用点 2 处:①
/etc白名单装配 ②--bind root root与共享技能层绑定之前(同因)
- 新增模块级
- 🔴 救命的一条(回归测试抓到的):第一版用
--perms 0755 --dir在 106(bubblewrap 0.11.0)通过,但 47 是 bubblewrap 0.4.0 ⇒bwrap: Unknown option --perms⇒ 沙箱起不来 = 所有实例全挂(47 上/etc可见条目 78 → 0,一眼可见)。 ⇒ 改用--tmpfs:0.4.0 就支持,且自带 0755(本文件注释早已写明)。47 实测/etc条目 78 == 78 零变化。 📌 通用结论:改 bwrap 参数前必须先看目标机的 bubblewrap 版本(47 = 0.4.0 这个基线很旧,很多新选项都没有)。 - 修复效果(三机证据):① OpenSSL EACCES 出现次数 1 → 0 ②
/etc可见条目 495==495(106)/ 78==78(47) ③ bwrap 接受参数 rc=0 ④ 中间目录 0700 → 0755 ⑤ 私钥/etc/pki/tls/private仍不可达 ⑥ 沙箱内/opt只有该用户自己的树、平台目录不可见。 - 忠实链探针(
systemd-run --scope→ bwrap → setpriv → node,cwd 在用户工作区):cwd=…/ws/proj uid=50001,相对路径写成功 ⇒ account 启动链本身是通的。 - ⚠️
smoke-isolation不能作为 account 模式验收(夹具结构性缺陷,非平台缺陷):① 夹具路径必须在沙箱绑定集内(fake-dsh.mjs放/opt/...⇒Cannot find module;放/usr/local/...就好)② 数据根用mkdtemp(/tmp)会与沙箱的--bind <userRoot>/tmp /tmp冲突 ⇒bwrap: Can't chdir to /tmp/dsh-smoke-iso-XXXX/...: No such file or directory(生产dataRoot=/var/lib/dshs不撞)。⇒ 遗留小项:修夹具或另写 account 验收脚本。 - 47 未部署(本步只在 106 部署;47 只做同参数实测,在临时目录跑 bwrap,不碰生产进程)。criterion ③ 终检通过:
lib//web//unit/DB 四项与开工基线逐字一致,dshsactive。 - 产出:
交接单/T08-*.md §10(S1.6 执行记录);orchestrator.tsmd5 记入 OWNER;docs-auditrc=0。 - 下一步:S2(schema v7 +
lease.ts)。
13:3x–14:1x — S2 ✅ + S3 ✅(用户「一直执行完毕」)
S2 · 数据模型 v7 + 租约服务
- 改动:
db/schema.ts(v7:dsh_hosts+dsh_instances加host_id/epoch/heartbeat_at/lease_until+ 两索引,SQLite 与 PG 两方言同写)|db/types.ts(DshHost/ClaimResult/clusterInstanceId())|db/adapter.ts|db/repo.ts+db/sqlite.ts(SQLite 侧)|db/pg.ts(UPDATE … RETURNING原子抢占)|supervisor/lease.ts(新):InstanceLease+stillHolder()+ttl > 2×renew硬校验|test/lease.test.mjs(新,10 例)。 - 验收:SQLite 10/10 == PG 10/10(PG 侧在 106 上用
LEASETEST_PG_URL跑)。覆盖单写者 / fencing(旧持有者续租必失败) / 过期可抢占且 epoch 递增 / epoch 不复用 / 对账视图 /renewAll失权清单。 - 🔴 踩坑(已修):
INSTANCE_COLS漏 v7 的 4 个新列 ⇒ 行能匹配但hostId恒为 null(3 个用例挂住)⇒ 两方言的列清单必须同步。
S3 · Worker agent + RemoteSpawner(端到端通过)
- 改动:
worker/agent.ts(新)(/healthz/instances/launch/stop/status/endpoint/fence/restart-probe/watchdog/touch;bearer 鉴权timingSafeEqual+ 白名单动作 + 幂等缓存 + epoch 跟踪)|supervisor/remote-spawner.ts(新)(实现Spawner全接口;重试 200ms/1s/3s 且全程复用同一operationId)|orchestrator.ts只加两个 additive 方法(listUserInstances()、launchTokenOf())|config.ts(DeployMode加'cluster'+ 4 个配置)|web/server.ts(cluster 分支 + 缺地址 fail-loud)|cli.ts(dshs worker)|scripts/verify-cluster-agent.mjs(新)。 - 关键设计:Worker 不碰 DB ——
apiKey/uid由 Manager 在/launch时投递、只存内存(与 k8s per-user Secret 同思路),uid 兜底用纯函数hashUid。 - 验收(106):
verify-cluster-agent.mjs→ OK,四项保证全绿:路由/代理层零改动 ✅|launch token 回传(P0-6)✅|幂等键 ✅|self-fencing(epoch 1→2 触发)✅;另证实例确实在 worker 上、代理经远端协议命中 fake-dsh。 - 🔴 两处踩坑(已修):
- 夹具
fake-dsh.mjs不吐 launch token(真实 dsh 会打dsh web: http://127.0.0.1:<port>/?token=…)⇒ 平台只能等 10 s 超时、launchToken恒 undefined ⇒ 「登录直达会话」在本机测试里根本测不到。补上后 launch 从 10.2 s 变即时。连带暴露smoke-subdomain:78的陈旧断言(把 URL 写死成不带 token,是"夹具不吐 token"的意外产物)⇒ 已改为startsWith(子域)+ 断言不回环端口;复跑冒烟回到 6/8、失败项与基线完全相同。 - agent 在"实例已在跑"的路径上没记 epoch ⇒
/fence拿不到 epoch、self-fencing 永不触发 ⇒ 修法 = epoch 必须在尝试 spawn 之前落(记的是「Manager 的意图」)。
- 夹具
- ⚠️ 影响后续的重要发现:今早 06:26 另一会话把 k8s 后端连同
src/tcp-bridge.ts/src/web/file-service.ts/src/fs/k8s-user-fs.ts一起删了(当时按"k8s 专用"处理)⇒ 设计里 S5/S6"复用跨机原语"的假设失效。两条出路(S4 后再定):(a) 按 cluster 语义自建 bridge + 每用户 file 服务;(b) 让 agent 直接隧道(只暴露 1 个端口、portGuard语义完整,需实现 WS 隧道)——(b) 更优。S3 的同机形态两者都不需要,不影响已完成部分。 - 三关:① 冒烟 6/8(失败项与基线相同 = 无回归)② 单机形态可跑 ✅ ③ 47 三个 hash 逐字一致 ✅
- 下一步 S4:1a 形态(2 台 Manager,一台
capacity=0+ Worker-01 同机)+ 单活退化 +dsh_hosts注册与心跳。⚠️ 跨机代理需先定 11.3 的 (a)/(b);1a 同机不受影响,可先做。
13:5x–14:2x — 设计纠正(数据分层)+ S4 ✅
🔴 设计纠正(用户口径):Worker 不是"不许有数据库"
- 用户原话:「不一定 worker 不连数据库,有些插件有可能有大量数据需要保存和管理、长期使用」 ⇒ 我在 §1.2 写的「Worker ❌ 不碰 DB」过窄:本意只是控制面数据(尤其归属)不能由 Worker 写;插件长期数据根本不在同一层。
- 已改:设计文档新增 §1.3「数据分层」(四条判据 + 插件三种落点);并同步修正
agent.ts/remote-spawner.ts/cli.ts三处注释(注释是后来人读代码的唯一线索,必须一致)。 - 三条判据(已写进 MEMORY.md 常驻):① 是归属/租约吗 ⇒ 只有 Manager 能写 ② 跟着用户走吗 ⇒ 放实例 home(插件库实测
home/.dsh/mcn-plugin.db),别塞控制面 PG ③ 是本机运维用的吗 ⇒ 放 Worker 本地库。 - 插件三种落点:per-user SQLite 在 home(现状,天然跟迁移)|量大要真 DBMS ⇒ 每用户一库 + 数据目录放位置无关存储|跨用户聚合 ⇒ Manager 侧离线汇总,不在 Worker 上做。
S4 · 归属租约接进启动路径 ✅(端到端五项全绿)
- 改动:
spawner.ts(launchopts 加epoch)|remote-spawner.ts(透传 epoch)|supervisor/leased-spawner.ts(新)(launch前 claim、失败抛LeaseBusyError=退让;心跳 renewAll、失权即向 agent 下发更高 epoch;stop时 release;启动即注册 host + 起心跳)|web/server.ts(cluster 分支 =LeasedSpawner(RemoteSpawner),租约参数走 env)|scripts/verify-cluster-lease.mjs(新)。 - 验收(106,两 Manager 共享 PG + 同一 agent,短 TTL 秒级制造过期):
dsh_hosts = m-1:up, m-2:up|① 归属落库host_id=m-1 epoch=1|② 单写者:m-2 抛LeaseBusyError(holder=m-1)、归属未被改动|③ 释放即接手epoch=2(单调递增,不用等 TTL)|④ 失权即 self-fence:worker 实例数 1 → 0、且归属未被误清。 - 回归:冒烟 6/8(失败项与基线相同)|S3 端到端 OK|lease 单测 SQLite 10/10 == PG 10/10。
- 编译期两坑(已修):①
quotaInfo签名被别的会话扩过(多baseMb)⇒ 委托要按当前接口对齐 ② 我把心跳停止器命名stop()与Spawner.stop(userId)重名 ⇒ 改stopHeartbeat()。 - 47 未变 ✅(三个 hash 逐字一致)。
- 下一步 S5/S6:按 11.3/(b) 让 agent 直接隧道(只暴露 1 个端口,
portGuard语义完整;需实现 HTTP/WS 转发),再做 join 脚本与计划内迁移。
14:0x–15:0x — S5 ✅ + S6 ✅ + S7(部分),用户要求「执行到任务完成」
S5 · 跨机文件面
- 改动:agent 加
/fs/*(init/list/mkdir/create/upload/read/isdir/plugins/handoff/root,复用同一个LocalUserFs)|src/fs/remote-user-fs.ts(新)(路径安全复用fs-guard.resolveWithinRoot,不重写;resolvePath按 worker 的 dataRoot 做纯数学)|provider.ts按模式选实现 +DSHS_CLUSTER_WORKER_DATA_ROOT+ 启动探测不一致就报 |scripts/verify-cluster-fs.mjs。 - 验收(关键设计:Manager 的 dataRoot 故意与 worker 不同 ⇒ 才能证明走远端):门户 tree/mkdir/upload 全经远端 ✓|文件落在 worker、不在 manager ✓|
bad_path与本地同源 ✓|下载回读逐字一致 ✓。 - 🔴 踩坑(已修):agent 的
/fs/create、/fs/upload直接 return 字符串 ⇒ Fastify 发text/plain⇒ 调用方JSON.parse失败 = 静默 500 ⇒ 必须包{name}。 - 基线约定:cluster 下所有 worker 的 dataRoot 必须同路径(同镜像可满足),不一致启动即报。
S6 · 多 worker + 容量准入 + 计划内迁移(方案落点)
- 改动:
InstanceLease.held记{epoch, hostId}|Spawner.launch/stop支持显式hostId|RemoteSpawner多 host(hosts/hostsProvider懒加载+30s TTL ⇒ 新增 worker 不用重启 Manager、hostIdFor按归属路由、未知 host 直接报错拒绝静默改投)|LeasedSpawner选机(selectHost容量准入 +agentFor让 fence 发给实例所在那台;先选机再认领)|管理面GET/POST /api/admin/hosts(绝不下发 agentToken)+POST /api/admin/users/:id/dsh/migrate|scripts/verify-cluster-migrate.mjs。 - 验收(两 worker 共享 dataRoot):① 容量准入把高水位机排除 ⇒ 落 w-a ✓ ② 迁移 w-a→w-b(drain→拉起→epoch+1)✓ ③ 迁移后代理 200 + 文件可读(数据不搬家)✓ ④ 边界
already_there/unknown_host明确报错 ✓。 - 容量语义(已定):
>0声明并参与准入(已用+预留 ≤ 容量,DSHS_CLUSTER_RESERVE_MB默认 512)|≤0未声明(不设限)|-1显式禁用承载(专用 Manager)。新增DSHS_CLUSTER_REGISTER_SELF=0:一个 agent 只应有一条 host 记录。 - 🔴 三条教训(都已修):① verify 脚本挂死 —— 测试没收尾实例 ⇒
fake-dsh子进程继承 stdout ⇒ 管道不关 ⇒ ssh 一直等;顺带发现生产问题:agent 的stop()只关 HTTP 不收实例 ⇒ worker 停机留孤儿 ⇒ 已改「先 teardown、再关 HTTP」,四个脚本收尾也统一走agent.stop()②selectHost改了语义(不再"谁拉起就归谁")⇒ S4 接管步骤须显式指定目标机 ③holdings()结构变了 ⇒ 单测断言同步改(非回归)。
S7 · 观测面(部分完成)
- 新增
dshs doctor [--json](node/cgroup/swap/bwrap 版本(<0.5 提醒--perms不可用)/setpriv 降权/dataRoot/DB;退出码非 0 = 硬失败,可当 join 门禁)|dshs cluster status [--json](worker 目录+水位+实例数+过期租约,并明写"需人工确认才可接管 · R9")。 - 自动接管按设计保持关闭(
expiredAll()只报不做;人工路径 = migrate API),与 R9 一致。 - 仍待做:
join-worker.sh一键装机(现在 join = 起 agent + 注册两步手工)|集中日志/metrics|smoke-domain定性。
总验收(S0–S7 全量复跑,15:0x)
冒烟 6/8(失败项与开工基线完全相同,全程无回归)|四个端到端全 OK|lease 单测 SQLite 10/10 == PG 10/10|残留进程 0|47 三个 hash + unit + dshs active 逐字一致。
15:2x–16:0x — 真实部署形态功能确认(用户「按最佳决策执行…执行完毕后检查确认功能」)
为什么补这一步(缺口)
此前所有验证都是"同一进程内起两个 Fastify" ⇒ 部署形态从未验过(独立进程 + 真 HTTP + 真 PG + 真 dsh 子进程)。新增 scripts/verify-cluster-live.mjs:真起 2 个 agent 进程 + 1 个 Manager 进程。
结果:全绿(①–⑪)
注册→审批→登录 ✓|mkdir 落 worker ✓|拉起真 dsh(URL 带 token)✓|running=true(account 沙箱内) ✓|/enter 登录直达 ✓|实例页面 200(经代理、跟随 303)✓|停止 ✓|w-2 注册(列表不含 agentToken)✓|迁移 w-1→w-2(epoch=3) ✓|迁移后 enter 给新 token URL、页面再 200 ✓|doctor rc=0 + cluster status(w-1:0 / w-2:1 实例)✓。
🔴 真实部署抓到 3 个真 bug(单进程测试根本测不出)
- 我 S1.6 引入的
--tmpfs遮蔽 bug:bwrap: Can't chdir to <userRoot>/ws/proj: No such file or directory—— 用户根与共享技能层嵌套同前缀时,技能层的中间目录--tmpfs插在--bind root root之后 ⇒ 后挂的 tmpfs 把已绑好的用户根遮掉 ⇒ 实例崩溃循环。修:所有挂载点的中间目录统一前置 + 去重 + 由外到内(一处生成,禁止"就近创建")。 claimInstance没记folder/patch:集群模式实例行由它创建(local 模式不写库)⇒folder恒 NULL ⇒ 迁移复现不了启动参数(bwrap: Can't chdir to :空路径)⇒ 崩溃循环。修:认领时一并落库 + 迁移无 folder 就 409no_folder_recorded(fail-loud)。- 检查脚本自身的健壮性:启动窗口内代理会中途断连(
other side closed,需 try/catch 重试);/api/dsh/status的实例视图不含 launchToken(token 要用/enter判定);dsh 首页 303 需跟随重定向。 ⇒ 这三条不修会把真实部署误判为失败 —— 印证设计 §13「组件级测试不能替代部署级验收」。
47 侧说明(R11:不隐瞒)
本单在 47 上零写操作(find /opt/dshs -newermt 11:00 为空);实例 scope 由开工 1 → 0,最可能是平台自身 idle-reap(档案 08)或该实例自行退出,非本单动作(从未在 47 上 stop/kill)。
16:1x–16:4x — 真跨机演练通过(用户:"重点就是验证跨机,47 当 Manager / 106 当 Worker")
关键障碍与绕法(实测)
- 🔴 106 公网入方向被腾讯云安全组挡住:106 开 19100,47 与本机都连不上;放通只能在控制台点 ⇒ 绕法 = 让 Worker 主动拨 Manager 的
ssh -R反向隧道(走已开放的 SSH 端口,两端都不新增监听面)。 - 🔴 实例只监听
127.0.0.1(portGuard 设计前提)+ 端口是动态的(findFreePort())⇒ 用 ControlMaster +ssh -O forward/cancel在同一条长连接上动态加减转发(新增src/worker/tunnel.ts)。 - 安全:106 生成专用密钥,47 的
authorized_keys加restrict,port-forwarding(禁 shell/pty/ptys,只允许转发)。
现场
47 Manager(127.0.0.1:13080,不公网暴露) ←── 127.0.0.1:19000/19001 ──ssh -R──▶ 106 agent(w-106/w-106b);控制面 PG 也在 106(经隧道 15432)。
结果:scripts/verify-cluster-cross.mjs(在 47 上跑)九步全绿
⓪ worker 可达(隧道 ready)|① 注册 w-106(不含 agentToken)|② 用户流程|③ 文件面 mkdir 落 106|④ 拉起 running、worker /instances=1|⑤ 跨机页面 200|⑥ 第二台(106 模拟)已注册|⑦ 跨 worker 迁移 w-106→w-106b(epoch=2)|⑧ 迁移后新 URL 页面 200|⑨ 停止。
三方交叉取证:106 有该用户家目录与 ws/proj、两 agent 在跑;47 cluster status 显示 w-106/w-106b 均 up;47 生产逐字未变(三 hash 一致、/opt/dshs 0 改动、13080 无公网监听)。
顺带照出机器基线差异:47 = cgroup v1 + dsh 在 /usr/local/bin/;106 = cgroup v2 + /usr/bin/dsh。
边界(不夸大)
用的是演练级传输(SSH 反向隧道),不是设计 §2.3 的最终拓扑(受控网段白名单/正式隧道服务)⇒ 证明的是"软件在跨机下正确",不是"生产网络已就绪"。仍待做:① 云控制台放通受控网段(或隧道服务化)② 生产切换演练(drain→切→回滚,需窗口)③ join-worker.sh 一键装机。
现场保留待查验(拆除三步见 交接单/T08-§15.5):47 只新增 /opt/dshs-cluster/(新目录)+ authorized_keys 一行。
16:4x–17:1x — 云安全组取证 + 域名形态访问验证 ✅
云端限制取证(用户问"是端口限制还是什么")
- 是腾讯云安全组,不是 106 主机防火墙:106 上
firewalldinactive、iptables只有腾讯云「云镜」ipset 黑名单(只 DROP 已标恶意的源)、nft0 条 ⇒ 主机层面 19100 是允许的;而"106 自测 200 / 47 与本机都连不上"⇒ 拦在云平台层。 - 47 的出口 IP =
47.77.182.89(106 侧实测对端地址)⇒ 若走 B 方案,安全组写源 47.77.182.89/32。 - 实例端口取自内核临时端口段
32768-60999⇒ 要放通必须先收窄到可声明的端口池(我建议加DSHS_PORT_MIN/MAX),否则等于摊开 2.7 万端口。
域名形态访问(演练环境,不动生产/DNS/证书)
scripts/verify-cluster-domain.mjs(在 47 上跑)全绿:https://<user>.test.alotbuy.com/?token=… → 真 dsh 实例页(<base href="/">)、越权(A 的 cookie 打 root 子域)403、实例确实在 106。
🔴 两次假阳性,都要记住:① 判据只用状态码 ⇒ 门户/登录页也是 200;② fetch 会静默丢弃 Host 头(Fetch 规范禁止头,undici 忽略)⇒ 落到"无租户"门户路由、假通过。⇒ 多租户路由验证必须 curl -H Host:(或 node:http),判据要能区分"实例页 vs 门户页"(MEMORY 里早有这条,本次仍踩了)。
🔴 另一个重复踩的坑:pkill -f ".../lib/cli.js ..." 匹配不到实际 argv(进程是相对路径 node lib/cli.js …)⇒ 旧进程没死、新进程绑定失败,看起来"重启了"其实没有(在 47 的 Manager 与 106 的 agent 上各踩一次)⇒ 一律按端口定位 PID 再 kill(ss -lntpH 'sport = :PORT' | grep -oP 'pid=\K[0-9]+')。
✅ 顺带验证:按端口 kill agent 后 teardown 确实把实例全收了(残留 dsh --profile = 0)。
生产仍未切(明确)
alotbuy.com 仍由 nginx(443) → 生产 dshs(127.0.0.1:3080,deployMode 未设 = local) 服务,7 个用户 home 在 47。要"alotbuy.com 登录 → 实例在 106"需要一次生产切换:① 用户数据面到 106/共享存储 ② 生产库 SQLite→PG(脚本已验证)③ unit 加 cluster env + 重启 ④ 最后一跳(隧道或放通+端口池)⑤ 通配证书/DNS(Nginx 侧)。演练 Manager 在 127.0.0.1:13080、公网不可达、库是独立的 dshs_cross ⇒ 与生产互不影响。
10:2x — 产出交接单 T08(集群化落地 · 全程兼容单例模式)
- 用户要求:梳理落地方案 → 拆分实现步骤 → 核对无误后逐步执行 → 要兼容现在的单例模式运行。
- 交付:
dsh-server-docs/交接单/T08-集群化落地-兼容单例模式.md(8 段模板齐备 + 步骤 S0–S7);交接单/README.md §一已加 T08 行(🔵 待执行)。 - 硬约束(用户口径落地成机械判据):每步结束必须过三关 —— ① smoke 全绿 ② 单机形态(
deployMode=local)仍可完整跑通 ③ 47 生产 commit 一行不变。第 ③ 条是"兼容单例模式"的硬证据(验收 §6 第 2 条)。 - 步骤概览(每步自带验证 + 回滚):
- S0 前置与基线冻结(抢锁 / 记录 47 与 106 基线 / 47 跑一遍 smoke 存对照基线 / 开分支
feature/cluster-1a/ 106 建独立测试目录/opt/dshs-cluster端口 13080) - S1 SQLite→PG(不改业务逻辑;迁移脚本;同一组 smoke 两侧对比;回滚 = 去掉
DSHS_DB_URL) - S2 数据模型 v7(
dsh_hosts+ 归属/租约列,SQLite 与 PG 两份同步;lease.ts纯逻辑 + 单测) - S3 Worker agent +
RemoteSpawner(同机自连验证,拓扑不变;含 launchToken 回传;回滚 = 切回 LocalSpawner) - S4 1a 形态(2 台 Manager,其中一台
capacity=0;单活退化验证) - S5 文件面跨机(
RemoteUserFs复用 file-service) - S6 第二台 Worker +
join-worker.sh(必须建号 —— §17.3 实测 uid 无 passwd 条目则os.userInfo()失败)+ 计划内迁移 - S7 自愈与观测(心跳用 HTTP 不用 ping;自动接管开关默认关)
- S0 前置与基线冻结(抢锁 / 记录 47 与 106 基线 / 47 跑一遍 smoke 存对照基线 / 开分支
- 决策:D1–D5 已定(双活+支持单活 / 存储可插拔 / 自建 PG / 跨云只当测试床 / 测试环境用 106 另起一套);⏳ 仅 D6「生产拓扑最终落点」需用户拍板,且不阻塞 S0–S5;D7(
MemorySwapMax)明确不在本单,只做只读确认。 - 范围纪律:⛔ 不动 47 生产;⛔ 不动 106 既有
dsh-users-platform;⛔ 不动orchestrator.ts的隔离/配额;⛔ 不加自动化任务。 - 文档库检查(改完必跑):
docs-audit.pyrc=0(无 P0、无悬空引用)|docs-manifest.pyrc=0(已刷新)|docs-consistency.pyrc=0。docs-sync-check.sh报 5 内容不一致 + 1 仅本地 —— 其中 属于我的是 3 个(新增 T08、交接单/README.md、docs-manifest.json);另 3 个是别的会话的未提交改动(BRIEF.md/DEPLOY-本部署.md/skills/dsh-opensource-release/SKILL.md)⇒ 我只报不动、不推(§三.0.⑥ 幽灵文件纪律)。规划会话不 scp(§四),服务器镜像留给执行会话。 - 锁:
--claim-exec "集群化落地规划(09-15)"→ 检查 →--release-exec已释放并复核(交接单/.exec-lock不存在、无.doing-*)。理由:要等用户核对,不能挂锁空转(§6「持锁期间若要等用户拍板 → 先释放再等」)。
06:3x — 源仓下线 K8s 后端 + 删除 @kubernetes/client-node(路线 C)
- 用户指令:「又不是 dsh 主程序的,也不是插件需要的 ⇒ 可以备份起来,从项目代码中删除」⇒ 走影响说明 §9 的路线 C。
- ⚠️ 动手前查到的关键事实(已向用户点明):源仓仍保留 K8s 后端,
src/supervisor/{k8s-spawner,leader,reconcile}.ts三文件 import 该依赖 ⇒ 「回源仓直接 npm uninstall」会打断源仓 tsc(我上一轮的表述有误,已更正)。 另:《集群化改造方案 Manager/Worker》把这些文件列为要照抄/泛化的模板(leader.ts 租约 / k8s-spawner.ts stampFencing / k8s-user-fs.ts + file-service.ts → RemoteUserFs / tcp-bridge.ts)⇒ 备份按「模板」组织,不丢。 - 执行(源仓
D:/github/dsh_shenxian,基线 HEADed0c3c0,工作树干净):- 9 个文件
mv到D:/github/_dsh_shenxian_K8s后端备份_20260915/(含 README.md:操作 / 还原命令 / 模板清单 / 刻意保留项); - 脚本
_retire_k8s_from_source.py(CRLF 自适应,预演→落盘→复跑幂等)改写 4 个文件 14 处:provider.ts整份重写、server.ts换 fail-loud 守卫 + LocalSpawner、cli.ts摘 5 import + 选主块 + 2 子命令 + dispatch + HELP、package.json摘 2 测试入口 + smoke:file-service; npm uninstall @kubernetes/client-node --package-lock-only(用--package-lock-only避开原生依赖重编译;不手改 lock);- 收尾 3 处悬空 JSDoc(
user-fs.ts/spawner.ts×2,文案逐字取自导出侧规则 ⇒ 导出物保持逐字节不变); - 文档防坑:
docs/k8s-deploy.md/k8s-deployment.md加状态横幅(代码已下线,dshs file-service/tcp-bridge已不存在);README.md那句失实的「模式 B 已完整落地」改为「已于 2026-09-15 下线」。
- 9 个文件
- 验证(最终态):
tsc --noEmitexit 0|npm test36 测试 / 35 通过 / 0 失败 / 1 跳过 + 注入校验全绿|npm run verifyexit 0|依赖 0 命中|被删符号 0 命中|node lib/cli.js --help只剩 server/bootstrap-admin|源仓差异 19 文件 / +29 −2690。 - ⚠️ 刻意保留(非遗漏):
deploy/·poc/01–04·Dockerfile.dsh·docs/k8s*.md·config.ts的 7 个 K8s 配置字段与DeployMode联合类型(方案列为未来可选;server.ts仍 fail-loud,填k8s启动即报错)。 - 导出侧同步:
_build_export.py的EXCLUDE_FILES/REGEX_RULES⑦ 段现为幂等空转(命中 0)→ 注释标明「防御性保留」;台账/清单/影响说明/MEMORY.md均已更新为「批 3 ✅ 已清,至此清单全部清零」。 - ⚠️ 未提交:源仓 19 个文件改动未 commit / 未 push(等你发话)。
06:3x — 源仓 K8s 下线改动已提交(未推送)
- 提交前取证「不影响当前项目」:生产
deployMode=local(DEFAULT_DEPLOY_MODE='local',生产无覆盖)⇒ 被删的deployMode === 'k8s'分支本来就不可达;file-service/tcp-bridge子命令只在 K8s Pod 内调用;DeployMode联合类型与配置字段未动(env/配置兼容)。唯一行为变化:真设DEPLOY_MODE=k8s时启动即报错(原会 new 一个已不存在的类)—— fail-loud,不静默降级。 - 提交:
cf8b7b1chore(k8s): 下线 K8s 后端形态,移除依赖 @kubernetes/client-node—— 19 文件 / +29 −2690(含package-lock.json−716 行,由 npm 自行重算)。提交后工作树干净。 - 只
git add了自己改的 19 个路径(未用git add -A,按 R7)。本地node_modules里该包仍残留(无害;服务器npm ci会按新 lock 装出干净树)。 - ⏳ 未 push(项目纪律 §4:push 需用户明确说「推送」)。
06:4x — 源仓 K8s 下线改动已推送到远端
git push -u origin master成功:ed0c3c0..cf8b7b1 master -> master(快进,未 force),并顺手补上了master的上游跟踪。- 推送前先 fetch 对账(没盲推):
FETCH_HEAD显示远端 master =ed0c3c0= 本次提交的直接父提交 ⇒ 确认是干净快进。 - 独立复核:
git ls-remote origin refs/heads/master=cf8b7b1f5c42c6fb0d17afb025a9d8fddea925c0= 本地 HEAD ✅ - ⚠️ 新发现的本机怪癖(已写进 MEMORY.md §三):该仓
refs/remotes/**写不进去 ——git fetch origin与git update-ref refs/remotes/origin/master <sha>都报成功,但git show-ref/for-each-ref里没有该引用 ⇒git status显示## master...origin/master [gone]。hooks 目录只有.sample(不是 hook 干的)。 推送本身不受影响;与本机的历史结论「源仓 remote-tracking 一直读不到」一致。⇒ 与远端对账改用git ls-remote origin refs/heads/master。 - ⚠️ 本机
git走 SSH 需加非交互参数(GIT_SSH_COMMAND="ssh -o BatchMode=yes -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new"),否则可能挂在交互提示。 - 锁:
--claim-exec "源仓推送K8s下线-0639"→ 已反序释放(释放前验过 OWNER)。
06:5x — 补删 K8s 命名为的死接口 LivePod(用户发现)+ 修正首轮两个副作用
- 用户指出:
src/supervisor/spawner.ts的export interface LivePod—— 注释已中性但名字来自 K8s 的 Pod,0 引用死代码,cf8b7b1漏了。核实属实(源仓 src 里只有声明那一处)。 - 🔑 提炼判据(已写进 MEMORY §一):K8s 残留要按三类扫 —— ① 字面量
k8s② 语义措辞(Pod/sidecar/Headless…)③ 标识符名(LivePod/K8sSpawner/stampFencing…)。第三类最易漏(注释改了、名字还在)。扫法:用 K8s 词汇表捞非注释行里的 interface/type/class/函数名。 - 顺带发现自己首轮留下的两个副作用并修掉:
docs/k8s-deploy.md/k8s-deployment.md的状态横幅被重复插入(cf8b7b1里每份 2 条、挤在同一行;工作树又变 3 条)—— 根因:横幅规则不幂等,每跑一次多插一条。⇒ 补「H1 后已有横幅就不再插」的负向断言 + 一条把重复收敛成 1 条的自愈规则。lib/(构建产物,gitignore)里留着已删模块的编译结果(k8s-spawner.*/leader.*/reconcile.*/k8s-user-fs.*/tcp-bridge.*/file-service.*,共 18 个)⇒ 会让「全仓 grep k8s」出现假阳性,已移入备份目录_stale_lib/。(tsc不会自动清已删源文件的产物。)
- 验证:
tsc --noEmitexit 0|npm test36 / 35 通过 / 0 失败 / 1 跳过|LivePod全仓 0 命中(含lib/)|两份文档横幅各 1 条。 - 提交并推送:
68c0a32refactor(k8s): 删除 K8s 命名的死接口 LivePod;修正 K8s 文档横幅重复(3 文件 / +2 −10)→cf8b7b1..68c0a32;远端git ls-remote复核 = 本地 HEAD;工作树干净。 - 导出侧同步:
_build_export.py的K8S_SEMANTIC_RULES新增第 ⑫ 条(LivePod整块删除,作兜底);导出物已无LivePod;台账/影响说明/备份 README 均已更新(新增「K8s 残留三类扫」判据与脚本重跑注意事项)。 - 锁:
--claim-exec "删LivePod死代码-0649"→ 已反序释放。
07:0x — 多语言(i18n)现状核查 + R5 口径更新(默认英语)
- 用户问「项目支持多语言吗(英文/中文)」⇒ 三层核查(全部实测,非推断):
- 官方 dsh 实例内 UI:✅ 语言开关(Settings → General);0.1.5-rc.1 覆盖已相当完整(见下)。
- 自研插件(
business-plugins「功能管理」):✅ 已中英双语,走官方 locale。 - 平台自己的门户 6 页 + 注入浮层:❌ 仍是纯中文(
<html lang="zh-CN">+ 硬编码,无 i18n 运行时、无切换入口)—— 属 R5 未做项。 - 文档:开源仓 ✅ 英文为主 + 中文副本;源仓
docs/中文。
- 🔴 推翻文献旧结论:档案 81 §10.7 ⑤ 记「官方只迁了 2 个包、20+ 包硬编码中文」—— 只适用 0.1.2-rc.1。实测 0.1.5-rc.1:客户端包 55 个中 36 个已走官方 locale API(
locale.register/bind/addLanguage/LOCALE_IDS),其中 35 个含中文(那是它自带的zh词条,不是硬编码),不用 API 却仍含中文只剩 1 个(dsh-client-connection)⇒ 官方在后续版本已大面积补齐。 复现:对<dsh 包根>/node_modules/@deepseek-ai/*/lib/client.js逐包grep -cE "locale\.(register|bind)|addLanguage|LOCALE_IDS"与grep -c '[一-龥]'。 - 按「档案只增不改」处理旧结论:⑤ 处挂同源提示 + 文末追加 §10.8 修正(2026-09-15);§10.5 那句「切英文后官方包仍显示中文」挂一行指针。
- 用户新口径(2026-09-15):「开发的插件需要沿用 dsh 官方项目的多语言方案,这个项目是全球化的项目,默认选中英语」⇒ 记入 §10.9 追加口径:
- 自研插件现状已合规(
poc/business-plugins/lib/client.js:locale.register(NS,{zh,en})+locale.bind(NS)+inject=["slots","locale"])⇒ 无需改造。纪律:后续插件一律走官方扩展点,不自造词条运行时;register要求 zh/en 同时提供。 - 官方兜底即英语:
FALLBACK_LOCALE="en"·LOCALE_IDS=["zh","en"]· 偏好存settings.yaml的locale.preference(NSlocale/ fieldpreference)⇒ 平台无需改官方代码或打补丁。 - ⚠️ 唯一例外:
navigator.language仍参与解析(注释原文 “navigator.language trails the ordered …”)⇒ 中文浏览器且无偏好时可能自动落zh。要「一律默认英语、忽略浏览器」⇒ 铺实例时把locale.preference: en写进该用户settings.yaml(平台侧配置,不触官方代码;用户之后仍可在 Settings 自选)。 - 平台 6 页的 R5 兜底语言由
zh-CN改为en;是否保留 navigator 跟随一步 = 我的取值(已在档案里标注可推翻)。
- 自研插件现状已合规(
- 文档库流程:抢锁 → 改档案 81 →
docs-audit/docs-manifest/docs-consistency全绿 → scp 同步镜像(600)→ 对账确认 81 已一致。 - ⚠️ 两个旁证(不是我的改动,需留意):
docs-shrink-guard.py报skills/dsh-decision-method/SKILL.md491 → 296 行(-40%) —— 疑似被整文件重写抹行(本库规矩:共享文件只用 Edit 精确替换)。docs-sync-check.sh报 5 个文件「内容不一致」:BRIEF.md·docs-manifest.json·DEPLOY-本部署.md·CODEBUDDY.md·skills/dsh-opensource-release/SKILL.md—— 均为其他 lane 的在途改动,镜像未同步(不在我本次清单里)。
- 锁:
--claim-exec "更正R5官方locale覆盖率-0703"→ 已反序释放。
07:2x 会话 —— 「用户自传个人技能」支持度核查
- 用户问:「记得之前提过用户可以自己上传、使用、管理个人 skills,看看项目支持吗」。只读核查(读文档 + 读源码 + 读源码仓
git ls-files+ ssh 读/opt/dshs现状;未抢锁、未改任何库内文件)。 - 结论:支持到「后端 + 落盘」两层,用户侧没有界面。 证据:
- 后端
src/web/routes/skills.ts(686 行)6 个路由全requireAuth:GET/POST /api/skills/mine、POST /api/skills/mine/apply、POST /:name/{enable,disable}、DELETE /:name;服务器/opt/dshs实测同一文件存在(grep -c= 7 处)。 - 落盘:启用位
$DSH_HOME/skills/<name>/ 禁用位home/skills-library/<name>(dsh 无 disable 机制的绕法);dsh watch 即时生效,不需重启实例。安全加固(拒 symlink/设备/FIFO、解压 ≤2000 文件 / ≤200MB、lchown跳过 symlink)已在(档案 41)。 - 界面缺失:门户
#/skills在portal.html标注admin: true,只调/api/skills/shared(共享层);web/已无skills.html(档案 16 阶段 1 移除「我的技能」+ 加 role 守卫);实例内poc/business-plugins/lib/client.js只注册 4 个settings.section(preferences/model-settings/business-plugins/platform-admin),没有「我的技能」。档案 41 文末自己也写「用户目前无界面可用,只有 API」。 - ⇒ 缺口 = 待执行单
T01(档案 16 阶段 3/4;决策点 1 已于 09-12 拍板 = 扩展business-plugins加第二个 section,不新建包)。另:档案 11 「待办」里「dsh 会话内/技能名调用」的 UI 端到端验收至今未做(仅源码级确认 watch 即时性)。
- 后端
- 记忆库维护(系统提示 MEMORY.md 超注入上限被截断):
MEMORY.md19,979 → 12,054 字节(-40%);删的是重复与叙事,判据零丢失 —— 被压缩的详版(K8s 清理全量判据 / 兼容收口 / 圈选细节)原样搬到PLAYBOOK-实例与插件坑.md新增的 §7·§8(PLAYBOOK 6,682 → 10,842 字节),MEMORY 顶部加了详版索引。- ⚠️ 顺带纠正两处过期状态:① 「归档编号 至 93」→ 至 98(§一/§四 早已引用 96/97/98,登记行没跟上)② §二 新增「个人技能」行(本次核查结论)。
- ⚠️ 未跑
docs-shrink-guard.py(本文件不在受保护根、不属文档库;该脚本按行数骤降报警,本次MEMORY.md行数 -40% 属有意的等价搬迁,非抹行)。
07:26–07:35 会话 —— 「技能与插件统一管理」口径适用性分析(已落 T01)
- 用户问:「之前提的把 skill 和插件都看成功能(能力),统一管理,适用于用户管理个人 skills 吗」。
- 原文定位(旧工作区会话目录
E:\ProgramData\.workbuddy\projects\d-AI技能-aliyun-dsh-server,用extract-user-voice.py --needle 技能 --full捞出):- 2026-09-12 09:18(会话
ca58e09f)原话:「…!业务技能能否打包到插件中 一起安装和使用,不要分开管理!…」→ 落地 =archive/交接单-已完成/T03 §4.1「技能随插件包投放」,判据内核 = 素材库 U4。 - 同会话 09:35 追问:「是否需要把技能装到
$DSH_HOME/skills/还是就在插件内目录使用,如果不懂(不同)插件有相同名称技能如何处理」。
- 2026-09-12 09:18(会话
- 结论(分两层,方向相反):机制/投放层 ❌ 不适用(个人技能不是投放物,没有第二条链路可合并;且
T03 §4.1第 4 条「不覆盖用户自建同名技能」正是刻意把两向隔离)|入口/心智层 ✅ 适用(实例内那一栏已由档案 60 命名为「功能管理」,再另立「我的技能」section = 造反平行概念)。 - 落地(本会话自决,可推翻):把 T01 的形态从「新增
settings.section」改为并入既有「功能管理」section 内分组。- 改
交接单/T01-档案16阶段3-4-实例内我的技能.md:§一 目标 + 完成判定加形态修正横幅;§三 范围表 client bundle 行;§四 新增决策 6(含依据与"仅入口层合并"的边界);§六 验收 B 措辞;新增 §九「口径依据与适用范围」(原话 / 两层结论表 / 8 维度机制差异对照表 / 相关档案)。 - 改
03-路线图与待办.md:待执行行补「形态修正(2026-09-15)」+ 指向 T01 决策 6 与 §九。 - 校验:
docs-audit.py无 P0 / exit 0(交叉引用无悬空);docs-consistency.py全绿;scp 两文件到/opt/dsh/docs(同相对目录、600)→docs-sync-check.sh本文件两项已从"内容不一致"消失(余下 5 项 = 其他 lane 的在途改动,不在我清单,按纪律不动)。 - ⛔ 未跑
docs-manifest.py/docs-archive-index.py --write:前者会重写docs-manifest.json(此刻正是别人的在途改动,R7 + 幽灵文件纪律);后者会把INDEX.md的 CR 翻倍(既有实测坑)。未 commit(用户未要求)。 - 锁:
--claim-exec "技能插件统一口径分析-0726"→ 已反序释放。
- 改
08:25–09:1x 会话 —— T01 落地:「我的技能」并入「功能管理」分组(档案 100)
- 用户:「规划一个产品方案(界面布局美观 方便操作)然后实现这个功能」→ 完整走完 方案 → 实现 → 投放 → 验收 → 归档。
- 方案(档案
04-调整方案/100-实例内我的技能-并入功能管理分组.md):并入既有「功能管理」section 内分组(不新开 section);分组头(竖条标题 + 计数 + 右侧主按钮)/ 上传面板(虚线拖拽 +label包隐藏input)/ 技能行(名称 + 来源徽章 + 状态点 + 事实tabular-nums+ 动作,官方卡片行语汇 ⇒ 结构上不换行)/ 两个页内确认弹窗(替换 · 删除)/ zh+en 46 条词条。⛔ 仅入口层合并,机制层(两条 API、两套落盘、即时生效 vs 重启)一律独立。 - 实现:
poc/business-plugins/lib/client.js(+ 词典 + 状态机loadSkills/submitUpload/applyReplace/toggleSkill/removeSkill+ 参数化弹窗skillDialog()+ 渲染 +.bp-*CSS);package.json0.3.20 → 0.3.21;新增scripts/verify-my-skills.mjs(34 条断言:结构 / 危险操作二次确认 / 锁定行无按钮 / 版式不换行 / 措辞),并挂进npm run verify。 - 投放:
npm pack(70,758 B / sha25677429d2c…,与服务器一致)→ scp/opt/dsh/artifacts/→ensure-biz-plugins.cjs --all --restart→ 两实例 0.3.21,bundles7 / 6 均含@dsh-local/business-plugins: true(未被pruneBrokenFileDeps摘掉)。 - 验收全绿:①
npm run verify(词典 zh/en 各 366 键、引用键 343 全声明);② 06 §7 三段式(磁盘命中 / 壳页 combo 含该包 +rev=533537bbc02b/ bundle 200 · 11,363,655 B 含新特征);③ 端到端 19/19(含共享技能 409、非 zip 400);④agent-browser全链路零缺陷(设置→功能管理→展开→选文件→上传 200→删除确认弹窗→删除 200)。 - 🔑 本轮最可复用的收获 = 实例侧取数四坑(已写入
PLAYBOOK §9):① 抓实例页要带门户sid(否则拿到登录页)②/plugins/只在实例子域且仍要sid(否则假 404)③ 新用户 profile 首访才生成 ⇒ 铺 bundle 必须"先 enter → 再 ensure-biz-plugins → 再 restart";且直改 DB 置 active 会绕过"审批时铺 bundle"钩子 ④POST /api/dsh/restart要{"command":""}。另:role取值是active不是approved。 - ⚠️ 踩坑(文档侧):Edit 工具改
INDEX.md会把整个文件判为改动(该文件是历史\r\r\n混合行尾)⇒ 已按兜底法恢复(镜像 == HEAD 字节)+ 改用字节级单行插入(_tmp_t01/fix_index.py),最终 diff = 1 增 1 删。同类改 INDEX 一律走字节级。 - 收尾:T01 填回报 →
git mv到archive/交接单-已完成/→ 镜像同步(旧路径已删,对账 0 仅本地 / 0 仅服务器)→03-路线图/交接单/README/INDEX §二指针同步;docs-audit无 P0、docs-consistency一致。临时用户与 /tmp 脚本全部清理(pocui.txt/probe.json是 09-11 别人的,按 R7 不动)。未 commit(用户未要求)。 - 锁:
--claim-exec "T01我的技能-0825"→ 已释放。