回收 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)、记忆修复前备份。
1137 lines
133 KiB
Markdown
1137 lines
133 KiB
Markdown
# 工作区日志 · 2026-09-22
|
||
|
||
## 06:0x–06:3x · 技能加载闸门:扩作用域(B)+ 扩词表(B)+ 清退桌面副本
|
||
|
||
**用户拍板**:① 「1、B」= 改源脚本扩钩子作用域(一份实现,全部工作区受益);② 「2、B」= 只加三条窄词给 `agent-operating-rules` 一条机制通道。
|
||
|
||
### 做了什么
|
||
|
||
**① 源脚本作用域:单值 → 多值 + env 可覆盖**(`dsh-server-docs/scripts/`,两个脚本同形)
|
||
|
||
- `skill-load-guard.py`:`SCOPE = 'aliyun-dsh-server'` → `_SCOPES_DEFAULT = ('aliyun-dsh-server', 'dsh-ai1net-desktop')` + `_workdirs()` / `_in_scope()`
|
||
- `stop-dialog-guard.py`:同款改写,4 处引用(:213 / :509 / :554 / :568)全部换成 `_in_scope(...)`
|
||
- 🔴 **形态刻意与桌面副本的 `sync-scoped-guards.py` 生成物同形**(`_scopes()` + `_in_scope()`)⇒ 两侧可互换、副本的机械变换规则不再需要改写源文件
|
||
- ⛔ **只比工作区目录名**,不写死绝对路径 ⇒ 换机器 / 改盘符不受影响
|
||
- env `DSH_GUARD_SCOPES`(逗号分隔)可覆盖;设为空串 ⇒ 只走默认表
|
||
- 校验:两脚本 `裸 SCOPE 残留 = 0` + `ast.parse` OK
|
||
|
||
**② 词表:12 条 → 17 条,并按命中词分流**
|
||
|
||
- 新增分组 2(作业规则族):`按规则来` / `按规则做` / `按作业规则` / `遵守规则` / `按规矩来`
|
||
- 注入内容**按命中词分流**:命中规则族 → 指令加载 `agent-operating-rules`;命中决策族 → 仍加载 `dsh-decision-method`(原行为不变)
|
||
- 两组都附带 `dsh-feature-first`(涉功能判类型时)
|
||
|
||
**③ 🔴 清退桌面副本挂载(本次最容易被漏的一步)**
|
||
|
||
- 副本 `E:/ProgramData/AI技能/dsh-ai1net-desktop/.workbuddy/guard/` 的存在理由(源脚本作用域太窄)**已被 ① 消除**
|
||
- **实测确认重复**:源脚本与副本在**同一秒各写一条 `HIT`**(时间戳逐字相同 `06:15:41`)⇒ 桌面工作区每轮收到两份相同注入
|
||
- 处置:从全局 `settings.json` 的 `hooks.UserPromptSubmit` **撤下 2 条**(4 → 2),其余逐字不动
|
||
- 备份:`settings.json.bak-20260922-061649-retire-desktop-replica`(另有手工备份 `-scopemerge`)
|
||
- ⛔ **目录本身未删** —— 留作回滚锚点;`guard/README.md` 头部已写「已退役 + 原因 + 备份路径 + 如何重新启用」
|
||
- ⚠️ hooks 是**应用启动时快照** ⇒ 需完全重启 WorkBuddy 才生效
|
||
|
||
### 修掉的真缺陷(自己引入 + 顺带发现)
|
||
|
||
1. 🔴 **`NameError: self_refresh is not defined`(我自己引入)**:改 `sync-scoped-guards.py` 加 `native_multi` 快路径时,`_HEADER`(含 `self_refresh()` 定义)**只拼在 else 分支** ⇒ 新分支生成的副本调用不存在的函数 ⇒ **副本整个崩掉(RC=1)**。
|
||
- 发现方式:副本独立真跑 3 个表内用例**全 FAIL** ⇒ 不猜、直接看 stderr 原文
|
||
- 修复:`head` 提到分支外、**两个分支都拼**
|
||
2. **变换失配时误报"副本均为最新" + RC=0**:源脚本改形态后 `_RE_DEF` 必然失配 ⇒ 旧逻辑同时打印 `❌ 变换失配` 和 `副本均为最新`(自相矛盾),且退出码 0 让调用方以为成功。已修为「有错误时不报最新」。
|
||
3. **同步成功后仍提示"待同步"**:`stale` 是"源变过"的过程标记,未区分"已重建"。已修为**重建成功即不再计入 pending**。
|
||
4. **`stop-dialog-guard.py` 远端属主被 scp 改坏**:推送后变 `UNKNOWN:UNKNOWN`(scp 保留本机 uid 197108)。已 `chown root:root` + `chmod 755` 修回。
|
||
|
||
### 验证(全部真跑,非静态检查)
|
||
|
||
| 对象 | 结果 |
|
||
|---|---|
|
||
| 源脚本测试集(7 用例) | **7 PASS / 0 FAIL** |
|
||
| 桌面副本测试集(7 用例) | **7 PASS / 0 FAIL** |
|
||
| env `DSH_GUARD_SCOPES` 覆盖 | ✅ 生效(设 `mcn-project,StoryForge` 后 mcn 命中、aliyun 落空) |
|
||
| 分流正确性 | ✅ 规则族 → `agent-operating-rules`;决策族 → `dsh-decision-method` |
|
||
| 表外工作区(`mcn-project`) | ✅ **放行**(两脚本均不注入) |
|
||
|
||
⚠️ **两个初判 FAIL 是我测试用例的假设错误,不是脚本缺陷**(已如实记录,避免下次重踩):
|
||
- ① 钩子有 **300 s 同会话冷却** ⇒ 我复用同一个 sid,第二条被正确拦下 ⇒ sid 必须唯一;
|
||
- ② `stop-dialog-guard.py` 在 `UserPromptSubmit` 事件**只留痕不注入**(注入走 `Stop` 事件)⇒ 期望 stdout 非空是错误的。
|
||
|
||
### 沉淀
|
||
|
||
- 技能 `agent-operating-rules` **v1.2.0 → v1.3.0**,新增 **§10.2「机制扩面后必须回头清退绕行方案」**(三步判据 + 撤挂载安全姿势 + 三条反模式)
|
||
- 核心认知:**修好根因后,为绕开它而建的临时机制会从"补丁"变成"重复"** ⇒ 必须连**挂载**一起撤(只删代码不删挂载 = 两套并存)
|
||
- ⛔ 反模式:看到"功能正常"就认为没问题 —— **重复注入不会报错**,只会啰嗦 + 状态互扰
|
||
- 三处同步:技能 `SKILL.md` 本机 → 文档库 → 服务器(`/opt/dsh/docs/skills/`,600 root:root);源脚本 2 份推 `/opt/dsh/docs/scripts/`(755 root:root)
|
||
- `README.md` 第 39 行登记更新为 **v1.3.0** + 补 §10.1 / §10.2(按**行首前缀锚定**改,⛔ 不按整行文本匹配)
|
||
|
||
### 现场状态
|
||
|
||
- 三处 md5:技能 `13a1c124162e623ce33da3415928e26d`(本机 = 文档库 = 服务器)
|
||
- 源脚本:`8a9d804bacfb2f8ba38d1473de07e5cb` / `ea4d4b7222955d7d22d33c14b32ead45`(本机 = 服务器)
|
||
- `settings.json` UserPromptSubmit = **2 条**(仅源脚本);`PreToolUse` 3 组 / `SessionStart` 1 组**未受影响**
|
||
- 全局执行锁已释放
|
||
- 桌面 `guard/` 目录 + `_bak-*.py` 保留(回滚锚点)
|
||
|
||
---
|
||
|
||
## 06:2x–07:0x · 取证:这么多 dsh 技能,会话能不能准确用 + 后不后维护
|
||
|
||
**用户问**:「确认实际情况:这么多dsh技能是否会话能准确的使用和后续方便维护」→ 追加要求「结合历史会话,多检查几遍」。
|
||
|
||
**方法 = 10 层递进取证**(脚本全部落在 `tmp/_keep-20260922/`,可复跑):
|
||
|
||
| 层 | 查什么 | 关键产出 |
|
||
|---|---|---|
|
||
| 1 | description 全量 + 探针词重叠 | 12 技能 522KB;探针太宽 ⇒ **假阳性**("文档""规则"必然多命中) |
|
||
| 2 | **精提取引号内触发短语** | **零重复、零包含** ✅ ⇒ 第 1 层的"14 个场景重叠"确认是假阳性 |
|
||
| 3 | 真实说法 → 命中谁 | **2 个 ❌**:「实例起不来」「装了插件但界面没变化」⇒ **该加载的没加载** |
|
||
| 4 | 三处一致性 / 引用 / 版本 / 体量 | 三处 0 差异 ✅;7 处"悬空引用"待判 |
|
||
| 5 | 引用上下文逐条看 | **7 处全是假阳性**(插件名 / 工作区名 / npm 包名)✅ |
|
||
| 6 | 自然说法覆盖率 | **95%**(62/65),盲区 3 个 |
|
||
| 7 | **853 条历史真实原话**做验证集 | 命中 0 = 55.8% / 1 = 33.8% / ≥2 = 10.4% |
|
||
| 8 | 盲区二分 + 补漏词重跑 | 补词后命中 0 降到 **42.9%** ⇒ 部分是**我判据漏词**;追问型 154 条(正常)/ 独立型 212 条(多为项目线内容,非方法论) |
|
||
| 9 | **用户明确定过的规则**是否接得住 | 🔴 **7 条真盲区**,最典型「只做被明确要求的事」历史原话 **62 次**却无技能命中 |
|
||
| 10 | frontmatter 结构校验 | 0/12 有问题(**但我自己改时踩了重复字段的坑**,见下) |
|
||
|
||
### 改了什么(3 个真缺陷 + 1 处描述词盲区,均为净改进)
|
||
|
||
1. 🔴 **`dsh-instance-diagnose` 缺「实例起不来」** —— 用户最自然的说法命不中,会**误落到** `dsh-desktop-dev-shell`(后者声明了"dsh 实例起不来")。
|
||
⇒ 补触发词(实例起不来 / 挂了·崩了·一直重启 / 报 503·502)+ **与 desktop-dev-shell 双向指认边界**(服务器用户实例 vs 本机源码实例)。v1.1.0 → **v1.2.0**
|
||
2. 🔴 **`dsh-desktop-dev-shell`** —— 给「dsh 实例起不来」加「**本机**」限定,并写明作用域。v2.0.0 → **v2.0.1**
|
||
3. 🔴 **`dsh-plugin-diagnose` 缺「装了插件但界面没变化」** —— 同因(只有对方声明了同一说法)。⇒ 补词 + 双向指认。v1.2.0 → **v1.3.0**
|
||
4. 🔴 **`agent-operating-rules` 补 8 类高频点名场景触发词**(这是最重要的一条):
|
||
实测其 description **没有**「只做/明确要求·抢锁·法规·解决问题·妥协·D盘·直入主题·代词·成本·token·积分」**任何一个词**,
|
||
而这些恰是用户反复强调、且**已写进本技能正文**的规则 ⇒ **规则写在文件里 ≠ 在正确时机被取用**(与 `skill-load-guard.py` 同族问题)。
|
||
v1.3.0 → **v1.4.0**
|
||
5. `dsh-feature-first` 补「先做哪个 / 优先级怎么排 / 做不做」+ **补齐缺失的 `last_change` 字段**。v1.8.0 → **v1.8.1**
|
||
|
||
### 验收(三项全达标)
|
||
|
||
| 指标 | 修前 | 修后 |
|
||
|---|---|---|
|
||
| 真实场景撞点(❌ = 命中错) | **2 个 ❌** | **0 个** ✅ |
|
||
| 自然说法覆盖率 | 95% | **100%** ✅ |
|
||
| 用户明确定过的规则盲区 | **7 条** | **0 条** ✅ |
|
||
|
||
- 三处 md5 一致性:5/5 全一致(本机 = 文档库 = 服务器,600 root:root)
|
||
- frontmatter 结构校验:**0/12 有问题** ✅
|
||
|
||
### 🔴 自己踩的坑(如实记录)
|
||
|
||
- 改 `dsh-plugin-diagnose` 时 **old_string 少截了一行 ⇒ 产生两个 `last_change`**。发现方式:改完逐字段清点行结构。已按行号删掉旧行(带断言:第 1 行必须是新版、第 2 行必须是旧版,不符即中止)。
|
||
⇒ **教训:改 frontmatter 后必须数一遍字段出现次数**(无人守这个唯一性)。
|
||
- 用 `python -c` 改 README ⇒ **反引号被 shell 吃掉**,pattern 匹配 0 行(静默无效果)。改为落盘 `.py` 后一次通过。
|
||
⇒ 再次验证 MEMORY 里那条规矩:**`python -c` 先落 .py**。
|
||
|
||
### ⚠️ 未动(留待拍板 / 属别人 lane)
|
||
|
||
1. **`dsh-change-workflow` 的 version = `2.9.2-playwright-banned`** —— 非标准 semver(版本号里塞了策略标记)。**已污染下游**:README 登记行现也带此值。倾向改成 `2.9.3` 并把该信息留在 `last_change`;但它可能是别的会话**有意**设的标记 ⇒ **未擅自改**。
|
||
2. **`last_change` 字段 12 个里缺 5 个**(architecture-lifecycle / auto-handoff-chain / decision-method / env-bootstrap / knowledge-upkeep)—— 字段规范不一致。⛔ 未批量补("只做被明确要求的事")。
|
||
3. **体量不均**:`dsh-opensource-release` 181KB + `dsh-change-workflow` 116KB = **全量 46%**;且 change-workflow 的 `last_change` 堆成 **3000+ 字符**的巨型变更日志("此前…此前…"反复)⇒ 维护成本集中在这两个身上。
|
||
4. **独立型盲区 212 条的主题分布**:覆盖网络线 27 / 数据权限多用户 25 / 画布前端 9 —— 这些是**项目线内容而非方法论**,按技能设计意图**不该做成技能**(归 `state.py` / `接续入口` / 文档库)。⇒ 判定为"分类正确",非缺陷。
|
||
|
||
### 结论一句话
|
||
|
||
**能用,且这次修完明显更准**(真实撞点 2→0、覆盖率 95%→100%、规则盲区 7→0);**维护链路本身健康**(三处零差异、引用无悬空、结构零缺陷),风险集中在**两个巨型技能**和**三个字段/版本规范隐患**上。
|
||
|
||
### 收尾补充(同轮,锁内完成)
|
||
|
||
**1. 又发现一个维护性缺口:README 漏登 4 个技能**
|
||
- 实测:README 只登记 **8 个**,缺 `dsh-instance-diagnose` / `dsh-knowledge-upkeep` / `dsh-opensource-release` / `dsh-plugin-diagnose`(全是本平台方法论技能,看不出有意省略 ⇒ 判为漏登)。
|
||
- ⇒ 已按现有行格式补 4 行 ⇒ 现登记 **12 个 = 全部**(11 dsh + agent-operating-rules)。补行时带断言:锚点唯一 + 目标未登记过 + **CR 计数未变**(防行尾被静默改写)。
|
||
- 顺带修服务器 `/opt/dsh/docs/README.md` 的**属主**(原为 `197108:197121`,非 root 存量问题)⇒ `600 root:root`。
|
||
|
||
**2. 沉淀:`dsh-knowledge-upkeep` 新增 §9「技能集可用性体检(10 层取证法)」**(v1.3.0 → **v1.4.0**)
|
||
- 该技能本就是"知识库维护与纠偏方法",**技能集体检正是它的子集** ⇒ 不新建技能。
|
||
- §9 五节:9.1 十层表 / **9.2 最高价值层 = 查"规则写下来了但没技能接得住"** / **9.3 判据漏词会伪装成技能盲区** / 9.4 撞点分恶性-良性 / 9.5 两条自踩的坑。
|
||
- description 同步补触发词「这么多技能会不会话能准确调用」,并顺手补齐它缺失的 `last_change`。
|
||
|
||
**3. 又踩一个坑(第 2 个自踩)**
|
||
- 新增 §9 时 old_string 用 `## 实测坑(2026-09-20)…` 标题占位,**替换后忘了把它加回去** ⇒ 那一节的 4 条 bullet **失去标题**(内容在、标题没了)。
|
||
- 发现方式:改完 grep 章节结构。修复:落盘脚本按行插回(带断言:python-c 行 / docs-audit 行各唯一 + 中间纯空行)。
|
||
- ⚠️ 连同上一条(重复 `last_change`)⇒ **同一个根因**:**替换长文本时把锚点当占位符用掉**。教训:**插入新章节时,锚点文本必须原样保留在新内容里**。
|
||
|
||
**4. 最终验收**
|
||
- 技能三处一致:**12/12** ✅(本机 = 文档库 = 服务器 `/opt/dsh/docs/skills/`,全部 600 root:root)
|
||
- README:文档库 ↔ 服务器 md5 一致 ✅
|
||
- frontmatter 结构:**0/12 有问题** ✅
|
||
- 全局执行锁已释放
|
||
|
||
---
|
||
|
||
## 06:4x–07:0x · 跨工作区冲突检查 + 精简评估 + **版本号统一为 1.0.0**(用户拍板 A)
|
||
|
||
**用户两问**:① 这些技能跨工作区使用是否冲突?② 在保证功能一致的前提下能否精简(⚠️ 强调功能不受影响)。
|
||
**用户拍板**:「A、将所有技能版本号统一改为 1.0.0」。
|
||
|
||
### ① 跨工作区隔离性:**无真冲突** ✅
|
||
|
||
扫描全部 12 个技能(12 个 .md × 全部 references)分三级判:
|
||
|
||
| 级 | 含义 | 命中技能 |
|
||
|---|---|---|
|
||
| A **严重**(会跑错家) | 指示读写本工作区 / 别的线专属路径 | 3 个:auto-handoff-chain(3) / env-bootstrap(1) / opensource-release(10) |
|
||
| B 示例(换环境要替换) | 本平台资源路径(`dsh-server-docs/`、`.workbuddy/`) | 8 个 |
|
||
| C 事实(正确且必要) | IP / 域名 / `/opt/dsh` | 多个 |
|
||
|
||
🔑 **逐条看上下文后判定:A 类全是"非冲突"** ——
|
||
- `auto-handoff-chain` 的 3 处 = **命令示例**(示范 `state.py --ws` 用法)+ 一张工作区对照表;
|
||
- `env-bootstrap` 的 1 处 = **快照文件内容**(设计上就是要被 `--inject` 替换的);
|
||
- `opensource-release` 的 10 处 = **技能固有内容**(本平台开源导出的工作根 `/dsh-ai1net-github/`、脱敏工具路径)⇒ 它本就是平台专属技能。
|
||
|
||
⇒ **结论:隔离性 OK**。根本原因是**技能按需加载**(无关工作区不会加载它),且真正需要跨工作区的是 `agent-operating-rules`(已确认**完全可移植**:A/B/C 全 0)。
|
||
|
||
### ② 精简空间:**≈1%,且不该动** ✅(结论与直觉相反)
|
||
|
||
- 量化:全 12 技能 6327 行;唯一成规模的"跨文件重复"是 `agent-operating-rules` 的 **14 句**(SKILL.md ↔ references)。
|
||
- 性质判定:14 句里 **1 句在 ``` 照抄块内**(模板,必须逐字一致);**13 句在正文** ⇒ 表面上"可精简",实测**仅 691 字符 = 全技能 1.0%**。
|
||
- 🔴 **但这 1% 也不该删** —— 它们是 **有意的双轨设计**:`SKILL.md` 存**每次加载必读的判据实体**,`references/` 存**按需展开的细节**。
|
||
而本工作区已有的分层判据明令「**不看会导致违规/事故的内容必须常驻实体,⛔ 不许只给指针**」⇒ 把 SKILL.md 那 13 句换成指针 = **直接违反既有判据**(加载 SKILL.md 时会看不到关键规则)。
|
||
- ⇒ **判定:不该精简**。当前"正文 + references 拆分"形态已是精简后的结果,继续压会伤功能(正是用户强调的红线)。
|
||
|
||
### ③ 版本号统一(用户拍板 A)
|
||
|
||
- **12/12 技能 `version` → `1.0.0`**(其中 1 个本就是 1.0.0)。含原 `2.9.2-playwright-banned`(非标准版本号问题**随之消解**)。
|
||
- **范围明确**:只改 frontmatter 的 `version`;**⛔ 不动**正文/历史里提到的版本号(如「v1.8.0 起三节收敛为指针」「原 v2.0.0 并入」)—— 那是**当时的事实记录**,改了会破坏追溯性。
|
||
- **留痕**:每个技能的 `last_change` **前面加一句**统一说明(含**原版本号** ⇒ 可追溯);4 个原本缺 `last_change` 的(architecture-lifecycle / auto-handoff-chain / decision-method / env-bootstrap)**新建该字段**。
|
||
- README 登记行 **11 处**版本号同步改(行数不变,CR 计数校验通过)。
|
||
- 三处核验:技能 **12/12 一致** ✅ + README 一致 ✅(600 root:root)。
|
||
|
||
### 🔴 本轮踩的第 3 个坑(**已沉淀进技能**)
|
||
|
||
**批量 `scp` 同名文件到同一目录 ⇒ 互相覆盖、只剩最后一个。**
|
||
- 实测:`scp a/SKILL.md b/SKILL.md … /tmp/push/` ⇒ 目标目录里**只有 1 个 `SKILL.md`**(后来者覆盖前者)。
|
||
- 所幸当时只推到**暂存目录**(未就位)⇒ 未造成损坏。
|
||
- 正解:**逐文件推到各自路径**(循环 scp),或**先在本地按名建目录树再整树传**。
|
||
- ⇒ 已写入 `agent-operating-rules/references/02` 的 §6 环境陷阱表。
|
||
|
||
|
||
|
||
|
||
|
||
---
|
||
|
||
## 07:0x · 排查:E 盘根为什么出现 dsh-logs / dsh-mcn-fix / dsh-worker-dev / e
|
||
|
||
**用户问**:「为什么 E 盘下面会出现 dsh-logs、dsh-mcn-fix、dsh-worker-dev、e 这些文件夹」
|
||
|
||
### 四个目录分两类
|
||
|
||
| 目录 | 建立时间 | 内容 | 判定 |
|
||
|---|---|---|---|
|
||
| `e/` | 09-20 02:07 | 29 MB / 355 文件;内含 `ProgramData/AI技能/{aliyun-dsh-server,dsh-ai1net-desktop}/.workbuddy/**` | 🔴 **误创建的影子目录** |
|
||
| `dsh-mcn-fix/` | 09-20 08:16 | **空**(0 文件) | 🔴 **空目录**(建了没用) |
|
||
| `dsh-logs/` | 09-18 20:48 | 1.4 MB;`106/` `47/` `state.json` | 🟡 某会话的服务器日志拉取落点 |
|
||
| `dsh-worker-dev/` | 09-20 09:49 | 1.2 MB / 101 文件;`bin` `controlplane` `instance-home` `token` `logs` `overlay` | 🟡 某会话的本机 worker 开发环境 |
|
||
|
||
### 🔴 `e/` 的根因(**当时仍在增长**)
|
||
|
||
**铁证**:`E:\e\ProgramData\AI技能\aliyun-dsh-server\.workbuddy\stop-dialog-guard.log`(2496 B,最后写入 **09-22 06:17 = 当天**)。
|
||
- 文件名 = **hook 自己的名字**;
|
||
- `E:\e\ProgramData\AI技能\` 下只有 **2 个**工作区 —— 正是**仅有的两个装了 hook 的工作区**(真目录下有 16 个);
|
||
- 另一部分内容是 `dsh-ai1net-desktop/.workbuddy/_devlogs/pud-f1/{Cache,Code Cache,blob_storage,Dictionaries}` = **Chromium 的 user-data-dir**(浏览器自动化)。
|
||
|
||
**根因(一条,两个受害者)**:
|
||
> **把 MSYS / Git-Bash 风格的路径(`/e/…`)交给了 Windows 原生程序**(原生 python / Chromium)。
|
||
> Windows 原生程序把 `/e/ProgramData/…` 解释成「**当前盘根 + 相对路径**」= `e\ProgramData\…`
|
||
> ⇒ 当前盘是 E 时落到 **`E:\e\ProgramData\…`**。
|
||
|
||
⇒ 与 MEMORY 里已有的铁律**同源**:「**Python exe 不认 `/e/…` ⇒ 传 `E:/…`**」。本次是它的**第 N 次发作,且这次留下了 29 MB 实物**。
|
||
|
||
### 修复(已落地并真跑验证)
|
||
|
||
两个 hook(`skill-load-guard.py` / `stop-dialog-guard.py`)各加 `_norm_path()`:
|
||
`^/([A-Za-z])(/.*)?$` → `<大写盘符>:` + 余部(`/e/foo` → `E:/foo`),**幂等**(已是 Windows 形式则原样返回)。
|
||
- `skill-load-guard`:入口 `workdir = _norm_path(_workdir(obj))`(下游全受益);
|
||
- `stop-dialog-guard`:`log()` + 全部 7 处 `os.path.join(root…` 统一包 `_norm_path(...)`。
|
||
|
||
**验证(真跑,非静态检查)**:
|
||
- 单元:两脚本各 **6/6** 用例通过(含 `/e`→`E:/`、已是 Windows 形式不变、空值 / None 不炸);
|
||
- 真跑:喂 `cwd=/e/ProgramData/AI技能/aliyun-dsh-server` ⇒ 日志写进**真位置**(+165 B),**影子位置未增长** ✅
|
||
- 三处同步:脚本推 `/opt/dsh/docs/scripts/`(755 root:root,md5 与本地一致)。
|
||
|
||
### ⚠️ 未动(按 personal-files-safety:扫描只读、删要确认)
|
||
|
||
`e/`(29 MB)与 `dsh-mcn-fix/`(空)**我没删**。`dsh-logs/` 与 `dsh-worker-dev/` 含实质内容,更需用户判断。清理需点名,届时先出完整清单。
|
||
|
||
---
|
||
|
||
## 07:00–07:05 · 收口(上下文 33 万)+ 登记自动接续
|
||
|
||
**用户指令**:「创建接续会话 执行重组优化,并检查确认」—— 即把两个千行级技能拆成 `SKILL.md` + `references/`,并验证功能不受影响。
|
||
|
||
**接续包**:`E:/ProgramData/AI技能/aliyun-dsh-server/接续入口_技能重组线_20260922.md`
|
||
- md5 = `1f8b78c5a75e3804f2b1cd53a448edcf`(4694 字节)
|
||
- 含 §1 接续点(目标原话 / 基线 / 校验命令 / 未完成 / 下一步 / 关键决定 / 回滚点 / 不要重做)+ §2 本轮动作(拆分方案与四条硬约束 / 四项验收)
|
||
|
||
**基线(写进接续包,供新会话机器校验)**:
|
||
- `dsh-change-workflow/SKILL.md` md5 `e5d20a959ed83f2d…` / **1052 行** / 21 个一级章节
|
||
- `dsh-opensource-release/SKILL.md` md5 `86f3dcc0c1233a07…` / **1089 行** / 16 个一级章节
|
||
- 文档库 HEAD `3d8f50e`;全局锁 = 无(空闲)
|
||
|
||
**已登记一次性 automation**:`b56314b0-e2e1-4ae6-bfc7-b053ab173abb`(scheduledAt 2026-09-22T07:06,cwds = 本工作区)
|
||
- prompt 按 `04-调整方案/126-会话接续规范.md §3.2.1` 模板:⓪ 先跑 `state.py` → ① 跑校验命令 → **①b 带接续包 md5 的口径门禁** → ①c 未定项不许替拍 → ② 从「下一步」开工 → ③ 工具调用 ≤8
|
||
- ⛔ prompt 内**不含**任务细节(细节唯一来源 = 接续包)
|
||
- ⚠️ 登记后**本会话不再改接续包 / 不再改关键判断**(若改了必须回来撤销或重登记)
|
||
|
||
**本轮全程(三条线,一次会话内完成)**:
|
||
1. 技能加载闸门扩作用域(B)+ 扩词表(B)+ 清退桌面副本(4 条 hook → 2 条)
|
||
2. 技能可用性 10 层取证 → 修 3 个真缺陷(触发词盲区)+ 7 条规则盲区 → 覆盖率 95%→100%、真实撞点 2→0
|
||
3. 跨工作区隔离性(无真冲突)+ 精简评估(≈1%,且不该动)+ **12 技能 version 统一 1.0.0** + README 补登 4 个技能
|
||
4. E 盘影子目录排查 → 定位 MSYS 路径根因 → 两个 hook 加 `_norm_path()` 并真跑验证
|
||
|
||
**新会话开工口令**(用户自己开时用):
|
||
`"E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" "E:/ProgramData/AI技能/aliyun-dsh-server/state.py"`
|
||
|
||
## 07:08 技能重组线接续棒(自动化 · 校验+备份)
|
||
- 口径门禁与 §1 校验命令全部通过(接续包 md5、两个待拆技能 md5/行数 与期望一致)
|
||
- 回滚点已落 tmp/_keep-20260922/split-bak/SKILL.md;全局锁已抢并释放
|
||
- 拆分(change-workflow / opensource-release)与三处同步未开工
|
||
|
||
## 07:00–08:00 技能重组线(用户「确认执行」· 本轮主体)
|
||
- 拆分两个千行技能为 `SKILL.md`+`references/`(机械搬运、逐行未改):
|
||
· `dsh-change-workflow` 1052 → **319 行** + 9 档详情(867 行)⇒ **达标 ≤350**
|
||
· `dsh-opensource-release` 1089 → **534 行** + 5 档详情(629 行)⇒ **未达标**:R-O1–R-O17 硬规则 239 行等「不看会违规」常驻项 ⇒ ≤350 结构上不可达(判据常驻优先)
|
||
- 四项验收全绿:行数守恒(主干内容+详情=原行数)/三处 md5 一致(16 文件)/51+68 个标题全部可寻址/frontmatter version=1.0.0
|
||
- 修正下沉副作用:补「原章节 → 详情档」对照列,接上 5 处跨档断头引用(`见下表:dsh-univer-office`/`见 §8 坑 9`/`见坑 36` 等)
|
||
- 登记跟改:`README.md` 2 处、`INDEX.md` 4 处(精确串替换器,命中各 1 次)
|
||
- 回滚点:`tmp/_keep-20260922/split-bak/*-SKILL.md`+服务器 `/opt/dsh/backups/docs-skills/*.bak-*`
|
||
- ⚠️ 环境坑:bash 包装器本轮**整体起不来**(`ls`/`cat`/`head` 全 not found、export PATH 也救不回)⇒ 全程改走 PowerShell + 托管 python
|
||
- ⚠️ 发现(未动手):技能里 `scp -i ~/.ssh/id_ed25519_dsh` 指向的密钥不存在;`INDEX.md` L19/L202 仍写 R-O1–R-O14 与 v1.7.8(陈旧);MEMORY.md 超注入上限
|
||
|
||
## 07:36–07:50 技能重组线(续):把「技能整理/拆分优化」做成可复用能力
|
||
- 落点 = **`dsh-knowledge-upkeep` 新增 §10「技能重组:千行技能拆分」**(不新增第 13 个技能;§9 体检 → §10 整改 成闭环)
|
||
- 新增 `references/10-技能重组-千行技能拆分.md`(实操清单 + 首跑实测数据 + 断头引用扫法 + 6 条坑)
|
||
- 新增可复跑工具 `scripts/split_skill.py`(`--spec <json>` 声明式规格 ⇒ 纯机械搬运 + 四项自校验 + 幂等)
|
||
- 口径已写进 §10:**判据常驻 > 行数目标**;跨档引用断头用「覆盖的原章节」索引表修
|
||
- 三处同步(本机 / 文档库 / `/opt/dsh/docs/skills/` 600 root:root)+ 三方 md5 对账 + README/INDEX 登记跟改
|
||
|
||
---
|
||
|
||
## 07:38–07:5x · 查证:插件接入 DB「是否该分库」+ 数据库现状(**实测**)
|
||
|
||
**用户两问**:① 之前规划的插件接入数据库方案是否该设计「插件分库接入」② 现在数据库在哪台服务器、用的什么库。
|
||
|
||
**实测事实**(只读命令,2026-09-22 07:4x;来源 = `BRIEF.md` + 服务器实查)
|
||
|
||
| 项 | 读数 |
|
||
|---|---|
|
||
| 宿主机 | **47.77.182.89**(Manager,阿里云 iZrj99af19cibck1ge93tqZ)= 唯一数据库所在机 |
|
||
| 数据库 | **PostgreSQL 13.23**;数据目录 `/var/lib/dshs-pg`;单元 `dshs-pg` = **active** |
|
||
| 监听 | `127.0.0.1:15432`(**仅回环**,pid 662048) |
|
||
| 业务库 | **只有 1 个**:`dshs`(owner dshs);连接串 `postgres://dshs:***@127.0.0.1:15432/dshs`(源 = `dshs.service.d/*.conf`) |
|
||
| 库内表 | **13 张,全为内核表**;`p_*` 插件表 **零命中**;`schema_migrations` 已到 **v13** |
|
||
| 106 | 装了 PG 二进制但 `postgresql*` **全 inactive**、无 5432/15432 监听 ⇒ **不承载数据库** |
|
||
| 插件数据现状 | 只有 MCN 一份实例内 SQLite:47 上 `users/cce6d1cd-b376-4304-80f0-0e1c58c9ffde/home/.dsh/mcn-plugin.db`(2,383,872 B);106 用户 home 内**无** db 文件 |
|
||
|
||
⇒ **两条新事实**:(a) 插件数据面(声明式建表 + `im.data`)**尚未实现**,`src/db` 无对应模块、平台库无 `p_*` 表;(b) 连接层是**单库单适配器**(`src/db/index.ts`:有 `dbUrl` 走 PG,否则本地 SQLite),代码里**没有任何分库概念**。
|
||
|
||
**判定:不该设计插件分库接入** —— 维持 `DB-01 §主从与扩展` 与 `DB-00 §待定` 既有的「⛔ 不提前分库」,理由按硬度排:
|
||
|
||
1. **双后端同构(最硬)**:sqlite 没有 database/schema 命名空间对等物(`ATTACH` 是连接级、不持久)⇒ 分库 / 分 schema 破坏「任何结构在 sqlite 与 pg 上都建得起来」的既有承诺;表前缀在两边都只是名字,天然同构。
|
||
2. **收益为零**:插件不持有连接、不能写 SQL、数据面全走内核 API ⇒ 分库能给的权限隔离对插件用不上;平台侧隔离诉求已由前缀校验 + 房间 ACL + 三重配额 + `plugin_data_audit` 覆盖。
|
||
3. **成本随插件数线性涨**:PG 连接池是 per-database 的 ⇒ N 插件 = N 套 pool;47 是小机(实例硬顶 1024 MiB),backends 内存实打实。单库 = 1 个 pool。
|
||
4. **把不可逆操作常态化**:插件可启停/卸载 ⇒ 分库要动态 `CREATE/DROP DATABASE`;现行卸载 = 停用 + 数据保留 30 天可恢复。
|
||
5. **堵死跨域事务**:既有「跨前缀要原子 ⇒ 走内核事务口」在分库后无法实现。
|
||
|
||
🔑 **关键认知**:「独立库」这条路**已经存在** —— 插件的「只属于某用户自己的数据」落实例 home 本地 SQLite,天然每用户每插件一个文件(MCN 现况),随实例迁移、故障互不影响 ⇒ **真正需要独立库的场景已被覆盖,平台库不必再分**。
|
||
⚠️ MCN 09-21 那次迁移不顺的根因 = 「平台侧没有写用户 home 的合规通路」(`UserFs` home 白名单只有 3 个裸文件名 + 文本语义),**不是缺分库**;分库反而让「谁写、怎么迁」更绕。
|
||
|
||
**回头重估的触发条件**(不是现在):压测证明单库触到物理瓶颈(磁盘 / IO / 单表行数)**且**某插件占比超阈值(建议 20–30%);扩展顺序仍按 `索引与游标分页 → 只读副本 → 分区 → 分库`。
|
||
|
||
**⚠️ 未动(只报告)**:`DB-03-插件数据面规范.md` 里**没有**「插件是否该分库」的论证节 —— 该结论只散落在 `DB-01` 表格一格 + `DB-00` 待定一行。要落进文档时再动手(未改任何文档、未提交)。
|
||
|
||
---
|
||
|
||
## 07:5x–08:0x · 数据库重规划:**一插件一库 + 用户数据入库**(用户拍板,推翻我上轮判定)
|
||
|
||
**用户拍板(两次)**:①「我认为数据需要分库,并且用户数据也需要保存到库中方便备份和用户迁移,用户本地只保存文件相关内容(后续除了缓存和临时文件,主要文件内容也需要储存在 sso 中)」② **澄清粒度**:「我说的分库是指**不同的插件建立不同的库**」,且统一跑在 **PostgreSQL 13.23** 上。
|
||
|
||
🔴 **我上轮的判定被推翻,且其中一条论据是错的**(必须记住这个教训):
|
||
我上轮用「sqlite 没有 database 命名空间对等物 ⇒ 破坏双后端同构」+「连接池会爆炸」否掉分库。按**按插件分库**的口径:
|
||
- (a) sqlite 侧「**一个插件一个文件**」同样承载"库"这个概念 ⇒ **双后端同构仍然成立**;
|
||
- (b) 连接池爆炸**只在按用户分库时发生**(库数 = 用户数);**按插件分库的库数 = 插件数**(十几~几十)⇒ 完全可控。
|
||
⇒ **教训:否掉一个方向之前先确认它的粒度** —— 别拿 A 粒度的代价去否定 B 粒度的方案。
|
||
|
||
### 交付(已落文档库,纯 LF,未提交)
|
||
|
||
| # | 文件 | 动作 |
|
||
|---|---|---|
|
||
| 1 | `架构设计/数据-分库与权威存储架构.md` | **新建**(12 节定稿):分库口径 / 库清单 / **两个诉求正交** / database-vs-schema / 访问通路 / 备份迁移导出 / S0–S5 / 风险 / 衔接 / 回溯 |
|
||
| 2 | `数据库/DB-03-插件数据面规范.md` | **12 处更新**(148→162 行):落点改一插件一库 · 归属列强制 · 红线新增 2 条 · API 库路由 · 管理面按库备份迁移 · 验收 12 条 |
|
||
| 3 | `数据库/DB-00-专区入口.md` | 判据速查重写 6 条 + 指向定稿 + **原「⛔ 不提前分库」标作废** |
|
||
| 4 | `数据库/DB-01-接入指南.md` | 情形表改 7 行 · 情形 2/3 重写(用户数据入库、home 降级为缓存/临时)· 选型与扩展顺序更正 |
|
||
| 5 | `架构设计/README.md` · `INDEX.md` | 登记(INDEX 字节级替换,CR 5→5 未变) |
|
||
|
||
### 设计要点(核心 5 条)
|
||
|
||
- **分库维度 = 插件**:库名 `dshs_pl_<pluginId>`,全部建在**同一个 PG 13.23 实例**(不新建实例、不换版本)
|
||
- 🔑 **两个诉求正交**(本设计的关键):**库 = 插件**(满足"分库");**库内归属列 = 用户/房间**(满足"按用户备份迁移")⇒ **归属列强制**(内核自动补 `user_id`/`room_id`,插件不许省略)是分库的**前提**,缺了它"按用户迁移"直接不可能
|
||
- **默认独立 database**(非 schema):字面合规 + 隔离最硬(跨 database 无原生 join/事务)+ 卸载 = `DROP DATABASE` 一库带走;schema 档留作连接吃紧时的备选(同套代码,只改路由映射)
|
||
- **实例本地收窄为三类**:文件内容 / 缓存 / 临时 ⇒ 判据「**本地丢了不影响正确性,只影响延迟**」;🔴 新红线「**不得以实例本地库当数据权威**」(作废 MCN 那套自建 SQLite 当权威的形态)
|
||
- **"能备份能迁移"的正解 = 统一迁移器**:按归属列遍历 控制面库 + 各插件库 + 桶清单,一次遍历、可重入、可对账(落 S5)
|
||
|
||
### 事实更正(写进定稿)
|
||
|
||
47 上 PG 精确版本 = **13.23**(上轮笼统写"PG13");现有 **13 张表全是内核表**、`p_*` **零命中** ⇒ 插件数据面**未实现**,仍是规范。
|
||
|
||
### 未动(按纪律只报告)
|
||
|
||
- `交接单/MCN线-DB接入规范合规化_20260921.md §四` 的待拍板项(历史数据导入通路)**现已有答案**(定稿 S2 的可复用导入通路)—— 该单未改
|
||
- 未建交接单、未开工代码、未 commit / push
|
||
- ⚠️ 顺带发现:`dsh-server-docs/数据库/` 与 `架构设计/` 两个目录在 git 里**整体未跟踪**(`??`),即已存在多日但一直未提交
|
||
|
||
## 07:50–08:00 技能重组线(§10 首轮迭代:把两个新坑与 docs 根漂移写回技能)
|
||
- 修掉本轮自己引入的**登记误挂**:`NAME in line` 匹配把 §10 挂到了 `dsh-architecture-lifecycle` 行(该行描述里提到 `dsh-knowledge-upkeep`)⇒ 判据改为**只认行首单元格** `l.startswith('| `skills/<n>/`')` 且命中数必须 = 1
|
||
- 发现并关闭**登记漂移**:`/opt/dsh/docs/` 根也镜像 `/opt/dsh/docs/{README,INDEX}.md`(600 root:root);此前三处同步只推 `skills/` ⇒ 根登记落后一整轮。本轮推送 + 备份 `/opt/dsh/backups/docs-root/` + md5 复核一致
|
||
- 远端陈旧判定教训:⛔ 别用 SequenceMatcher 的「opcode 全 equal/delete」(`**` 密集行会假 replace ⇒ 假阴性停手);✅ 用**整行子序列覆盖率 ≥0.98**,或 `git show HEAD:<f>` 作 oracle
|
||
- 三条教训已写回 `dsh-knowledge-upkeep/references/10-技能重组-千行技能拆分.md`(坑 7/8/9)+ SKILL.md §10 第 4 条;技能三处复推并复核 md5 一致
|
||
- 08:10 又查出 **3 处技能内陈旧事实**:① 私钥名 `~/.ssh/id_ed25519_dsh` **不存在**(真名 `id_ed25519`,已实测 rc=0)② `~/.ssh/config` 的别名 `bt-server` 写死 `Port 32022`,而 sshd 只听 22 ⇒ 用它必 `Connection refused` ③ 平台速查把文档库写成 **不存在的** `E://ProgramData//AI技能//aliyun-dsh-server//dsh-server-docs`(实测 `Test-Path=False`;真源 = `D:/github/dsh_shenxian/dsh-server-docs`)⇒ 纠正脚本 `tmp/_fix_stale.py`(幂等 · 5 条精确串替换 + 残留自检 + 三处同步对账)已备好,但**抢不到锁**(DB 分库重规划线 07:50 起占用)⇒ 未落盘,已登记一次性执行棒② `8370a5dd-1fa8-4fa5-a90e-1f3a3f46bbaa`(09-22 08:26)接手
|
||
- 遗留未动(仅报告):`接续入口_技能重组线_20260922.md` 的 §2「下一步=拆分两个技能」已与事实不符(本轮被明令禁止改该入口 ⇒ 未动);`INDEX.md` 无 `dsh-knowledge-upkeep` 行、L19/L202 仍写 `R-O1–R-O14`/`v1.7.8`;按纪律未 commit / push
|
||
|
||
## 07:55 `humanizer_zh` 找回(结论:没丢,是没装)+ 登记执行棒②
|
||
|
||
- **真身** = 源码库 `E:/ProgramData/AI技能/_skill_src/Humanizer-zh/`(frontmatter `name: humanizer-zh`,MIT,翻译自 blader/humanizer,含 24 条 AI 写作模式);`.workbuddy/skills/` 下**从未装过**(那里只有英文 `humanizer`,它自带 `references/patterns.zh.md`)。判定依据:技能根 + `~/.workbuddy/skills` 双侧 `Test-Path` 全 False;全盘 `*humanizer*` 目录/文件搜索只剩源码库与审计报告 ⇒ 不是被删,是从未落地到技能目录
|
||
- **已恢复安装**:复制 3 文件(SKILL.md 19,382 B / README.md / LICENSE)到 `E:\ProgramData\.workbuddy\skills\humanizer-zh\`,md5 = `294DBBBD…` 与源一致;源码 mtime 仍为 09-20 11:26(未变)⇒ 当日审计「100 分 / 0 风险 / 无 scripts」对这批字节继续有效(报告:`dsh-ai1net-github/_安全审计_Humanizer-zh_20260920.md`、测评:`_中文侧测评_Humanizer-zh_20260920.md`)
|
||
- **登记执行棒②** `8370a5dd-1fa8-4fa5-a90e-1f3a3f46bbaa`(08:02,两阶段):A = 上一节那 3 处陈旧事实纠正(`tmp/_fix_stale.py`);B = 用 `humanizer-zh` 改写技能中文描述,**首单元 = `dsh-opensource-release/references/03-实测坑.md`**(用户举例所在),扩面顺序 opensource → change-workflow → knowledge-upkeep → decision-method → feature-first → 其余
|
||
- 🔴 文案优化硬门禁(写进棒里,防「改文风顺带动了功能」):① **技术 token 多重集相等**(反引号内联码 / 路径 / `--flag` / 全大写 env / 数字)② 表格首列标签不变 ③ 纯 LF ④ 条目/小标题不丢;落地只用**精确串替换**(命中数必须 = 1),⛔ 不整文件 Write
|
||
|
||
## 08:07 技能重组线 · 阶段 A + 阶段 B 首单元
|
||
- 阶段 A `_fix_stale.py`:5 条精确替换全 ✓(ssh 私钥真名 / bt-server 端口 32022 已死 / 00-平台速查的文档库路径),dsh-change-workflow 三处 md5 一致;残留自检报的 5 处经逐条复核**全是纠正文案自身引用旧值**的假阳性 ⇒ 真实残留 0。
|
||
- 阶段 B:`dsh-opensource-release/references/03-实测坑.md` 按 humanizer 判据人话化 43 处(去破折号解释链、改否定式排比、去指代、收敛空转句);门禁四项全过(技术 token 多重集 / 表格首列 / 纯 LF / 条目与行数);三处一致 `d13a42443e51`。首轮 1 条锚点写错 ⇒ 已反向还原后原子重落。
|
||
- 遗留待拍板:坑 20 尾部两条 ⚠️ 判据内容重复(「四次」与「三次」并存)⇒ 未自行动手。
|
||
- 下一棒 = `dsh-opensource-release/SKILL.md`(按 +7 分钟排)。
|
||
|
||
## 08:18 技能重组线 · 第 4 棒(dsh-opensource-release/SKILL.md)
|
||
- 目标文件:`dsh-opensource-release/SKILL.md`(534 行 / 61,802 字符;原文 ` —— ` 100 处)。
|
||
- 手法:机械规则 A `** —— `→`**:` 53 处 · B `)—— `→`):` 10 处(跳过 frontmatter 与围栏代码块);另 21 处语气需判断 ⇒ 精确串替换(全文命中须恰为 1 次)。
|
||
- 门禁四项:全过;未能命中 1 处;落盘 = 否(整体未落盘)。
|
||
- 三处同步:本机 `(未同步)` / 文档库 `(未同步)` / 服务器 `(未同步)` ⇒ 待核。
|
||
- 未动项:frontmatter(路由元数据 + 变更台账);引文模板与代码块内破折号按「技术标识/引文不动」保留。
|
||
- 下一棒 = `dsh-opensource-release/references/01-保留移除与脱敏口径.md`(+7 分钟)。
|
||
|
||
## 08:19 技能重组线 · 第 4 棒(dsh-opensource-release/SKILL.md)
|
||
- 目标:`dsh-opensource-release/SKILL.md`(534 行 / 61,802 字符;原文 ` —— ` 100 处)。
|
||
- 手法:通用破折号规则 ` —— `→`:` 64 处 + `」——**`→`」:**` 1 处(跳过 frontmatter · 围栏代码块 · 3 处引文/字面输出);另 26 处语气需判断走精确串替换。
|
||
- 门禁四项 有未过;未命中 0 处;落盘 = 否;字符 61802 → 61537。
|
||
- 三处同步:本机 `(未同步)` / 文档库 `(未同步)` / 服务器 `(未同步)` ⇒ 待核。
|
||
- 上一枪因锚点写错**整体未落盘**(旧脚本无此栏杆)⇒ 本版已修正通用规则并重落。下一棒 = `dsh-opensource-release/SKILL.md`。
|
||
|
||
## 08:20 技能重组线 · 第 4 棒(dsh-opensource-release/SKILL.md)
|
||
- 备份:`E:/ProgramData/AI技能/aliyun-dsh-server/归档/humanize-原文件备份-20260922\dsh-opensource-release`(技能全 .md + 文档库副本)。
|
||
- 目标:534 行 / 61,802 字符;原文 ` —— ` 100 处 ⇒ 破折号规则(反引号感知)替换 64 处 + 精确串 26 处。
|
||
- 门禁 8 项(原 4 + 语义 4:模态词 / 交叉引用编号 / 粗体标记 / 表格行)全过;落盘 = 是;字符 61802 → 61540。
|
||
- 两次前置失败均被门禁拦在写盘前:① 锚点写错(`**显得半成品**` 实为 `半成品**`)② 反引号内字面被误改 ⇒ 均未落盘,磁盘始终是原文。
|
||
- 三处同步:本机 `636a7490803f` / 文档库 `636a7490803f` / 服务器 `636a7490803f` ⇒ 一致。下一棒 = `dsh-opensource-release/references/01-保留移除与脱敏口径.md`。
|
||
|
||
## 08:26 技能重组线 · 坑 20 判据去重(方案 A)
|
||
- 决策:用户选「方案 A:回源核对数字,删掉错的那条」。
|
||
- 回源依据(`references/03-实测坑.md` 自身):坑 20 自述「四处一起改」= `OVERLAY` / `REQUIRED_EXPORT` / `_check_parity.mjs` 的 `MANUAL`·`PAIRS` / `_check_links.mjs` 的 `MANUAL`;L51 明写「③ 与 ④ 是两份独立实现 ⇒ 必须各改一次」;L52 验收判据要 links 与 parity 两处都回读 ⇒ 同类静默跳过共 **四次**。
|
||
- 处置:删除「已出现**三次**」那条(旧版枚举,把 `PAIRS` 单列却漏掉两个 `MANUAL`,与 L51 冲突),保留「已出现**四次**」那条。
|
||
- 门禁 5 项 全过;行数 93 → 92;字符 8605 → 8480;三处 md5 e4d9facfcdb6 ⇒ 一致。
|
||
- 改动前快照:`E:/ProgramData/AI技能/aliyun-dsh-server/归档/humanize-原文件备份-20260922\dsh-opensource-release\03-实测坑.md.pre-坑20去重`;⛔ 未 commit / push。
|
||
|
||
## 08:37 技能重组线 · 第 5 棒(dsh-opensource-release/references/01-保留移除与脱敏口径.md)
|
||
- 备份:`E:/ProgramData/AI技能/aliyun-dsh-server/归档/humanize-原文件备份-20260922\dsh-opensource-release`(技能全 .md + 本轮改动前快照 `.pre-hz06`;文档库副本已备份)。
|
||
- 改动:精确串 5 处(4 处破折号解释链 ⇒ 逗号/冒号、1 处指代代词 ⇒ 点名主体)+ 反引号感知通用破折号规则 0 处。
|
||
- 门禁 8 项 全过;落盘 = 是;字符 5924 → 5913,行数不变。
|
||
- 三处同步:本机 `84f1906d8488` | 文档库 `84f1906d8488` | 服务器 `84f1906d8488` ⇒ 一致;下一棒 = `dsh-opensource-release/references/02-多远端推送.md`。
|
||
|
||
## 08:44 技能重组线 · 第 6 棒(dsh-opensource-release/references/02-多远端推送.md)
|
||
- 备份:`E:/ProgramData/AI技能/aliyun-dsh-server/归档/humanize-原文件备份-20260922\dsh-opensource-release`(技能全 .md + 本轮改动前快照 `.pre-hz07`;文档库副本已备份)。
|
||
- 改动:精确串 3 处(破折号解释链 ⇒ 冒号、指代「自己」⇒ 点名主体、口语 ⇒ 书面);通用破折号规则 0 处。
|
||
- 门禁 8 项 全过;落盘 = 是;字符 1649 → 1643,行数不变。
|
||
- 三处同步:本机 `d3a6d2241973` | 文档库 `d3a6d2241973` | 服务器 `d3a6d2241973` ⇒ 一致;下一棒 = `dsh-opensource-release/references/04-交互式图示-archify.md`。
|
||
|
||
## 09:50 技能重组线 · 第 7 棒(dsh-opensource-release/references/04-交互式图示-archify.md)
|
||
- ⚠️ 本棒原定 automation(dd0d1db8)08:51 到点**未启动**(无会话、state.json 停在 08:44、锁未持有)⇒ 用户 09:48 追问后由本会话直接执行;同一 automation 已改为第 8 棒。
|
||
- 备份:`E:/ProgramData/AI技能/aliyun-dsh-server/归档/humanize-原文件备份-20260922\dsh-opensource-release`(技能全 .md + 本轮改动前快照 `.pre-hz08`;文档库副本已备份)。
|
||
- 改动:精确串 11 处;通用破折号规则 1 处;反引号外「——」残留 = 0。
|
||
- 门禁 9 项 全过;落盘 = 否;字符 5477 → 5452,行数不变。
|
||
- 三处同步:本机 `(未同步)` | 文档库 `(未同步)` | 服务器 `(未同步)` ⇒ 待核;下一棒 = `dsh-opensource-release/references/04-交互式图示-archify.md`。
|
||
|
||
## 09:52 技能重组线 · 第 7 棒(dsh-opensource-release/references/04-交互式图示-archify.md)
|
||
- ⚠️ 本棒原定 automation(dd0d1db8)08:51 到点**未启动**(无会话、state.json 停在 08:44、锁未持有)⇒ 用户 09:48 追问后由本会话直接执行;同一 automation 已改为第 8 棒。
|
||
- 备份:`E:/ProgramData/AI技能/aliyun-dsh-server/归档/humanize-原文件备份-20260922\dsh-opensource-release`(技能全 .md + 本轮改动前快照 `.pre-hz08`;文档库副本已备份)。
|
||
- ℹ️ 本棒为修正后**重跑**:首次尝试(09:53)有 1 处 old 串转写错字(写成 `当「`,实为 `用「`)⇒ `fails` 非空 ⇒ 门禁按设计整体拦下、未落盘、锁已释放;修正后本次落盘=是。
|
||
- 改动:精确串 13 处;通用破折号规则 0 处;反引号外「——」残留 = 0。
|
||
- 门禁 9 项 全过;落盘 = 是;字符 5477 → 5452,行数不变。
|
||
- 三处同步:本机 `af7561987ed1` | 文档库 `af7561987ed1` | 服务器 `af7561987ed1` ⇒ 一致;下一棒 = `dsh-opensource-release/references/05-已知待办与漂移.md`。
|
||
|
||
## 09:54 技能重组线 · automation「补跑」清理(用户明令:不允许补跑)
|
||
- 判据:一次性 automation 跑完不自动转完成态,调度器有 **12 小时补跑窗口** ⇒ 能再被扫到的 = `scheduledAt` 落在**过去 12 小时内**且仍 `ACTIVE` 的记录。
|
||
- 按此筛,当时在册 15 条 automation 里符合条件的**只有本线 4 条**:`f37f1668`(第5棒 08:35) · `62d370fd`(下一棒 08:14) · `8370a5dd`(执行棒② 08:02) · `b56314b0`(执行棒① 07:06) ⇒ 已全部 `mode=delete` 删除。
|
||
- 其余 11 条(carbon/浮层/MCN 三线的过期 `ACTIVE` 7 条 + `PAUSED` 4 条)`scheduledAt` 都早于 12 小时窗口 ⇒ 物理上不会再跑;是否一并删除待拍板。
|
||
- 协议加固(已写进第 8 棒 prompt):每棒**必须 `mode=update` 复用自己那条 automation 记录**,⛔ 禁止 `mode=create` 新建 —— 残留的「已跑过仍 ACTIVE」旧记录正是补跑风险源;收尾再 `mode=list` 清本线过期残留。
|
||
- 当前在挂的唯一接续棒 = `dd0d1db8`(第 8 棒,`scheduledAt` 10:01,目标 `references/05-已知待办与漂移.md`)。
|
||
|
||
## 10:0x · 判定:carbon 线「两处平台侧阻塞」——**它测的不是我们的代码基**(只读取证,零改动)
|
||
|
||
**输入**:`E:/ProgramData/AI技能/dsh-plugin-carbon/对接文档_carbon插件-平台侧两处阻塞_20260922.md`
|
||
|
||
🔴 **核心事实(本轮新发现,最易再犯)**:carbon 线的 `testlocal` 跑的是**开源导出物**,不是 47/106 的源仓平台。两套命名**并存且都真实存在**:
|
||
| | 源仓 / 生产 | 开源导出物 |
|
||
|---|---|---|
|
||
| 服务 | `dshs` / `dshs-worker` | `dsh_ai1net.service` |
|
||
| env 前缀 | `DSHS_*` | `DSH_AI1NET_*` |
|
||
| 落点 | `/opt/dshs` · `/etc/dshs.env` · `/var/lib/dshs` | `/opt/dsh_ai1net` · `/etc/dsh_ai1net.env` · `/var/lib/dsh_ai1net` |
|
||
| 数据根权限 | **711 + users/ 711**(47 / 106 实测) | **700 + users/ 700**(testlocal 实测) |
|
||
| enablePatch | `/etc/dshs.env` **无** `DSHS_ENABLE_PATCH` ⇒ false(47/106 实测 0 命中) | `DSH_AI1NET_ENABLE_PATCH=true`(导出物 `install.sh` 默认写) |
|
||
⇒ **carbon 线的行号引用全对**(`dsh.ts:149-175` / `business-plugins.ts:649` / `orchestrator.ts:65,582,864` / `local-user-fs.ts` 逐条吻合),**但结论不能搬到 47**。
|
||
|
||
- **事项一**:本平台**不成立**(711 已落地)。真缺陷 = 导出物 `install.sh:275 run chmod 700 "$DATA_ROOT"` 与源仓既定口径冲突(`bootstrap-worker.sh:139-148` 明写"users 必须 o+x(711),否则 setpriv 无法穿越 ⇒ 实例起不来" + 幂等自检)⇒ 归属**开源导出线**,⛔ 不需走 R5(711 是本平台既定架构口径,档案 02/122/144)。
|
||
- **事项二**:源仓 `dsh.ts:152` 的 `/api/dsh/restart` **确实从不写 handoff**(只留日志)。其注释两条前提:①"线上为 false" **对源仓仍成立**(对导出物失效);②"handoff.json 无人消费" **本身写错** —— 消费者是 watchdog(`:65` WATCHDOG_TASK + `:864`),且 `business-plugins.ts:649` 平台自己每次启停插件都写 handoff(空命令)。
|
||
- 🔴 **安全面定位修正(关键)**:恢复 `command` **不新增**权限面 —— `handoffPath` = `<userRoot>/handoff.json`,而 `users/<id>` 属主就是该用户 uid(700)⇒ **用户自己写文件 + 触发一次重启**即可让 watchdog 执行(`dsh.ts:162` 无条件 `spawnWatchdog`,仅受 `enablePatch` 门控)。增量风险**全在 `enablePatch=true` 这一个开关上**(=平台侧 D8 候选A 已列的待拍板项)。执行者是用户在**自己的 bwrap 沙箱、自己 uid**(`orchestrator.ts:1073`),不是 root 链路;但它是 **headless agent 会话**(由 LLM 解析执行),有 token 成本。
|
||
- ✅ **比 carbon 三案更好的一条路**:平台**已有**正是它"乙案"想要的落点 —— `LocalSpawnerHooks.onInstanceStart`(序47·S4,`orchestrator.ts:801-818 / 878`):main 实例 spawn 前调用一次、Manager 注入 worker 不注入、唯一用途就是"平台侧代签 + 走 UserFs 投凭据到实例 home",且**失败不阻断启动**。⇒ 凭据投递可挂这里,不必启 enablePatch、不必恢复 handoff。
|
||
- ⚠️ 未核实项:47 与 106 的 `users/<id>` 单目录模式已见 700(预期);导出物在 account 模式下 700 是否会同时打崩实例(未复现,机制上可能 —— bwrap 内父目录权限与宿主不同)。
|
||
|
||
## 10:06 技能重组线 · 第 8 棒(dsh-opensource-release/references/05-已知待办与漂移.md)
|
||
- 备份:`E:/ProgramData/AI技能/aliyun-dsh-server/归档/humanize-原文件备份-20260922\dsh-opensource-release`(技能全 6 .md + `.pre-hz09` 快照;文档库副本已备份)。
|
||
- 改动:反引号外破折号 61 处(冒号 41 / 逗号 18 / 行首删除 2)+ 指代修正 2 处;门禁 9 项 全过;落盘=是;字符 22818 → 22684,行数不变。
|
||
- 三处同步:本机 `e9384f74632d` | 文档库 `e9384f74632d` | 服务器 `e9384f74632d` ⇒ 一致;下一棒 = `dsh-change-workflow/SKILL.md`。
|
||
|
||
## 13:2x · **开新线:插件投放与分库线**(用户提要求,当场收口开接续捧)
|
||
|
||
**用户原话(目标,⛔ 不得改写)**:「插件由admin上传后,应该是建独立库据库进行对接(需考虑更新插件的情况),然后分发到服务器worker,或端侧设备上,任何登录用户都可以访问才对」
|
||
⇒ 四条判据:**P1** 上传即建该插件独立库并对接 | **P2** 插件**更新**要对库有定义 | **P3** 分发到 **worker + 端侧设备** | **P4** **任何登录用户可访问**。
|
||
|
||
**现状缺口(本轮已核)**
|
||
- 一插件一库 = **已拍板未实现**(定稿 `架构设计/数据-分库与权威存储架构.md`;库里 `p_*` 零命中)。
|
||
- 分发现状 = admin 导入候选池 → 用户自助启用 → **实例内 `pnpm add`**(`business-plugins.ts:625-651`);**跨机那条腿是断的**(handoff 停写,`dsh.ts:149-175`)⇒ P3 当前不成立。
|
||
- 端侧**没有插件投放通路**(只有设备登录接入)。
|
||
- P4:逐用户启用 ≠ "任何登录用户可访问";语义待拍(入口 §4 三案)。
|
||
|
||
🔑 **本线已定技术方向(可推翻)**:**把「包投递」与「实例内安装」解耦** —— ① 包按**节点预铺**(平台/worker agent 铺到各节点本地包点,不在实例内执行命令)② 安装仍走实例内 `pnpm add file:<本机已有包>` ⇒ **跨机 handoff 不再必需**,也⛔ 不必给登录用户开"重启后执行命令"的口子(正是 carbon 线卡住的那处)③ 建库是**平台侧**动作(上传钩子里 `CREATE DATABASE dshs_pl_<pluginId>` + 迁移登记)④ 端侧先只做**可见性**。
|
||
- ⚠️ 分清两层:既有硬说明「投放业务插件只有一种方式(不铺 profile)」说的是**启用语义**;P3 是**包分发面**,⛔ 不是推翻它。
|
||
|
||
**接续捧(已登记)**:`79d47dff-9a32-4a12-8c34-b3cf1b05fac0` · 规划棒① · `scheduledAt` **2026-09-22T13:33** · `cwds` = 本工作区
|
||
**入口(唯一一份)**:`E:/ProgramData/AI技能/aliyun-dsh-server/接续入口_插件投放与分库线_20260922.md`
|
||
- 最终字节:**md5 `dd8d5f6dc754e3bb758a384e469c1a03`** / 6411 B(该值已写进启动棒 prompt 作口径门禁)
|
||
- ⛔ 规划棒只出**一份交接单**,不改码 / 不部署 / 不 commit / 不替拍 §4
|
||
|
||
**本轮未动**:源仓代码一行未改、47/106 未动、未 commit。carbon 线的文件一个字未动(判定也没回传)。
|
||
|
||
### 13:2x 补充:用户拍板 **C 档 + 四条硬口径**(入口已按此改写,md5 变更)
|
||
|
||
**用户原话(第二段,⛔ 不得改写)**:「c 全员默认可见,需自己开通,服务器上所有用户共享只读插件库,不会每个用户分发一份,使用中数据入库,文件保存到用户插件文件夹下对应插件名称的路径中」
|
||
|
||
⇒ 四条硬口径(已写进入口 §3,规划棒 ⛔ 不得改档):
|
||
- **D1**:候选池插件对**所有登录用户默认可见**,**开通由用户自己点** ⇒ ⛔ 不采"默认安装"
|
||
- **D2**:服务器上**一份只读插件库共享**,**⛔ 不给每个用户复制一份** ←(这一条**修正**了开线棒的方向:原写"实例内 `pnpm add file:<本机包>`"会各自复制进 profile)
|
||
- **D3**:使用中数据**入库**(`dshs_pl_<pluginId>`)
|
||
- **D4**:用户侧文件落 **「用户插件文件夹 / <插件名称> / …」**(目录真身须现场核实,⛔ 不许猜)
|
||
|
||
🔴 **我原来的方向被修正的一处**:D2 意味着**不能靠每用户 `pnpm add` 复制实体** ⇒ 首选改为**节点级共享只读 pnpm store**(`node_modules` 硬链/symlink,实体一份)+ 备选"解包到共享目录后 symlink";注意**硬链要求同一文件系统**、共享包目录要给用户 uid `o+rX`(`users/` 711 那类前例)。
|
||
- ⛔ 同时**去掉**了开线棒提的"admin 可标默认启用"(用户未要求)。
|
||
- 入口最终字节:**md5 `1f62266bce87a35bab652b7e1133610c`** / 8192 B(已同步更新启动棒 prompt 的门禁值,**复用同一条 automation**,未新建)。
|
||
- 新增 **§5-3 现场待核实清单**(用户插件文件夹真身 / 当前 pnpm store 形态 / 共享 store 的迁移与旧副本处理 / 端侧加载通路),要求逐条实测、⛔ 不许推断。
|
||
|
||
---
|
||
|
||
## 13:3x–13:5x · IM 群组线(档案 142):按最新两份架构定稿复核,**五单逐单对齐**(纯文档修订,零代码)
|
||
|
||
**用户问**:「im 的功能规划的如何,根据最新平台网络和用户层架构还有要更新的地方吗」
|
||
|
||
**取证(最小,全部实测)**
|
||
- `src/im/` **目录不存在** ⇒ IM 实施进度 = **零**;`src/db/schema.ts` 最新迁移 = **v13** ⇒ IM 三表取 **v14**(142 与 README 台账里写的 **v12** 系 09-20 旧值,已更正)。
|
||
- 五单 `IM群组-{A,B,C,D,E}` **全部 ⏳ 待执行**、**均未登记执行棒**;串行建议 = **A → B →(C ∥ D)→ E**(冲突域:A 独占 `schema.ts`;B 独占 `server.ts` 挂载+upgrade 分流)。
|
||
- 四个待拍板项**仍未决**:① UI 落点(门户 / 独立页)② E2EE 本期做不做 ③ 首批场景优先级(办公群 / MUD / 跑团)④ 房间上限与发言预算数值(待压测标定)。
|
||
|
||
**发现的对齐缺口(09-20 成稿 早于 09-21 形态定稿与 09-22 分库定稿)**
|
||
1. **库归属未点名** ⇒ 钉死 = **控制面库 `dshs`**(`DB-03 §〇` 明列 `rooms`/`room_members`/`messages`/`files` 为**内核表**、不适用插件数据面规范)。
|
||
2. **联邦形态缺失** ⇒ 房主 = **建房那个区的 Manager**;**跨区本期不做**(依赖 149 §七 F6 跨区可见性,待拍板)+ G3 用户无区字段;但三表须**留可扩字段**(148 §G4:`overlay_devices` 主键已含 `network`)。
|
||
3. **142 §3.5「relay / DIAL / presence 可直接复用」—— 整体错** ⇒ **relay 与 DIAL 不在 IM 依赖链上**(IM 走平台侧 HTTPS/WS);**presence 只复用机制口径**(1 s 批合并 / 只订阅可见成员),**通道须平台 IM hub 自建**(网络层跨用户隔离是**结构性**的 `dialers` 默认拒绝,接不出跨用户在线态)。
|
||
4. **A 单 §九-1 / D 单 §九-1 的插件数据落点已作废** ⇒ 改 **一插件一库 `dshs_pl_<pluginId>`** + 归属列强制;**删掉「用户自己的 ⇒ 实例 home」**(实例本地只留缓存/临时,权威必须在库)。
|
||
5. **C 单卡在一条今天不存在的通路** ⇒ 插件要装进**用户所在节点**的实例,而跨机投放腿 `writeHandoff` 因 `DSHS_ENABLE_PATCH` 默认 false **空转**(147 G 系列)⇒ 已登记依赖 = 「插件投放与分库线」P3,或把验收限定在 47 本机实例。
|
||
6. **D 单 §九-1** 同上;示例插件须按**新投放面**打包(非 09-20 的「实例内直装」)。
|
||
|
||
**交付(6 个文件,已落文档库工作树,⛔ 未 commit / 未 scp)**
|
||
- `04-调整方案/142-...md`:头部加**架构对齐状态块**(过程档案纪律:**正文不改**)+ 新增「🔴 现状复核(2026-09-22)」。
|
||
- 五单各加 **§〇 架构对齐节**(与正文同等效力);A 单三处就地更正(§四-1 / §六-8 / §九-1)、D 单 §九-1 表格就地更正。
|
||
- `交接单/README.md`:台账 A 单行 **v12 → v14** + IM 串行建议处加**对齐要点行**。
|
||
- `INDEX.md` 04-142 登记行:加复核结论(relay/presence 口径 · 库归属 · v14 · 五单已对齐)。
|
||
|
||
**本轮踩的坑(已记,方法论级)**
|
||
- 🔴 **Bash 工具的 shim 整体起不来**(`shell-runtime-bash-env.sh: line 3: dirname: command not found` ⇒ PATH 未注入 ⇒ `ls`/`cat`/`head`/`mkdir` 全 not found;`export PATH=<PortableGit usr/bin>` 也救不回——shim 在 bash 启动时重设 PATH 覆盖手设值)。连带 effect:**钩子要求先抢全局锁,而抢锁是 bash 脚本** ⇒ 直接 Edit 被「无锁」钩子拦下。
|
||
- ✅ **解法(可复用)**:写 `tmp/claim_lock.py`,用**托管 python 的 `subprocess`** 调 `PortableGit/bin/bash.exe`,并给它一份**含 `usr/bin`+`bin`+`mingw64/bin` 的 PATH** ⇒ `handoff-guard.sh --claim-exec/--release-exec` 均 **rc=0** 正常。⚠️ 抢锁失败时脚本 exit 1 且**中文输出会乱码** ⇒ 别把「乱码+exit 1」误判成「锁被占」(本次实测是 `mkdir` not found)。
|
||
- 锁已 `--release-exec` 释放;**未动**源仓代码 / 47 / 106。
|
||
|
||
---
|
||
|
||
## 13:40–13:5x · IM 拍板(用户四条答复)+ 🔴 未落文档(锁被别线占)
|
||
|
||
**用户原话(逐条)**:①「1 说的是什么 ui 具体点,现在应该只讨论了 im 架构和接口,没有功能把」②「2 一步到位 但要看似不似最佳方案」③「3 都需要」④「4 待定」
|
||
|
||
**口径判定**
|
||
- **① UI 落点 —— 仍未拍板**;用户要求先讲清「UI 具体指什么」⇒ 议题**细化为三案**:A 门户内嵌「会话」分区(`web/portal.html`)| B 独立聊天页(`web/im.html`,可挂子域)| **C 实例内工作台面板**(走 dsh 既有 slots 机制,与 MCN 工作台同族)—— ⚠️ **原 E 单只列 A/B,漏了「实例内」这一面**。
|
||
顺带确认用户判断成立:五单里 A/B/D = 内核与接口契约、C = agent 接入,**E 是唯一用户可见界面且只有「最小闭环」骨架**(进房 / 看消息 / 发消息+@ / 待应答开关 / 成员表 / 代答标识),**没有功能细化**(群管理、通知、搜索、文件、已读、线程均未定)。
|
||
- **② E2EE = 本期做(一步到位)** ⇒ 即原 D 单**候选 B**。⚠️ 但「**看似不似最佳方案**」**未找到等价判据** ⇒ 我按「**工程务实档**」理解(对称群密钥 + 成员分发 + agent 解密授权;⛔ 不做双棘轮 / 前向保密),**已在回复里请用户确认该理解**。
|
||
- **③ 首批场景 = 三类都要**(办公群 / MUD / 跑团)⇒ D 单判据由「至少跑通**一个**非聊天场景」**升级为三类各跑通一个**;C 单 `room_type` 三类各给一份默认值(聊天档 / 实时档限速 / 实时档回合制)。
|
||
- **④ 预算数值 = 待定** ⇒ 维持压测标定,A 单沿用保守初值。
|
||
|
||
**🔴 落地状态 = 未落文档**:13:40 抢全局锁**失败**,被 **`插件投放与分库线-规划棒①`**(09-22 13:34 起、未声明单号)持有 ⇒ 按 **R9 停手**(⛔ 不删锁、不接管、不硬改)。
|
||
⇒ 四项拍板**只记在本日志**,待该棒 `--release-exec` 后由**下一棒/下一会话**落到:`04-调整方案/142 §六`、`交接单/IM群组-C §四-8`、`IM群组-D §四-6/7`、`IM群组-E §四-4/5`、`交接单/README.md` 台账行。
|
||
⚠️ 顺带风险提示:那条线在改 `交接单/` 目录(产出新交接单)+ 可能碰 `README.md` 共享台账 ⇒ 与 IM 五单的落地**有重叠面**,更要串行。
|
||
|
||
**✅ 13:5x 更正(同轮内已闭环)**:第二次抢锁**成功**(那条规划棒已自行 `--release-exec`)⇒ 四项拍板**已全部落地**,落点 = `04-调整方案/142 §六`(改为「待定 / 待拍板(2026-09-22 更新拍板结果)」)+ `IM群组-C §四-8`(场景三类都要 · `room_type` 三类各一份默认值)+ `IM群组-D §四-6·7`(E2EE 本期做 · 协议先行 · 三类各跑通一个)+ `IM群组-E §四-4·5`(**新增候选 C 实例内工作台面板** · 场景已拍板)+ `交接单/README.md` 台账(加「四项拍板」行)。锁已释放,**未 commit / 未 scp**。
|
||
⚠️ 遗留待确认一项:E2EE 形态档位(用户原话「看似不似最佳方案」)—— 我按「工程务实档」暂记,**已在回复里请用户确认**。
|
||
|
||
---
|
||
|
||
## 16:4x · **开新线:IM 线**(IM 稳定性实现 + 开源框架调研)
|
||
|
||
**用户原话**:「im具体如何实 才能在复杂环境下稳定 还需要调研同类开源框架,比如telegram等海外通讯项目,找个最新性能稳定最好的」
|
||
|
||
**本轮做了什么(开线棒;本会话上下文已 23.5 万 ⇒ 收口开线、不硬做深度调研)**
|
||
|
||
- **实搜 3 次,固化 8 条事实**(框架 5 + 我方约束 3),全文落入口 §3 ⇒ ⛔ 下一棒不必重搜。要点:
|
||
- **ejabberd 26.07**(2026-07-30 发布)— Erlang/OTP,官方称**单节点 200 万并发会话**(生产实测),WhatsApp / Nintendo / BBC 在用;XMPP + 400 XEP + 内建 MQTT/SIP/Matrix;社区版 GPLv2。
|
||
- **OpenIM**(Go 微服务)— msggateway(WS) + Kafka + MongoDB + Redis + etcd;官方口径单节点 **10 万+ 连接 / P99 < 100 ms**;**seq 全局序列 + 增量同步**;十万级大群。
|
||
- **Matrix** — Synapse(Python) / **Dendrite(Go)**;联邦 + 强制 E2EE;运维复杂度最高。
|
||
- 🔴 **Telegram 官方服务端不开源**;2026 第三方实现三条:**Opengram**(C#/.NET9 · layer 216 · 微服务) / **Ferrite**(C# · layer 214 · 494/732 方法) / **teamgram-server**(Go · layer 222 · **社区版仅私聊+基础群**,频道/超群在商业版)。
|
||
- 其他:Tinode / Rocket.Chat / Mattermost / Zulip / Openfire / Jackal / lhttp。
|
||
- 🔴 **我的初判(写进 §3-6/8,供规划棒核)**:除 **ejabberd 是单运行时**外,**其余候选都要外挂 3–5 个组件**(Kafka/Mongo/etcd/MinIO)⇒ 与我方**单实例硬顶 1024 MiB / 宿主 1870 MiB** 直接冲突 ⇒ **整框架引入代价高**;更现实 = **借机制**(seq 增量同步 / CQRS 削峰落 PG `FOR UPDATE SKIP LOCKED` / 无状态网关 / 心跳僵尸清理 / 背压熔断)+ 必要时只引**单二进制**实时层。
|
||
- **交付**:新建 **`接续入口_IM线_20260922.md`**(本工作区根,只此一份)—— §0 状态 / §1 用户原话三判据 T1–T3 / §2 本轮动作(第 1 棒 = 规划棒出交接单)/ §3 八条事实 / §4 未拍板三项 / §5 开工校验(含 bash 故障绕法)/ §6 不要重做。
|
||
- 锁:抢 ✓ → 释放 ✓|**未改码、未动 47/106、未 commit**。
|
||
|
||
**下一棒** = IM 线第 1 棒(规划棒:稳定性机制清单 + 框架判据矩阵 + 三案选型建议),已按 §3.1.1 登记一次性 automation(+6 分钟),只挂这一个。
|
||
|
||
**环境备忘(本轮反复用到)**:Bash 工具 shim 坏 ⇒ 抢/放锁走 `tmp/claim_lock.py`(subprocess 调 PortableGit bash + 补 PATH);`state.py` 与 python 脚本可直接跑;抢锁失败的「乱码 + rc=1」不等于锁被占。
|
||
|
||
---
|
||
|
||
## 17:0x · 用户更正口径:**机器可以换**(IM 线规划修正)
|
||
|
||
**用户原话**:「机器可以换 不要那这个限制规划」
|
||
|
||
⇒ **资源(单实例 1024 MiB / 宿主 1870 MiB)不再是选型否决项**。已落**入口文件 5 处**:
|
||
- **§0** 加口径更新行(明令:⛔ 不得以"装不下 / 现有机器跑不动"否掉方案)
|
||
- **§1 T3**:判据主位改为 **稳定性 · 性能 · 可运维性**,资源只作「需求测算」输出
|
||
- **§2-3**:三案改按**四维**比较(+ 资源需求=测算值)
|
||
- **§3-6**:原「资源硬约束」改写为「资源口径(现实基线仅供测算)」
|
||
- **§3-8**:借机制的**理由改为"机制更可控可验"**,⛔ 不再是"塞不下" ⇒ **整框架引入重新成为平等候选**
|
||
|
||
⚠️ **我上一轮的初判("整框架引入基本不可行")据此作废** —— 那条判断的地基就是资源约束,现已被用户撤掉;正确口径 = **自研薄内核 / 整框架引入 / 混合**三案**同台按四维比较**(ejabberd 单运行时 200 万并发实证、OpenIM 整装微服务都回到桌面上)。
|
||
|
||
已同步更新接续棒 prompt(automation `2798048a-0ac8-4515-85bd-b58375e756ac`,**只改 prompt、未动 scheduledAt** —— 避开 §4-⑦「改时间不触发」的坑)。
|
||
落点:本工作区入口文件(非文档库 ⇒ 免锁);未改码、未 commit。
|
||
|
||
|
||
|
||
---
|
||
|
||
## 13:33–13:5x · 插件投放与分库线 规划棒①(产出交接单,未启动执行)
|
||
|
||
**开工三步**:① `state.py` ✅(锁空闲 · HEAD `3d8f50e` · 47 上无异常)② 口径门禁 ✅(入口 md5 `1f62266bce87a35bab652b7e1133610c` 现算一致,**未改档**)③ 抢全局锁 ✅(13:34 起持有,13:5x 已 `--release-exec` 释放)。
|
||
|
||
**产出**:`dsh-server-docs/交接单/插件投放与分库线-①共享只读包库与插件数据面.md`(8 段 + §〇对齐 + §九实测 + §十 DB-03 差异 + §十一未决)+ `交接单/README.md §一` 新增一行。占号 `交接单/.lock-插件投放-①`。⛔ 零代码改动 · 零部署 · 未 commit。
|
||
|
||
**四条现场实测(入口 §5-3 要求,⛔ 全部实取,非推断)**
|
||
- **「用户插件文件夹」真身 = 不存在**:四个候选路径(`home/.dsh/plugins` / `ws/plugins` / `home/plugins` / `ws/.dsh/plugins`)在 47 实测**全部 MISS** ⇒ D4 必须新定落点(推荐 `home/.dsh/plugins/<pluginId>/`,与既存 `home/.dsh/mcn-plugin.db` 同族)。既有最接近的是 `home/.dsh/`(含 mcn-plugin.db 2.4M)· `home/skills/` · `home/.dsh-stage/`(20+ 份历史 tgz)。
|
||
- **pnpm store = 每用户一份**:`<userRoot>/ws/.local/share/pnpm/store/v3`(47 的那份 99M;`home/.pnpm-store` 8K 是旧位置残留)⇒ `runPnpmAs` 的 `HOME=<ws>` 决定(`business-plugins.ts:438/458`,注释 `:492-494`)。全库仅 2 份 store(47/106 各一)。
|
||
- **旧路径兼容**:profile `dependencies` 实测 5 条 `file:` 里**已有 1 条指向共享池**(`@softspark/dsh-file-preview → /var/lib/dshs/business-plugins/…tgz`)⇒ A2(`file:` 指共享目录)**不是新发明**;`node_modules/dsh-plugin-mcn-suite` 实测为**软链 → `.pnpm/…`** ⇒ dsh 解析软链正常(复核 144 §8.2①)。
|
||
- **端侧插件加载通路 = 无**:只有设备登录接入;桌面线载体未产出(工作区顶层只剩 `CODEBUDDY.md`/`docs/`/`_t.txt`)⇒ 序46 步骤 7 仍停 `client-artifact-missing`。
|
||
|
||
**本棒为 D2 落点顺带取到的两条高价值读数**
|
||
- **bwrap 全参(47 活实例逐字)**:`--tmpfs /var → /var/lib → /var/lib/dshs → /var/lib/dshs/users` → `--bind <userRoot>/tmp /tmp` → `--bind <userRoot> <userRoot>` → **`--ro-bind-try /var/lib/dshs/bundled-skills`** → `--ro-bind-try <profile>/{cordis.patch.yml,package.json,pnpm-lock.yaml}` → `--unshare-pid --chdir <userRoot>/ws` → `setpriv --reuid <uid>`。⇒ **共享插件库落点/挂法可逐字照抄 `bundled-skills`**(`orchestrator.ts:1052-1054`),且**必须排在 `--tmpfs /var/lib/dshs` 之后**(实测证实 144 §8.6③);**profile 三件策略文件已是 ro-bind** ⇒ 装配只能平台在正确那台机上写(= G4)。
|
||
- **47 与 106 各有一份候选池且内容不同**(47 五个含 storyforge;106 三个是 09-13/09-14 人工 scp 的旧包)⇒ **147 G2「内容出不了 Manager」的实证 + 版本漂移实证**。
|
||
|
||
**方案主干(写进单的判定)**
|
||
1. 🔑 **技能共享层已把 D1+D2+「同名替换(=P2 更新语义)」全落过一次**(`skills.ts:429-499` 上传/替换+`:539-548` 用户面合并三类)⇒ 本线**不是新设计,是复用该形态**,只多「依赖树」与「数据面」两件事。方案可行性由此大幅上调。
|
||
2. **卡点不在部署,在"谁在哪台机上动手"**:装插件那步(`runPnpmAs` `business-plugins.ts:454-461`)是 Manager 本机 `setpriv pnpm` ⇒ 跨机必 `ENOENT`;但**重启探活那条腿已经通了**(`supervisor.restartAndProbe` → `remote-spawner.ts:388` 按 `hostIdFor` → worker `agent.ts:459`)⇒ 只需补 `POST /plugins/apply`(模型 = 既有 `/restart-probe/:userId`),⛔ **不新开入站口**。
|
||
3. **D1 其实已是现状**:`GET /api/plugins/mine`(`:578`,`requireAuth`)走 `listBusinessPlugins()`,实测 SQL = `SELECT … FROM business_plugins ORDER BY name ASC`(`pg.ts:420` / `repo.ts:677`)**无任何过滤** ⇒ 任何登录用户今天就能看到候选池全量。⛔ 别"顺手加过滤"。
|
||
4. **三个"今天不存在"要新建**:`/var/lib/dshs/bundled-plugins`(47/106 都没有)· `dshs_pl_<pluginId>`(`p_*` 零命中)· D4 的「用户插件文件夹」。
|
||
5. **前置缺口 P1b**:worker 上实例要读写插件库 ⇒ 需「实例→控制面」数据 API,而该通道**今天为 0**(147 G3 / 序47 §②.1)⇒ 单里标 P1b 并**建议与序47 步 6–9 合批**。
|
||
|
||
**待用户拍板(单 §四-2,只有两条)**:① 共享包库版本目录策略(A 留 K=2~3 版 / B 只留最新 —— 倾向 A)② D4 落点(A `home/.dsh/plugins/<pluginId>/` / B `ws/plugins/<pluginId>/` —— 倾向 A)。
|
||
|
||
**收尾自检**:`docs-audit.py` 退出码 **1**,但**归因清楚且非本棒引入** —— 【1】档案编号冲突全部来自 `dsh-server-docs/skills/*/references/0N-*.md`(`git status` = `??` **未跟踪**,属**技能重组线在途新增**)与根级 `0N-*.md` 同名;【8】缺状态 33 个在 `04-调整方案/`。**新单与 README 改动在 audit 输出里零命中**。⛔ 按「只做被明确要求的事」**未去改他线的文件**。
|
||
|
||
**⚠️ 给下一位**:本棒持有全局执行锁 13:34–13:5x;**已释放**。IM 群组线 13:40 因本锁按 R9 停手并把四项拍板只记进了本日志(见上一节)⇒ **该线现已可继续**,其四项拍板待落到 `04-调整方案/142 §六` 等五处。
|
||
|
||
---
|
||
|
||
## 16:37–16:5x · 插件投放与分库线 · 拍板闭合 + 排执行棒①
|
||
|
||
**用户回话**:「1 B,2 我说的是 平台用户的文件路径 和你说的是一个吗」。
|
||
|
||
**① 版本策略 = B(只保留最新一版)** —— 用户原话「1 B」。落档:单 §4.1 新增 `D-i`;§4.2-1 由"倾向 A"改为"✅ 拍板 = B",并写明 ⛔ 不许因"要回滚"反向在共享库留多版本;**§五 S5-a-3 回滚整段按 B 档改写**(原文"① 回滚指向旧版本目录"在 B 档下**不成立** —— 会做出不存在的目录)⇒ 改为:从运维备份面 `/opt/dsh/backups/plugins/<pkg>/<ver>.tgz` 取旧包 → 覆盖共享库**固定文件名** `bundled-plugins/<pkg>/pkg.tgz`(⛔ 路径不带版本号,否则老用户 `file:` 悬空)→ 对受影响用户**显式 `pnpm add file:` 重装**(⛔ 不能只 `pnpm install`,路径未变 pnpm 未必感知)→ 库不回滚。B 档**强制两条前置**:(a) 每次上传先落一份 tgz 到 `/opt/dsh/backups/plugins/`(否则无回滚素材)(b) 台账必须记当前版本号。无灰度 = 已接受的代价,③ 须报"影响 N 用户"。
|
||
|
||
**② 口径澄清:是同一件事** —— 用户说的「平台用户的文件路径」= **平台用户(租户)自己的目录** `<userRoot>` = 实测 `/var/lib/dshs/users/<userId>/`;候选 A/B **都长在这棵树下**,差别只在 home 面 vs ws 面。⛔ 别读成"插件作者侧路径"或共享包库(D2)那条。**D4 落点自决 = A**(`<userRoot>/home/.dsh/plugins/<pluginId>/`,单 §4.1 `D-j`,**可推翻**),判据 = 144 §6.2 五问第 1 问 + 与既存 `home/.dsh/mcn-plugin.db` 同族。
|
||
⚠️ 顺带钉死一条易混点:**平台侧路径 = 实例内可见路径(同一绝对路径)** —— bwrap 实测 `--bind <userRoot> <userRoot>` 同路径绑定、`--chdir <userRoot>/ws`,**不重映射**;但实例内 `HOME` = `ws`(`business-plugins.ts:438/458`)⇒ 插件 host 半边 ⛔ 禁 `homedir()` 拼路径,只走 SDK `im.paths.pluginData()`。
|
||
|
||
**改动文件(⛔ 零代码 / 零部署 / 未 commit)**
|
||
- `dsh-server-docs/交接单/插件投放与分库线-①共享只读包库与插件数据面.md`:状态行 · §4.1 加 `D-i`/`D-j` · §4.2 改题并加"平台侧 vs 实例内路径"注 · S5-a-3 回滚重写 · S5-b 标已定 A · §十一-1 改 ✅ · **修正一处自述错误**(原写"docs-audit 退出码 0" ⇒ 实为 **1**,两条均属他线存量,本单零命中)。
|
||
- `接续入口_插件投放与分库线_20260922.md`:§4 D4 下**加一行日期补记**(只记实况 + 指向单 §4.1/§4.2,⛔ 未改任何已拍板口径行)。
|
||
|
||
**已排执行棒(本轮收口 + 15 min)**:automation `6fb3f4b2-da91-4515-b8ac-8bbb81628777`「插件投放与分库线 · 执行棒①」`scheduledAt=2026-09-22T16:52`,`cwds` = 本工作区;prompt 只给指针(入口 §3–§6 + 单 §〇/§四/§五),要求抢锁、照单范围执行、撞 P1b/序47 就停手报告、收尾走交付门禁。⚠️ 本棒会占用锁 ⇒ 已有会话请让路。
|
||
|
||
**⚠️ 顺带发现(未处置,待你发话)**:automation 列表里 **13 个一次性任务全部 `ACTIVE` 且 scheduledAt 早已过期**(carbon 线 3 个 09-21 的 · 浮层线 2 个 · 技能重组线 10:13 · 本线规划棒 13:33 · MCN 收口 09-21 06:45 …)⇒ 已完成的棒没有下线,与"同一时刻只挂一个"的判据相冲,建议清一遍(⛔ 未经你发话我没有动它们)。
|
||
|
||
## 17:0x–17:3x · 🔴 插件投放与分库线 · 执行棒①(automation `6fb3f4b2`)—— **S1 全绿 · S2 全绿并已上线**
|
||
|
||
**入口**:`接续入口_插件投放与分库线_20260922.md` | **执行依据**:`交接单/插件投放与分库线-①共享只读包库与插件数据面.md`(§〇 对齐节 + §四 D-i/D-j 已闭合 + §五 S1 起)。**逐条读数全在单 §十二**,这里只留状态与判据。
|
||
|
||
### 落地的(已上线,两节点)
|
||
- **S1 承重墙**:`config.ts` 加 `bundledPluginDir`(默认 `<dataRoot>/bundled-plugins`)+ `orchestrator.ts` env 注入 + `--ro-bind-try`(紧邻 `bundled-skills`、在 `--bind root root` 之后)+ 中间目录集合收进去;47/106 各建 `0755 root:root` 目录;两节点 env 文件加 `DSHS_BUNDLED_PLUGIN_DIR`(已备份)。
|
||
- **S2 共享只读包库(D2 核心)**:`business-plugins.ts` 新增 `materializeShared` + `.manifest.json` + `POST /api/plugins/business/:id/share`(+body 形态 `POST /api/plugins/business/share`)+ `DELETE …/share` + `GET /api/plugins/shared`;上传钩子补落 B 档回滚素材。
|
||
- **实测已投放**:`[email protected]`(657 文件)· `@dsh-local/[email protected]`(117 文件),各 **1 份实体**、`root:root 755`、依赖树 `.pnpm` 就位、回滚 tgz 在 `/opt/dsh/backups/plugins/`。
|
||
- **S1-E 真机取证(106 非 admin 用户 `4092b965`/uid 100002)**:mountinfo `ro,nosuid,nodev` + 实例内 `ls` 可见 + `touch` = **`Read-only file system`** + `grep -c bundled-plugins` = 2。
|
||
- **零回归**:`npm test` **248/247 pass/0 fail/1 skip**(与开工基线同)。
|
||
|
||
### 🔴 必须记住的四个坑(详见 `PLAYBOOK-实例与插件坑.md §22`,⛔ 别重踩)
|
||
1. **部署真源 = `/opt/dshs/lib/`**(`lib/` 被 gitignore);`/opt/dshs` 的 git HEAD 是噪声。
|
||
2. **两节点 `lib/` 会静默分叉** —— **实测 106 缺 14 个 `.js`、33 个文件内容不同** ⇒ 新 `config.js` import 缺失文件 ⇒ `dshs-worker` **崩溃循环**。**已整包同步修好**(106 现与 47 逐字节一致);⛔ **部署后必须全量对账**(`is-active` 看一次不算)。
|
||
3. **沙箱内验收 `nsenter` 挑错 PID = 读的是宿主(假阳性)** —— bwrap 的父监控进程在外面;必须用 `ps -eo pid,comm,args | awk '$2=="node" && /dsh --profile/'` 那个 pid,且先确认 `/proc/<pid>/mountinfo` 里有 `bundled-skills`。
|
||
4. **共享层物化四个必修**:`--config.auto-install-peers=false`(`@deepseek-ai/dsh-client-*` 是 optional peer,公共源没有 `^0.1.2-rc.1`)| scoped 包名必须走 body 路由(`:id` 不吃 `/`)| 解包后 `chown -R root:root`(tar 按归档 uid 还原)| share 时兜底补回滚素材。
|
||
|
||
### 🔴 卡住 / 待拍板(⛔ 未自决)
|
||
- **S4 建库**:`dshs` 角色 **`rolcreatedb=f`** ⇒ 平台建不了 `dshs_pl_*`。放开要**扩权**(授 `CREATEDB` 或新建角色+新口令进 drop-in)⇒ 命中 **R5 权限只准收窄** + 边界外④ ⇒ **停下报告**。
|
||
- **S4-3 / D7**:`P1B_INSTANCE_TO_CONTROLPLANE_API_MISSING`(单里已标的前置缺口,未自造第二处身份判据)。
|
||
- **S5-b 第 3 条**:`PLUGIN_SDK_NOT_IN_THIS_REPO` —— 本仓 `im.paths`/`pluginData` **零命中**;属 dsh 宿主面,**R2 零改动** ⇒ 退出本单可动范围(D4 落点已定,可由插件侧按 `DSH_HOME` 约定自行拼)。
|
||
- **未做**:S3 跨机装配(`OUT_OF_BUDGET_S3`,本单剩余最大一块,留给下一棒)· S5-a 兼容矩阵 · S6 门户三段 UI · 跨节点内容分发(按 §十一-3 登记后续)。
|
||
|
||
### 其它
|
||
- 📄 单 §一 状态改 🟡、**§十二 执行回报已回填**(含 §六 验收逐条 ✅/❌ 与具名原因码);`交接单/README.md §一` 行已更新;`数据库/DB-03` 头部加**执行状态块**(数据面尚未开工,⛔ 别当已实现读)。
|
||
- ⛔ 未 commit / push;⛔ 未动他线文件;锁 `--claim-exec` 起、`--release-exec` 收。
|
||
- ⚠️ 顺手发现(未动手,只报告):`bt-server` ssh 配置与 `config/platform.env:61` 的 `32022` 均已陈旧(覆盖网络线 R4 已回收该口)。**已改用 `[email protected]:22`**。
|
||
|
||
## 17:4x · IM 线第 1 棒(规划棒)收口 ✅
|
||
|
||
**产出**:`dsh-server-docs/交接单/IM群组-稳定性机制与框架选型.md`(新增)
|
||
|
||
- **T1 稳定性机制清单 25 条**(连接 A1–A5 / 消息 B1–B7 / 存储 C1–C6 / 跨区 D1–D4 / Agent 插件 E1–E3),每条 = 机制 → 可复现判据 → 落点(`file:line` 或「待新建」)
|
||
- **T2 框架判据矩阵**:7 类候选 × 7 条判据轴。判定 = Telegram 系三支**出局**(方向不匹配:强绑账号体系 + 3–5 组件 + 无插件扩展点)、OpenIM **出局**(不支持 E2EE,与用户已拍板「本期做」硬冲突)
|
||
- **T3 三案**(自研薄内核 / 整框架引入 / 混合)各写优点与缺点 + 四维比较(稳定性 · 性能 · 运维复杂度 · 资源需求测算)
|
||
- **建议(自决,可推翻)** = **案 3 形态 + 案 1 路径**:连接层写成可替换(Centrifugo 类当**可选后端**),内核自研,⛔ 不现在引整框架
|
||
- 四个实验 **E-1~E-4**,顺序 E-3 → E-1 → E-2 → E-4,一律落 **106**
|
||
|
||
**判据轴的口径(本次关键判断)**:决定性轴 = ① 身份可复用性 ② 权威存储同库性 ③ 七扩展点承载能力 —— ⛔ 不用 star / 项目年龄 / 生态规模排序(依据 `dsh-decision-method §4.6`:「热度型论据不构成选型主依据」)。
|
||
|
||
**其他落点**:`交接单/README.md §一` 加一行;入口 `接续入口_IM线_20260922.md` §0 加收口行 + §2 推进到第 2 棒。
|
||
|
||
**下一棒**:automation `b656009b-ecdd-4f12-a077-d12e8294dce8`(一次性 · 2026-09-22T17:52)= 执行棒跑 E-3 + E-1。
|
||
|
||
**过程教训(值得记)**:Edit 的 `old_string` 命中了**非目标位置** —— 用「资源不是选型否决项。现实基线(仅供需求测算)」去锚 §0,但同一文本也存在于 §3 第 6 条 ⇒ 状态行被插进了 §3 事实段(已修复)。⇒ **锚点必须带足够上下文或选全局唯一片段**,⛔ 不用「看起来眼熟」的句子。
|
||
|
||
**检查读数**:`docs-audit.py` rc=1,但**全部为存量告警**(编号占用 / 档案元信息缺失 / 体量分布),报告里**不含本单文件名**,且【6】交叉引用 ✓ 无悬空 ⇒ **本单零新增问题**。另:hands-off 预检中「越界未提交改动 71 个」为工作区存量的跨线改动,与本棒无关(⛔ 未 push)。
|
||
|
||
---
|
||
|
||
## 18:0x · 插件投放与分库线(执行棒①续 · automation `6fb3f4b2`)—— 数据面管理面流程修订
|
||
|
||
- **锁**:全局执行锁被「IM线-第2棒」(17:53 起)占用 ⇒ 按 R9 **未落笔任何共享文件**(交接单 / DB-03 / 代码仓 / 服务器全未动)。本轮 = 只读取证 + 设计件 + 工作区记忆。
|
||
- **用户新增口径(本轮触发)**:admin 全流程自闭环 —— 上传 → 检测通过 → 点「建库」按钮 → **建库成功才能开启**;插件加「更新」按钮 → 更新也检测 → 通过后**告知数据库改动** → 「确认执行」按钮。⇒ 交接单 `§十-D1`「建库 = 上传钩子」**须改写**为 admin 显式按钮。
|
||
- **产出**:`交付物/数据面管理面-建库与更新流程-20260922.md`(7 态状态机 · 检测新增第 3 项「数据面声明校验」· `dsh.data` 静态声明格式 · API 契约 · 两段式「预演→确认执行」· 改动文件清单 · 需回改条款 · 权限三候选取舍)。
|
||
- **实测读数(47)**:`dshs` 角色 `rolsuper=f / rolcreatedb=f / rolcreaterole=f`;`postgres` 超级用户;`dshs` **拥有** `dshs` 库 ⇒ `has_database_privilege('dshs','dshs','CREATE')=t`(**建 schema 零扩权**);`dshs_pl_%` 库数 = **0**;PG **13.23**。`plugin_datastores` / `plugin_data_audit` **均不存在**(信息模式零命中)。
|
||
- **🔴 新发现(版本元数据脱钩)**:`business_plugins` 记 mcn-suite `version=0.3.9 / file_size=1953267 / updated_at=09-13 00:02`,磁盘池内 tgz 实为 `0.3.13 / 1989680B / mtime=09-20 22:13`,且 **sha256 与共享层 `.manifest.json` 相同**(= 同一份文件)⇒ 该 tgz **09-20 被平台外方式替换**、记录从未同步。对照 `@dsh-local/storyforge` 记录与磁盘**逐项一致** ⇒ 走上传 API 的路径本身没问题。⇒ 更新/回滚判定只认**内容指纹**,⛔ 不用记录字段。已沉淀 **PLAYBOOK §22.5**。
|
||
- **待拍板(唯一一项)**:建库权限怎么给 —— **A** `ALTER ROLE dshs CREATEDB`(最简、任意库名)/**C** 改「一插件一 schema」(零扩权、today 可跑,但降级隔离且改定稿)/**D** `SECURITY DEFINER` 建库函数 + `GRANT EXECUTE`(扩权最小、库名强约束、不动定稿)。**我选 D**。
|
||
- **顺带修正**:工作区 MEMORY 旧口径「`ls`/`cat` 也 not found ⇒ export 救不回、改走 PowerShell」**不成立** —— 本机 09-22 复测 `export PATH=<PortableGit 1.2.0 的 usr/bin:bin>:$PATH` **一次即恢复**;真判据 = **每条 bash 命令都要前置**(工具调用间不共享 shell 状态)。已回改 MEMORY + 新增 **PLAYBOOK §22.6**。
|
||
|
||
---
|
||
|
||
## 18:2x · IM 线(第 2 棒 · 执行棒 · automation `b656009b`)—— 交接单 §八 步 1–4 完成
|
||
|
||
- **锁**:抢到并已释放(17:5x–18:2x 持锁)。⛔ 47 只读、未改一行;⛔ 未 commit/push/scp;⛔ 未改 `src/**`。
|
||
- **前置核实 4/5**:`src/im/` 不存在 ✅|`schema.ts` 最新 = **v13**(⇒ 不触发「改去 A 单」)✅|upgrade 回调 **`proxy.ts:759`**(规划读数 718 已漂移)✅|`npm test` = **248 / 过 247 / 败 0 / 跳过 1**(53.5 s)✅|🔴 第 4 条(47 上 `dshs` 的 `\dt`)**未核实**。
|
||
- 🔴 **实测环境漂移(两条,都会打穿既有脚本/文档)**:① **47 的 sshd 已从 32022 漂到 22 端口**(32022 双方向 refused;`~/.ssh/config` 的 `bt-server` 别名**已失效**,需改 `Port 22`)。② 47 上 **`docker ps` 无任何容器**、`dshs-pg` 容器不存在 —— PG 是**本机进程**(`127.0.0.1:15432`,pid 662048),且 `su postgres` 无 unix socket 可用、`/root/.pgpass` 不存在 ⇒ 凡「`docker exec dshs-pg psql …`」写法的取数命令**全部失败**。⛔ 未取用凭据,故该前置项如实记「未核实」+ 替代证据(`schema.ts` 内三表 DDL 命中数 = **0**)。
|
||
- **E-3 = 能**:ejabberd 26.07 可用既有 `users` 作外部认证源,且**不落地自己的账号表**(干净 mnesia 起点下 `passwd` 表不存在;探测库全程只有既有 `users`)。**协议真源 = `src/extauth.erl`**:`open_port({spawn,Prog},[{packet,2}])` ⇒ 双向 2 字节大端长度前缀;请求 `auth:user:server:password`;**应答载荷必须 2 字节 `[0,N]`**(端口 list 模式,匹配 `{data,[0,N]}`)—— 只回 1 字节 `[N]` 会得 `unexpected_response`。🔴 **附带发现**:ejabberd 对 `check_password` **有进程内缓存**(改既有表的口令**不立刻生效**、`systemctl restart ejabberd` 后立刻生效)⇒ 案 2 需追加「查缓存开关/TTL」前置项。
|
||
- **E-1(106 实测 · 零依赖手写帧自研 WS)**:依据 = 平台 `dependencies` **无 `ws`**(只有 fastify/pg/better-sqlite3 等)⇒ 自研形态即零依赖。1k/5k/10k 三档 **upgrade 全成功、fail 0、errors 0、心跳超时率 0**;RSS 58.6→70.7 / 95.6 / **131.8 MiB**(边际 **7.5 KiB/连接**);**P99 = 45.276 / 256.314 / 421.109 ms**;**P99 ≈ 事件循环最大滞后**(34.7/258.6/405.2)⇒ 瓶颈 = **单进程全量扇出的排队**,⛔ 不是连接数供给。协议另用 **Node 内建 `WebSocket`(undici,独立实现)**交叉验证通过。
|
||
- **新机制判据(补充探针)**:客户端进程 SIGKILL 后 **≤3 s** 全部回收,走**写失败**路径(`errors=200`)而**非**心跳(`hbTimeouts=0`);观测窗口短于扇出间隔时 TCP 半开**不产生 `close` 事件** ⇒ **判离线必须有心跳兜底**(支撑机制 A1/A4)。
|
||
- **产出**:交接单 `IM群组-稳定性机制与框架选型.md` 追加 **§十二 执行回报**(八段全)+ **§十三 断言名清单草案(18/18,A5+B7+C6)**;入口 `接续入口_IM线_20260922.md` §0/§2 已推进。⚠️ **单外缺陷(只报告未动手)**:该单 §八 步 2 写「20 条」与 §九「18 条」矛盾,「20」为笔误。
|
||
- **106 足迹(已停用未删除,待拍板)**:`/opt/ejabberd`(4M) + `/opt/ejabberd-26.07`(53M) + `/var/lib/pgsql/im-e3probe`(47M) + `/root/im-e1`(212K) + `/root/im-e3`(22M) + 系统用户 `ejabberd` + 单元 `ejabberd.service`(已 stop+disable)≈ **126 MB**。⚠️ 厂商 `.run` 安装器**越过声明的 `/root/im-e3` 范围**装到 `/opt` 并建系统用户/单元(`--help` 本身即触发安装,事前无法预知)。
|
||
- **脚本落点(⚠️ 仅工作区 `tmp/`,未进代码仓)**:`tmp/im-e1/`(server / wsworker / run / crosscheck / postkill / sum)+ `tmp/im-e3/`(extauth.py / users.sql / mkhash.mjs / e3_*.sh / probe47.sh)。
|
||
- **下一棒**:automation **`21443815-acec-4069-94ad-6be3242c9d5d`**(一次性 · 18:23)= 第 3 棒(E-2 / E-4 / 二次判定 / 步 8 回填)。
|
||
- **顺带**:本机 bash shim 本轮**整体可用**(`ls`/`cat`/`head`/`grep`/`md5sum`/`tail` 均正常)⇒ 入口 §5「shim 已坏」应改为「**时好时坏**」。
|
||
|
||
---
|
||
|
||
## 19:0x · IM 线第 3 棒(执行棒续)收口 ✅ —— E-2 实测 + 二次判定 + E-4 + 步 8 回填
|
||
|
||
- **本轮动作** = 选型单 §八 步 5–8(步 1–4 属第 2 棒)。⛔ 未改 `src/**`、未动 47、未部署、未 commit/push/scp。实验全落 106。
|
||
- **E-2 结论(Centrifugo v6.9.6,106 同机同脚本)**:10k 连接 upgrade 全成功、**零掉线**(服务端权威 `POST /api/channels` 稳态 `num_clients` = 1000/5000/10000 = 目标值;`open_fds` = 1009/5008/10009)。客户端观测 **P99 = 28.9 / 68.1 / 111.9 ms**、扇出离散中位 **17.2 / 57.9 / 97.8 ms** ⇒ 均优于自研(**1/2.7、1/1.9**);代价 = **边际每连接 70.1 KiB vs 自研 5.9 KiB(11.9×)**、稳态总量 **750.2 vs 116.0 MiB(6.5×)**、goroutine 数 = 2N+89。
|
||
- 🔴 **保活方向相反(实测,非文档推断)**:Centrifugo **不发服务端 WS ping**(`wsPingRecv=0`,含单连接 25 s 探针);判别实验 ① 客户端**不发** ping ⇒ **26 s 内 10000/10000 全被断开**(`tcp_est` 10000→0)② 客户端**按连接计时**发 ping ⇒ 连续 66 s `num_clients` 恒 10000、`closed=0`。
|
||
⚠️ **踩坑留痕**:最初客户端用**整进程统一计时**发首 ping(固定第 10 s)与认证竞争 ⇒ t≈10 s 一次掉 **2773** 条 ⇒ **首轮 6 轮读数全废**(改成"认证成功后才起计时"即零掉线)。教训 = **A1 判据必须把保活计时起点绑到该连接自己的认证完成时刻**。
|
||
- **二次判定(陈述句)= 本期不引入外部连接层组件**(维持案 3 形态 + 案 1 路径,连接层写成**可替换接口**)。依据三条:① E-1 三档 `服务端 P99 ≈ 事件循环最大滞后`(10k **340.3 vs 342.5 ms**)⇒ 瓶颈是**可就地解决的扇出排队**,⛔ 不是连接供给(10k 只多 57 MiB);② E-2 的延时代价换不来"再加一个进程/端口/配置/JWT-APIKey/Prometheus/升级面 + 认证生效时机不由我们控制"(E-3 的 `check_password` 进程内缓存)—— ⚠️ 按「机器可换」口径**内存不是否决项**;③ 保活计时语义有硬要求。**回退判据(可证伪)**:扇出改造后 10k 档 P99 > **150 ms** 或离散中位 > **120 ms** ⇒ 引入。**建议 E-5** = 自研扇出分片批写,同机同脚本复测。
|
||
- **E-4 结论=可行**:E2EE 最小路径 = 三层密钥(设备身份 Ed25519 / 房间 `DK(room_id, epoch)` / **成员公钥包裹的密钥包**)+ 两张新表(`im_room_keys` / `im_agent_grants`)+「**加人不换钥、移除必换钥、轮换先于消息可见**」+ 客户端只接受 `epoch >= 本地最大`;出 **8 条可断言路径**(E4-1…E4-8);**relay 零改动**(结构性只见密文)。⚠️ 形态档位(工程务实档 vs 双棘轮)**仍未拍板**。
|
||
- **步 8 回填**:§四 **25 条机制全部有归属** —— A 单 16 + B 单 5 + C 单 3 + D 单 3 + E 单 2(共 29 行;组 E 与 A3 跨"实现层/契约层/客户端层"两端各写一条并注明同源);⛔ 未改 A–E 任一单既有结论。
|
||
- **产出**:交接单 `IM群组-稳定性机制与框架选型.md` 追加 **§十二 执行回报(IM线-第3棒)**(含 E-2 对照表 + 归因结论 + 二次判定 + E-4 + 回填行号表 + 遗留 7 条);A–E 五单各新增 `### 六.1 追加判据`;入口 §0/§2 已推进。
|
||
- **106 足迹新增(已停用未删除,待拍板)**:`/root/im-e2` ≈ **90 MB**(centrifugo 二进制 67 MB + tar.gz 23.5 MB + 读数与脚本);`ss -lntp` 无 180xx 监听。本机副本 = 工作区 `tmp/im-e2/`(`e2worker.mjs` 双协议客户端 / `e2pub.mjs` / `e2run.mjs` / `e2sum.py` / `config.json` / `hb.sh` / `tl.sh`)。
|
||
- ⚠️ **两条环境事实**:① 登记门禁 `handoff-guard.sh` **必须带 `ME="<线名>-第N棒"`** —— 不带会因「锁被自己占用」**误报不可放行**;② `dsh-server-docs/交接单/` 在 `.gitignore:45` 内 ⇒ 该目录文件**不会**出现在 guard 的「我声明的」列表(属正常,非缺陷)。③ 106 上 Centrifugo v6 配置键已改位:`client.token.hmac_secret_key` / `http_api.key` / 端口用 `--http_server.port`(顶层 `token_hmac_secret_key`、`api_key` 均**不被识别**且会连带禁用 HMAC)。
|
||
- **下一棒**:automation **`db244c60-db22-4d7d-87a9-bced514bbea4`**(一次性 · 19:13)= 第 4 棒(E-5 扇出分片对照 + 补跑 §九 判据 6/7 平台回归)。
|
||
|
||
## 19:2x · IM 线第 4 棒(执行棒)收口 ✅ —— E-5 扇出分片对照(**回退判据触发**)+ 判据 6/7 补跑
|
||
|
||
- **本轮动作** = 主任务 **E-5(自研扇出分片对照实验)** + 补跑选型单 §九 判据 6/7。⛔ 未改 `src/**`、⛔ 未改 `/root/im-e1/*.mjs`、未动 47、未部署、未 commit/push/scp。实验全落 106。
|
||
- **E-5 实现**:106 `/root/im-e2/server-shard.mjs`(**新建** 268 行;蓝本 `/root/im-e1/server.mjs`)。改造 = ①**分片扇出**(连接按 `id % SHARDS` 分片,一轮 = `SHARDS` 个独立 `setImmediate` 轮次)②**同片批写**(同片共享同一 frame Buffer + 每连接 `cork/uncork`;`BATCH=0` 可关)。`/metrics` 原字段逐条不变(新增只有 `shard*` 前缀诊断字段),`msgsSent` 语义核对 = 6×N ✅。本机副本 `tmp/im-e5/`(脚本 + `out/e2-raw-*.json` 十份)。
|
||
- 🔴 **核心结论(回退判据被触发)**:**同会话**内 E-1 基线复跑 10k = 客户端 P99 **268.6** / 离散中位 **171.8** / 滞后 349.7 / RSS 115.9;分片版两跑 = P99 **337.6 / 359.4**、离散中位 **227.6 / 225.5**、滞后 **27.0 / 19.9**、RSS 107.9 / 132.6 ⇒ **判据(P99 ≤150 / 中位 ≤120)两项均未达线,且相对基线变差**。1k 也变差(49.8 > 44.7);5k P99 略好(196.1 < 215.6)但离散中位变差(123.2 > 100.7)。
|
||
- 🔴 **被证伪的是「分时」这一手法(重要,接手别重复)**:滞后 349.7 → **13.0–46.9 ms**(13–27×)**但端到端整圈反变长** —— 整圈墙钟由 `write()` 的**绝对调用总量**决定、不由事件循环是否阻塞决定;拆成 S 段后每段之后要先让出 loop 抽干该片客户端的回包(ack/pong)才轮到下一段 ⇒ 最后一段到达更晚。**分片数 4/8/16 与批写开/关四个变体全部无改善**(P99 330.2–359.4、中位 213.8–230.1)⇒ **A 候选(自研)的正确形态是「并行(多进程)」,⛔ 不是继续切细"分时"**。
|
||
- ⚠️ **RSS 判为噪声内**(10k 五变体 107.9–132.8 vs 基线 115.9 ⇒ 跨跑方差 **±12 MiB / ±10%**)⇒ 后续内存判据**必须重复 ≥3 次取中位**,⛔ 单次不定论。**基线本身也有 ±20% 跑间方差**(5k P99 182.4→215.6、10k 299.7→268.6)⇒ **跨会话直接相减会把方差当效果,对照必须同会话**。
|
||
- 🔴 **门禁执行**:改造判为**负向**(有档位 P99 变差)⇒ 停下复盘、⛔ 未硬推;引入外部组件属**影响面扩大** ⇒ **⛔ 未擅自引入**,三条候选(A 就地继续优化(多进程并行)/B 引入 Centrifugo 类外部连接层/C 混合)各带优缺点**竖排成段**交用户拍板(收在选型单 §十二 第4棒 段末「待拍板」)。
|
||
- **判据 6 / 7**:判据 6 ✅ `npm test`(Node v22.22.2)= **248 / 247 过 / 0 败 / 1 跳过 / 53.5 s**,与第 2 棒基线一致;判据 7 ⚠️ **测试集内无该用例 ⇒ 未覆盖** —— `grep -rin "101" test/` 零命中、`grep -rln "proxy" test/` 零命中(`proxy.js` 只由 `scripts/verify-inject.cjs` 做静态校验);最接近的既有用例 = `test/relay.test.mjs:1383 rawWsProbe(...)`(**中继侧** WS upgrade,⛔ 不是"用户→自己实例"子域 upgrade);⛔ 未自造用例。
|
||
- **产出**:选型单新增 **§十二 执行回报(IM线-第4棒)**(改造点对齐表 / 三档同会话对照表 / 10k 六变体归因表 / 回退判据判定 / 判据 6-7 / 遗留 6 条 / 待拍板三候选)+ 第 3 棒 §十二-5「回退判据」处追加 🔻 块(⛔ 原文未改);入口 §0/§2 已推进。
|
||
- ⚠️ **新发现单内不一致(只报告未动手)**:A 单 §五-1 验证句与 §六 判据 1 写「新库 `schema_migrations` 最新 = 12」,而 §〇-5 定本单取 **v14** ⇒ 疑为旧值残留(已写进第 5 棒 prompt 让其按 v14 判断并在回报里指出)。
|
||
- **106 足迹**:本轮仅在 `/root/im-e2/` **新增 1 个脚本 + 10 份读数 JSON**;全部进程已停(`ss -lntp` 无 180xx、`pgrep` 无实验进程);是否清理 `im-e1`/`im-e2`/`ejabberd` 遗留足迹仍属**待拍板项**(未执行)。
|
||
- **下一棒**:automation **`e1c1b3a5-0c11-4544-8b28-6fa80684d9de`**(一次性 · 19:33)= 第 5 棒(**开工 A 单**:房间内核与 DB ⇒ `src/db/schema.ts` v14 + `src/im/**` + `test/im-store.test.mjs`)。
|
||
|
||
---
|
||
|
||
## 19:3x · 插件投放与分库线 —— 建库权限**已拍板 = D**(用户原话「D」)
|
||
|
||
- **拍板内容**:不自研 sudo helper、不给 `dshs` 角色 `CREATEDB`,改用 **`SECURITY DEFINER` 建库函数 + `GRANT EXECUTE`** —— 扩权面最小且库名被白名单强约束,同时保住定稿「一插件一库」。
|
||
- **产出**:`交付物/插件建库权限-D档落地Runbook-20260922.md`(前置实测 · 幂等 DDL · 7 条验收 · 回滚 · 必须同步落的三处文档 · 平台侧错误码映射)。
|
||
- **新取到的前置读数**:
|
||
- 超级用户通路 **可用** —— `su postgres -c 'cd /tmp && psql -h /var/run/postgresql -p 15432 -d dshs …'` ⇒ `current_user=postgres`(`local all all peer` + socket `/var/run/postgresql/.s.PGSQL.15432`)。⚠️ **必须先 `cd /tmp`**,否则 `su` 切目录被拒 ⇒ 误报「No such file or directory」。
|
||
- `pg_hba` = `host all all 127.0.0.1/32 scram-sha-256` ⇒ `dshs` 对**新库** `dshs_pl_*` 也能连(不必改 hba)。
|
||
- `dshs` 角色 **无任何角色成员**(无旁路);既有 `dshs_%` 函数 **0 条**。
|
||
- 🔴 **PG13 默认 `public` schema 上 PUBLIC 有 CREATE**(ACL `{postgres=UC/postgres,=UC/postgres}`)⇒ **函数不得放 `public`**,改放 `postgres` 拥有的私有 schema `dshs_int`(PUBLIC 已 REVOKE、只给 `dshs` USAGE);函数体 `SET search_path = pg_catalog`。
|
||
- PG 数据目录 = `/var/lib/dshs-pg`(非 `/etc/postgresql/13/main` —— 旧写法找不到文件)。
|
||
- **🔴 未执行**:全局执行锁已被 **IM线-第5棒**(19:33 起)接手占用(该线在连续跑棒:第2棒 17:53 → 第5棒 19:33)⇒ 动服务器受 R9 管辖 ⇒ Runbook 只备不跑。
|
||
- **未登记下一棒**(自决):本线剩余动作**全部**依赖「文档/代码/服务器」三类,撞他人锁必空跑 ⇒ 登记只烧钱;Runbook 已到「照抄即可执行」程度,锁一释放即可跑。
|
||
|
||
## 19:4x · IM 线第 5 棒(执行棒)收口 ✅ —— **A 单落地**(v14 房间内核 + `src/im/**`),五单合流第一单开工
|
||
|
||
- **本轮动作** = 开工 **A 单**(`交接单/IM群组-A-房间内核与DB.md`):5 条只读前置核实 ⇒ 步骤 1–7 ⇒ §六(含 §六.1)验收 ⇒ §八 格式回填(新建 §十 执行回报)。
|
||
- **交付**(`D:\github\dsh_shenxian`,⛔ 未 commit / push / 部署):`src/db/schema.ts` 追加 **v14**(三表 + 索引,**192 增 / 0 删**)|新建 **`src/im/{types,db,store,clock}.ts`(963 行)**|新建 **`test/im-store.test.mjs`(552 行 / 23 用例)**|`package.json` 的 `test`/`verify` 各登记 1 处。
|
||
- **判据读数**:`npm run build` rc=0 | `node --test test/im-store.test.mjs` = 23/22 过/0 败/1 跳过 | `npm test`(Node 22)= **271/269 过/0 败/2 跳过**(基线 248 ⇒ +23 全为本单)| `grep -c "^ { version:"` = **14** | 全仓改动 72 → 74(增量恰为本单 2 个已跟踪文件)。
|
||
- 🔴 **两条执行口径(已写进该单 §十.7)**:
|
||
- **`src/im/db.ts` 是本单为「只经 db 适配层」立的窄端口** —— 既有 `DbAdapter` 是表级接口、⛔ 无通用 SQL 面,而 §三 把改动面限死为「`schema.ts` + `src/im/**`」⇒ 不能往 `adapter.ts`/`sqlite.ts`/`pg.ts` 加方法。端口只有 `all`/`run`/`exec`(SQL 用 `?`),SQLite 绑定走既有 `openDatabase`(顺带 WAL + 外键 + 迁移),PG 绑定走既有依赖 `pg`(`?`→`$n` 翻译,跳过字符串字面量)。**换后端只换该文件,`store.ts` 一行不动。**
|
||
- **判据 3「不同到达序 ⇒ 同一数组」在 DB 层的分界**:§四-6 定「`seq` 由 DB 按到达序分配」⇒ 两种到达序的 `seq` **本来就不同**(既定设计,非缺陷)。故拆成两条可机械断言的:① `listMessages` 恒按 `seq` 严格递增;② 规范序函数 `orderDrafts` 对同一集合的 3 种到达序输出同一数组(**含落库往返版**)。
|
||
- 🔴 **对 §四-5 的一处自决(可推翻,已写成显式用例)**:`id = sha256(authorId ‖ canonical(payload))` —— **含作者**。纯内容哈希会让同房不同作者的同文消息互相吞掉(agent 的「收到 / 好的」很常见)= 静默丢消息;改后同作者同内容仍恰好 1 行(判据 2/9 全保持)。`canonicalJson` 递归排序键 ⇒ 键序不同内容相同仍去重。
|
||
- ⚠️ **卡点(既有、与本单无关)**:`npm run verify` rc=**1**,卡在 `scripts/verify-platform-admin-section.mjs`(`require()` + 顶层 await ⇒ `ERR_AMBIGUOUS_MODULE_SYNTAX`;该文件未经改动)⇒ 链路其后 6 步逐条补跑 **rc 全 0**,`check-layering` **✅ 无新增违规**(仅 ⚠️ `src/im/*` 4 文件未归类,不影响退出码)。⛔ 未顺手修(超 §三 改动面)—— ⚠️ **后续任何棒别再把它误判成本次改动引起的**。
|
||
- ⚠️ **待报告未改**:① A 单 §五-1/§六-1 的「最新 = 12」是 09-20 旧值残留,**实测 14**;② `交接单/README.md §一` 里 A 单仍「⏳ 待执行 / ⛔ 未登记」(同类也见于已收口的选型单)⇒ 依「只做被明确要求的事」只报告。
|
||
- **下一棒(唯一)** = 第 6 棒 · 执行棒 = **开工 B 单**(平台 API 与用户端 IM WS 通道),`scheduledAt` = 19:4x + 5~8 min;⚠️ B 单 §五-5 含房间上限与发言预算的**标定**(A 单只落待标定初值 2000/10/30 s)。
|
||
|
||
---
|
||
|
||
## 20:0x · 插件投放与分库线 —— 试抢执行锁失败(IM 线连续接力占锁)
|
||
|
||
- **用户问「可以继续处理了吗」** ⇒ 答案:**还不能**。全局执行锁仍在 IM 线手上,且又换手一次:**第6棒 19:53 起**(该线 17:53 → 19:33 → 19:53 连续接力)。
|
||
- **试抢结果**:`--claim-exec` ⇒ `✗ 抢锁失败:已有执行会话在跑 —— 占用者:IM线-第6棒`。按 R9 **停手**:⛔ 未删锁、⛔ 未接管。
|
||
- **本轮新增可执行件**:`交付物/插件建库权限-一次性执行.sh`(已过 `bash -n`,纯 LF)——
|
||
一条命令完成:**抢锁 → 落地 D 档 DDL → 跑 7 条验收 → 自动放锁**,占锁窗口约 **40 秒**。
|
||
设计意图 = 把不可用的"长占锁"改造成可插入 IM 线**棒间收口间隙**的短任务。退出码:`0`=全绿 / `2`=DDL 或验收失败 / `3`=抢锁失败(立刻退出,不空转)。
|
||
- **锁饥饿的根因**:单一全局执行锁 + 两条线都要长独占 ⇒ 连续接力的一方永远赢。**长任务(S4 代码实现,小时级)无法靠抢间隙完成** ⇒ 需用户定排期。
|
||
|
||
---
|
||
|
||
## 20:2x · IM 线第 6 棒(执行棒)收口 ✅ —— **B 单落地**(平台 API + 用户端 IM WS 通道),五单合流第二单
|
||
|
||
- **本轮动作** = 开工 B 单(`交接单/IM群组-B-平台API与用户端IM-WS通道.md`):5 条只读前置核实 ⇒ §五 步 1–6 ⇒ §六 + §六.1 验收 ⇒ §九 执行回报(**新建独立小节追加**,原文未改)。
|
||
- **交付**(`D:\github\dsh_shenxian`,⛔ 未 commit / 未部署):新建 `src/im/hub.ts`(372) + `src/im/ws.ts`(542) + `src/web/routes/im.ts`(333);改 `src/supervisor/proxy.ts`(+30/−0:**upgrade 委托钩子** `setUpgradeDispatch` + 回调首行分流)+ `src/web/server.ts`(本单 +7)。
|
||
- **判据读数**:`npm run build` rc=0 | `check-layering` ✅ 无新增违规(rc=0)| `npm test`(Node 22)= **271 / 269 过 / 0 败 / 2 跳过** | **本机端到端验收脚本 15 PASS / 0 FAIL / rc=0** | `npm run smoke:subdomain` = `OK: subdomain routing + auth flow passed`。
|
||
- **标定(§五-5)**:A 单初值 `2000 / 10 / 30 s` **建议不回填** —— 投递增量严格线性(10/100/1000/2000),2000 连接 hub 内扇出 P99 = **0.023 ms**;presence 1000 成员房每人每分钟一次变化 ⇒ **5.0 帧/秒**(C=5,低 3333×)/ **1000.0 帧/秒**(C=1000,低 17×);预算三档具名码逐条命中、人类不受限频。
|
||
- **关键设计(可复用的坑)**:① **不能挂第二个 `app.server.on('upgrade')`** —— Node 把同一 upgrade 事件**并发投给所有监听器** ⇒ IM 路径会被子域判定判失败并静默 destroy;正解 = 在既有回调首行加**委托钩子**(答 true 立即返回)。② `/api/im/` 下非 WS upgrade **fail-closed 404**(落到隧道会被静默销毁 ⇒ 排查时看不出是路径写错)。③ 实例凭据 `x-dsh-im-instance-token` 两处 **501 fail-closed**(留给 C 单)。
|
||
- **实测教训(写进第 7 棒 prompt 了)**:`fetch()` 带自定义 `host` 头**不生效**(假 404)⇒ 多 Host 场景必须 `http.request`|本机脚本的 Node 进程会因伪实例子进程持句柄**不自退** ⇒ 显式 `process.exit()`|假时钟驱动 hub 时**每轮必须重置 `now`**(时钟倒退 ⇒ 1 s 批次闸门整窗失效 ⇒ 读数假 0)。
|
||
- **未完成项**:nginx `/api/im/ws` location **未应用**(⛔ 不部署,草案在回报 §九.6)|§六.1-9 的「跨进程重启」半程未实测|`GET /api/im/rooms` 为 `O(房间数)` 查询(需 A 单 `store.ts` 加下推查询)。
|
||
- **下一棒(唯一)** = 第 7 棒 · 执行棒 = **开工 C 单**(Agent 接入插件),automation `d2161e2a-bb50-47e2-9a26-50f291fc1d0b`,`scheduledAt` = 20:26。
|
||
|
||
## 20:0x–20:3x · 锁机制模块化:域锁可行性设计与门禁草案(会话 `锁机制模块化-域锁设计`)
|
||
|
||
**用户需求**:「后续想多个会话并行开发,全局锁导致无法并行」→ 追问「网络层/数据库层/IM层/插件管理层是否独立模块可单独锁定」→ 定调「分层判定 + 秒级独占,检查遗漏与误判」→ 最终指令「会话执行任务前先判断涉及范围是否都可锁定,确认锁定后开始处理」。
|
||
|
||
### 交付
|
||
|
||
- **交接单**:`dsh-server-docs/交接单/锁机制模块化-域锁与任务分级.md`(设计定稿 · 待执行;含 16 条实测前置、6 条执行坑、E1–E7 验收、回滚)
|
||
- **门禁草案**:`dsh-server-docs/tmp/锁机制草案-20260922/preflight-lock.sh`(已实测两用例:IM 线判"可锁"、混含 schema/server 判"走秒级独占")
|
||
- 台账 `交接单/README.md §一` 已加本单一行。
|
||
|
||
### 核心结论(★ = 新判据,纠正前两轮误判)
|
||
|
||
1. ★ **判据从"扇入次数"换成"层号"** —— 仓库早有权威分层:`docs/architecture.md` R1–R4(层①`web/`·`cli.ts`|②`domain/`|③`supervisor/db/fs/worker/net/nginx`|④`config/crypto/isolation/index`)+可机检的 `scripts/check-layering.mjs`。前轮用"引用次数"把 `net/relay`(扇入 24)误判为"半独立";按 R1 它是层③,改动只向下向内 ⇒ **可全程域锁**(⛔ 只要不动 export 签名)。
|
||
2. ★ **`src/im` 扇入仅 1**(1877 行 = A 单 963 + B 单 914),文件边界天然不重叠(`store/types/clock/db` ∥ `hub/ws`)⇒ 台账原写「A→B 必须串行」**是全局锁逼出来的,不是代码结构要求** —— 域锁价值的最强实证。
|
||
3. ★ **`CODEBUDDY_SESSION_ID` 与 hook payload 的 `session_id` 同源**(实测 env 值 = 转录文件名)⇒ 抢锁可自动绑定会话,脚本能认领自己的锁、能判持有者活性。
|
||
4. ★ **实验证实同文件两处并发文本替换无覆盖丢失** ⇒ 共享文件(台账/MEMORY/INDEX)靠 Edit 天然冲突检测即可,不必锁。
|
||
5. **layerOf() 未登记 `src/im`**(返回 null ⇒ 分层检查对它**静默不判**);**R2 不可机检**("同层依赖前先问它属哪层");**`src/domain/` 目录不存在**(层②为空 ⇒ 基线 5 条 ④→① 违规无处可去,是"缺领域层接口"的实证)。
|
||
6. **本机代码仓包管理 = npm**(`package-lock.json`,无 `.pnpm`)⇒ `CODEBUDDY.md §7`「一律走 pnpm」仅适用图形/实例侧,对代码仓不适用。
|
||
7. 构建全局独占:`outDir=lib` 共享 + `npm test` 先全量 build + 27 个测试文件全量跑 ⇒ 两会话同时测试互相覆盖且信号污染。定了先落 B2(只跑自己域的测试)。
|
||
|
||
### 技术附录(自证 + 坑)
|
||
|
||
- 坑:`grep -c "a\|b\|c"` 在 BRE 下 `\|` 是**字面竖线**,21 这个数不是挂载点个数 ⇒ 计数类判据一律换精确命令。
|
||
- 坑:本机 bash PATH 缺失(`dirname/ls/head` 全 not found,rc=127)⇒ 每条命令前置 `export PATH=<PortableGit 1.2.0>/usr/bin:.../bin:$PATH`(PB §22.6 有全文)。
|
||
- 本会话两次抢锁:20:0x 抢失败(IM线-第6棒 持锁)⇒ 按 R9 只读分析;20:22 抢成功(该棒已收工)⇒ 落盘。**收尾已 `--release-exec`**。
|
||
|
||
|
||
---
|
||
|
||
## 20:2x · IM 线第 7 棒(执行棒)收口 ✅ —— **C 单落地**(agent 接入插件),五单合流第三单
|
||
|
||
**动作** = 开工 C 单(`交接单/IM群组-C-Agent接入插件.md`):5 条只读前置核实 ⇒ §五 步 1–7 ⇒
|
||
§六 + §六.1 验收 ⇒ §九 执行回报(新建独立小节追加,⛔ 原文未改)。
|
||
|
||
**交付**(`D:\github\dsh_shenxian`,⛔ 未 commit / 未部署 / 未动 47·106):
|
||
新建 `src/im/instance-token.ts`(197) + `src/im/agent-bridge.ts`(341) +
|
||
`poc/im-agent-bridge/{package.json,cordis.patch.yml,lib/index.js}`(368) + `test/im-agent.test.mjs`(29 用例);
|
||
改 `src/web/routes/im.ts`(+78) · `src/im/ws.ts`(+12) · `src/web/server.ts`(+14) ·
|
||
`src/supervisor/orchestrator.ts`(+55) · `src/supervisor/remote-spawner.ts`(+30) · `src/worker/agent.ts`(+3)。
|
||
|
||
**判据读数**:`npm run build` rc=0 | `node --test test/im-agent.test.mjs` = **29/29 过 0 败** |
|
||
`npm test`(Node 22)= **300 / 298 过 / 0 败 / 2 跳过**(第 6 棒基线 271 ⇒ +29 全为本单)|
|
||
`check-layering` rc=0 ✅ 无新增违规(仍提示 `src/im/*` 未归类,不影响退出码)| 登记门禁 rc=0。
|
||
|
||
**两处 501 预留点已换真校验**:`imAuth`(REST)与 `doUpgrade`(WS)—— 正确 token ⇒ 通过;
|
||
不认 / 过期 / 跨区 ⇒ **401 且不升级**(⛔ 不再 501,"功能没做" vs "你没被认出来"要分得开)。
|
||
|
||
**关键设计(可复用的坑)**:
|
||
1. 🔴 **`src/im/ws.ts` 的 `doUpgrade` 用注入钩子而非直接 import 登记簿** —— 直接在 `src/im/**`
|
||
里 import `web/*` 类型会**分层反向**(`check-layering` 就是查这个)。改法 = 加 `resolveInstance`
|
||
钩子(与既有 `authenticate` 同形),由 `im.ts` 注入。判据一字不变。
|
||
2. 🔴 **登记簿必须"发放侧与校验侧同簿"** ⇒ 由 `web/server.ts` 建**唯一一份**
|
||
`app.imInstanceTokens`,**同时**交给 `LocalSpawner` 与 `RemoteSpawner` 的 hooks。
|
||
只挂 `LocalSpawner` 的后果 = 一换集群形态就"一条 IM 凭据都不产生"(**缺省失效,长得像功能没做**
|
||
—— 与序47 S4 踩过的坑同型)。⇒ 按 R11 补了 `remote-spawner` → `worker/agent` 的投递腿
|
||
(Worker 侧没有登记簿,签不了也校验不了,只能原样注入 env)。
|
||
3. ⚠️ **插件包 `lib/index.js` 是纯 JS,⛔ 不许带 TS 类型标注** —— 写 `import { connect, type Socket }`
|
||
会让 ESM 直接 `SyntaxError: Unexpected identifier 'Socket'`(本机 `node --test` 立刻炸)。
|
||
4. ⚠️ **往 md 追加长文本别用 bash heredoc** —— 单引号会炸(本轮踩过:`unexpected EOF while
|
||
looking for matching '`)⇒ 改成 Write 一个 `.py` 再跑。
|
||
5. 🔴 **两处"明示未接"(⛔ 别当缺陷重做)**:① 本地 LLM 会话对接留成可注入接口
|
||
`opts.askLocalSession`(`defaultAsk` 是占位)—— 属 D 单扩展点契约面;② "标识截图"因 E 单未开工
|
||
而给的是消息 JSON。⇒ 本棒保证的是**触发 / 上下文 / 预算 / 回写 / 隔离**五条判据可验。
|
||
6. ⚠️ **凭据用内存登记簿的代价(已写进 C 单 §九.6-4)**:平台 `dshs` 重启后,**已签发但实例还在跑**
|
||
的 token 会全部失效(那批 agent 桥 401,直到实例重启)。按 §四-5 口径可接受。
|
||
|
||
**五条只读前置**:5/5 已核(插件 server 侧 21 行 vs client 4001 行 · client 靠 session cookie 跨子域 ·
|
||
env 注入块在 `orchestrator.ts` 的 `baseEnv` · 插件形态 = `dsh.bundle.patch` 走候选池 ·
|
||
worker/实例只拨出)⇒ ⛔ 未触发停手。
|
||
|
||
**卡点 / 未完成**:`check-layering` 的 `src/im/*` 未归类(改它超改动面 ⇒ 只报告)|
|
||
「30 s 内自动恢复」只验了退避参数与 resume 路径,**未做真 socket 端到端**(属部署面)|
|
||
`npm run verify` 仍必红(卡在**既有** `verify-platform-admin-section.mjs`,与本线无关)。
|
||
|
||
**下一棒(唯一)** = **第 8 棒 · 执行棒** = automation `c57c0577-f2ab-496a-91dd-24d90bb405fa`
|
||
(一次性 · `scheduledAt = 2026-09-22T20:53`)= **开工 D 单**(`交接单/IM群组-D-插件SDK与扩展点契约.md`)。
|
||
|
||
|
||
---
|
||
|
||
## 21:1x · IM 线第 8 棒(执行棒)收口 ✅ —— **D 单落地**(插件 SDK 与扩展点契约),五单合流第四单
|
||
|
||
**本轮只做一件事**:开工 D 单。⛔ 未 commit / 未 push / 未 scp / 未部署 / 未动 47·106 / 未引入新依赖。
|
||
|
||
### 交付(本机代码面)
|
||
|
||
- **新建 `src/im/sdk/{types.ts(458), host.ts(695), index.ts(38)}`**(共 1191 行)= 七个扩展点的契约层 + 宿主实现(注册面 / `ImCircuitBreaker` / 声明式建表 / 数据端口 / 审计)。
|
||
- **新建三个示例插件** `poc/business-plugins-im/{office-tasks, mud-rate, trpg-turn}/`(`package.json` + `cordis.patch.yml` + `lib/index.js`,共 475 行)—— 按 §四-7 拍板口径**三类场景都给**。
|
||
- **新建 `test/im-sdk.test.mjs`(1019 行 · 66 用例)**;`package.json` 的 `test`/`verify` 各登记 1 处。
|
||
- **新建说明页** `dsh-server-docs/docs/IM插件SDK与扩展点契约.md`(面向插件作者)。
|
||
|
||
### 判据读数
|
||
|
||
| 项 | 读数 |
|
||
|---|---|
|
||
| `npm run build` | rc=0 |
|
||
| `node --test test/im-sdk.test.mjs` | **66 / 66 过 / 0 败 / 0 跳过** |
|
||
| `npm test`(Node 22) | **366 / 364 过 / 0 败 / 2 跳过**(第 7 棒基线 300 ⇒ +66 全为本单) |
|
||
| 内核零改动 | `git diff` 对 `src/im/store.ts`/`hub.ts`/`web/routes/im.ts` **空**;`git status --short src/im` **无 `M` 标记** |
|
||
| `check-layering` | rc=0 · **✅ 无新增违规**(仍提示 `src/im/*` 未归类,不影响退出码) |
|
||
| `npm run verify` | ⚠️ 必红且与本线无关(既有 `verify-platform-admin-section.mjs` 的 `ERR_AMBIGUOUS_MODULE_SYNTAX`) |
|
||
|
||
### 三条硬判据
|
||
|
||
1. **七点齐备**:`EXTENSION_KEYS` 恰为 142 §3.6 的七个(顺序一致);三插件合起来全覆盖,缺一即 fail。
|
||
2. **三类场景各跑通一个**:office(聊天档)/ mud(实时档 + 限速)/ trpg(实时档 + 回合制),各落一行到自己的表,**内核一行未改** ⇒ 142 §五-6 通过。
|
||
3. **失败模式齐备**(§六.1-8):每个扩展点条目都带 `timeoutMs` + `circuit`,缺一注册不通过。
|
||
|
||
### 本轮新踩的坑(已固化,⛔ 别重踩)
|
||
|
||
1. 🔴 **「内核零改动」不能用 `git status --short` 判** —— `src/im/**` 在 A–D 四单落地后**仍未 commit** ⇒ 整块显示 `??`(未跟踪,不是改动)⇒ 首版判据假红。**正确判据 = 内容级**:每个模块单独 `git diff` 为空 + `status --short src/im` 里无 `M` 标记。
|
||
2. 🔴 **文档库的 `Edit`/`Write` 要求先持锁** —— 先释放锁再追加入口/文档会被钩子拦(报「无锁 ⇒ 不允许直接修改」)。⇒ 追加类操作**分批抢锁**:抢锁 → 写文档 → 放锁 → 过门禁 → 再抢锁写入口。
|
||
3. 🔴 **写库测试的桩必须做「表全名归一」**(`p_<pluginId>_<name>`)—— 否则「裸名 vs 全名」被当成两张表 ⇒ 房间 ACL / 跨前缀判据**假绿**。
|
||
4. ⚠️ **熔断降级方向 = 放行**(不是拒绝):「插件故障不拖垮房间」若判成"拒绝发言",插件一挂整个房间就哑了 —— 与 §四-2 约束 2 正相反。已写成用例钉住。
|
||
|
||
### 口径更正(本棒新增,⛔ 未改 A–E 任一单的既有结论)
|
||
|
||
D 单 §九 表格里的 **`im.data`** 是 09-20 规划态命名 —— 执行落成的是 **`ImDataPort` 注入式端口**(真库端口要接 `src/db/**` 迁移器与 `plugin_data_audit` 表,属**建库面** = 「插件投放与分库线」,本棒不越界)。⇒ 已在 D 单 §九 就地加**只标注的落点说明块**(⛔ 正文未改)。
|
||
|
||
### 🔴 停手等拍板(本轮**未登记**任何接续棒)
|
||
|
||
**E 单开工前必须定「UI 落点」**,三案**各有优有劣、无客观优劣**(属 §1 边界外第 ⑤ 类)⇒ ⛔ 不替用户拍:
|
||
|
||
- **候选 A · 集成进门户**(`web/portal.html` 新面板):优点 = 与账号/成员/管理面同域同一次登录,成员与权限天然对齐平台用户;缺点 = 门户与「实例内聊天」是两套线程,房间消息要在平台侧渲染,页面变重。
|
||
- **候选 B · 独立页**(`web/im.html`,可挂子域):优点 = 与门户解耦、可按全屏消息流设计,改动面小、回滚干净;缺点 = 多一个入口要维护(登录态/导航/i18n 各一套),用户要记住「聊天在这里」。
|
||
- **候选 C · 实例内工作台面板**(走 dsh 既有 slots):优点 = **与 agent 同一屏**(代答与上下文都在自己实例里),复用既有面板与插件分发面;缺点 = 房间是跨用户平台级资源 ⇒ 成员表/消息仍走平台 API,同一房间在**每个用户实例里各渲染一份**(UI 状态不共享),交付面落进实例内插件链。
|
||
|
||
**E 单原倾向**(可被推翻):**候选 A**。⇒ 拍板到手后再登记 E 单接续棒。
|
||
⚠️ 另有一项仍悬:E-5 实测「分时扇出」无效 ⇒ 连接层三候选(A 就地优化(多进程并行)/ B 引入 Centrifugo 类 / C 混合)—— 拍板前 ⛔ 不引入任何外部组件。
|
||
|
||
### 五单合流进度
|
||
|
||
A ✅(房间内核 + v14 三表)→ B ✅(平台 API + 用户端 WS)→ C ✅(agent 接入插件)→ **D ✅(插件 SDK 与扩展点契约)** → **E ⏸ 待拍板(UI 落点)**。
|
||
|
||
|
||
---
|
||
|
||
## 锁机制模块化 — 域锁落地(会话 `锁机制模块化-域锁落地`|22:0x–23:0x)
|
||
|
||
**起点**:用户「检查下全局锁的规则,看是否能优化为模块锁,后续想多个会话并行开发,全局锁导致无法并行」→ 四轮追问 → 拍板「会话执行任务前先判断,当前任务涉及范围是否都可锁定,确认锁定后开始处理」→ 选 **A**(直接落地脚本改造)。
|
||
|
||
### 落地清单(全部完成)
|
||
|
||
| 项 | 文件 | 状态 |
|
||
|---|---|---|
|
||
| 域锁三件套 + `.gate` 临界区 + 骨架/发布锁 + `--locks` | `scripts/handoff-guard.sh` | 完成 |
|
||
| 无锁拒写钩子改域判定(三段分支) | `scripts/lock-guard-hook.py` | 完成 |
|
||
| 三态锁展示(空闲/域锁/旧全局锁) | `state.py` | 完成 |
|
||
| `src/im` 定层(未归类 12→1,层③ 63→74) | `scripts/check-layering.mjs` | 完成 |
|
||
| 开工门禁转正 | `scripts/preflight-lock.sh` | 新建 |
|
||
| 规则层四处同步 | `CODEBUDDY.md §6`/技能 `dsh-auto-handoff-chain` 铁律②/`docs/会话与接续/会话接续规范_20260916.md §3.4`/`交接单/README.md` | 完成 |
|
||
| 台账状态行 | `交接单/README.md §一` | 改「已落地」 |
|
||
|
||
### 本轮实测挖出的 4 个真缺陷(都是「假绿」型,静默失效)
|
||
|
||
1. **逗号分隔多域被压成 1 个** —— `--domains "a,b"` 被 shell 当**一个** token ⇒ `cut -d/ -f1-2` 把 `a,b` 当路径切。修:`norm_domain` 先 `//,/ /` 再逐段处理。
|
||
2. **两侧域键算法不一致** —— shell 取「前两段」、Python 取「首个锚点段」⇒ 同一文件算出不同键。修:**统一为「首个锚点段/下一段」**,且锚点表顺序**先长后短**(`dsh-server-docs` 在 `scripts` 前),两侧**逐字一致**(`_ANCHOR_SEGS` ⇄ `_DOMAIN_SEGS`)。
|
||
3. **`.gate` 临界区被 SIGTERM/SIGPIPE 打断 ⇒ 泄漏** —— 管道 `| head` 提前关流 ⇒ trap 没跑 ⇒ 后续所有抢锁卡满 10 s。修:加 `trap _gate_guard EXIT INT TERM HUP`。
|
||
4. **`ids` 并集导致假绿(最严重)** —— `ids = {payload_session_id, env_CODEBUDDY_SESSION_ID}` 取**并集** ⇒ 伪造/过期 payload id 叠加进程 env 真实 id ⇒ 假会话被判成「我」⇒ **放行**。修:**payload 有 session_id 就只用它**,env 仅缺省兜底。
|
||
+ **同会话二次抢锁 `rm -rf` 重建把前一把域声明冲掉** ⇒ 别人能抢进来。修:改为**合并**(追加去重到 `DOMAINS`)。
|
||
|
||
### 验证(全部通过)
|
||
|
||
- 域隔离四场景:B `src/im` 成功 ∥ C `src/net` 成功 ∥ D 抢 `src/im` **拒 rc=1** ∥ E `src/db` 成功
|
||
- 同会话合并:两把域锁并成 `src/im`+`src/net`;异会话抢已占域 **拒 rc=1**
|
||
- hook 四场景:我改我域 放行 0 字节|无锁会话 拒(分支③)|别线改我域 **拒 + 精确冲突域(分支②)**|别线改自己域 放行
|
||
- `state.py` / `preflight-lock.sh`(【A】/【B】/【E】三类判定)全通
|
||
|
||
### 关键判据(写给后续会话)
|
||
|
||
- **域键算法两侧必须逐字一致** —— 改一侧必须同步改另一侧,否则域锁**静默失效**。
|
||
- **`--domains` 用逗号一次给**:`--claim-exec "<会话名>" --domains "src/im,src/net"`。
|
||
- **无 `--domains` ⇒ 退化全局独占**(旧 `.exec-lock` 兼容分支保留 —— 在跑会话用旧 hook 快照)。
|
||
- **机制层(`config`·`crypto`·`isolation`·`index`·`scripts`·`CODEBUDDY.md`·锁与钩子本身)仍须独占**。
|
||
- 排期铁律② 粒度从「全库」改「**每条线**」:不同线可各挂一棒(域不重叠真并行),同线仍只许一棒。
|
||
- ⚠️ **改 `settings.json` 的 hook 后需完全重启会话**才加载(旧快照仍用旧逻辑)。
|
||
|
||
### 收尾
|
||
|
||
测试残留已清;`tmp/锁机制草案-20260922/` 已删(转正到 `scripts/preflight-lock.sh`)。本会话结束前释放域锁。
|
||
|
||
|
||
---
|
||
|
||
## 锁机制模块化 — 域锁落地(会话 `锁机制模块化-域锁落地`|22:0x–23:0x)
|
||
|
||
**起点**:用户「检查下全局锁的规则,看是否能优化为模块锁,后续想多个会话并行开发,全局锁导致无法并行」→ 四轮追问 → 拍板「会话执行任务前先判断,当前任务涉及范围是否都可锁定,确认锁定后开始处理」→ 选 **A**(直接落地脚本改造)。
|
||
|
||
### 落地清单(全部完成)
|
||
|
||
| 项 | 文件 | 状态 |
|
||
|---|---|---|
|
||
| 域锁三件套 + `.gate` 临界区 + 骨架/发布锁 + `--locks` | `scripts/handoff-guard.sh` | 完成 |
|
||
| 无锁拒写钩子改域判定(三段分支) | `scripts/lock-guard-hook.py` | 完成 |
|
||
| 三态锁展示(空闲/域锁/旧全局锁) | `state.py` | 完成 |
|
||
| `src/im` 定层(未归类 12→1,层③ 63→74) | `scripts/check-layering.mjs` | 完成 |
|
||
| 开工门禁转正 | `scripts/preflight-lock.sh` | 新建 |
|
||
| 规则层四处同步 | `CODEBUDDY.md §6`/技能 `dsh-auto-handoff-chain` 铁律②/`docs/会话与接续/会话接续规范_20260916.md §3.4`/`交接单/README.md` | 完成 |
|
||
| 台账状态行 | `交接单/README.md §一` | 改「已落地」 |
|
||
|
||
### 本轮实测挖出的 4 个真缺陷(都是「假绿」型,静默失效)
|
||
|
||
1. **逗号分隔多域被压成 1 个** —— `--domains "a,b"` 被 shell 当**一个** token ⇒ `cut -d/ -f1-2` 把 `a,b` 当路径切。修:`norm_domain` 先 `//,/ /` 再逐段处理。
|
||
2. **两侧域键算法不一致** —— shell 取「前两段」、Python 取「首个锚点段」⇒ 同一文件算出不同键。修:**统一为「首个锚点段/下一段」**,且锚点表顺序**先长后短**(`dsh-server-docs` 在 `scripts` 前),两侧**逐字一致**(`_ANCHOR_SEGS` ⇄ `_DOMAIN_SEGS`)。
|
||
3. **`.gate` 临界区被 SIGTERM/SIGPIPE 打断 ⇒ 泄漏** —— 管道 `| head` 提前关流 ⇒ trap 没跑 ⇒ 后续所有抢锁卡满 10 s。修:加 `trap _gate_guard EXIT INT TERM HUP`。
|
||
4. **`ids` 并集导致假绿(最严重)** —— `ids = {payload_session_id, env_CODEBUDDY_SESSION_ID}` 取**并集** ⇒ 伪造/过期 payload id 叠加进程 env 真实 id ⇒ 假会话被判成「我」⇒ **放行**。修:**payload 有 session_id 就只用它**,env 仅缺省兜底。
|
||
+ **同会话二次抢锁 `rm -rf` 重建把前一把域声明冲掉** ⇒ 别人能抢进来。修:改为**合并**(追加去重到 `DOMAINS`)。
|
||
|
||
### 验证(全部通过)
|
||
|
||
- 域隔离四场景:B `src/im` 成功 ∥ C `src/net` 成功 ∥ D 抢 `src/im` **拒 rc=1** ∥ E `src/db` 成功
|
||
- 同会话合并:两把域锁并成 `src/im`+`src/net`;异会话抢已占域 **拒 rc=1**
|
||
- hook 四场景:我改我域 放行 0 字节|无锁会话 拒(分支③)|别线改我域 **拒 + 精确冲突域(分支②)**|别线改自己域 放行
|
||
- `state.py` / `preflight-lock.sh`(【A】/【B】/【E】三类判定)全通
|
||
|
||
### 关键判据(写给后续会话)
|
||
|
||
- **域键算法两侧必须逐字一致** —— 改一侧必须同步改另一侧,否则域锁**静默失效**。
|
||
- **`--domains` 用逗号一次给**:`--claim-exec "<会话名>" --domains "src/im,src/net"`。
|
||
- **无 `--domains` ⇒ 退化全局独占**(旧 `.exec-lock` 兼容分支保留 —— 在跑会话用旧 hook 快照)。
|
||
- **机制层(`config`·`crypto`·`isolation`·`index`·`scripts`·`CODEBUDDY.md`·锁与钩子本身)仍须独占**。
|
||
- 排期铁律② 粒度从「全库」改「**每条线**」:不同线可各挂一棒(域不重叠真并行),同线仍只许一棒。
|
||
- ⚠️ **改 `settings.json` 的 hook 后需完全重启会话**才加载(旧快照仍用旧逻辑)。
|
||
|
||
### 收尾
|
||
|
||
测试残留已清;`tmp/锁机制草案-20260922/` 已删(转正到 `scripts/preflight-lock.sh`)。本会话结束前释放域锁。
|
||
### 🔴 收尾验收又抓到一个真缺陷(机制层误判为「可锁定」)
|
||
|
||
**现象**:`preflight-lock.sh "会话" scripts/handoff-guard.sh`(纯机制层)⇒ 文件正确落【E】,但结论仍是「**可以锁定**」+ `rc=0`。
|
||
**危害**:门禁对本任务**自己的目标文件**给出假绿 —— 机制层本应「全平台共用 ⇒ 不得并行」,却判成可开工。等于门禁在最需要拦的地方放行。
|
||
**根因**:结论块**只 gate `unknown`(【D】)**,`mechanism`(【E】)只打印提示、**不参与退出码**。
|
||
**修复**:结论块改为 `rc=1` 当 `【D】>0` **或** `【E】>0`;【E】命中时输出「不得与其他会话并行 ⇒ 仅当确认无其他会话在跑才允许独占开工」。
|
||
**回归**:A 纯可锁定 `rc=0` ✅|B 机制层 `rc=1` ✅|C 未归类 `rc=1` ✅(三场景全通)。
|
||
**同步点**(2 处,原只写「【D】非空 ⇒ 拒开工」):`CODEBUDDY.md §6` 门禁段 + `交接单/README.md` 第 13 条。
|
||
|
||
**⚠️ 教训(写进流程)**:**`--release-exec` 必须放在所有文件改完之后** —— 提前释放域锁后,`Edit preflight-lock.sh` 被 hook 以「没有持任何锁」**硬拦**,只能重新抢锁再改。这是 hook 按设计工作(fail-closed),不是故障。⇒ 收尾顺序:**改完 → 回归 → 才放锁**。
|
||
|
||
### 🔴🔴 收尾验收连环抓出两个「假绿级」缺陷(本平台必现)
|
||
|
||
**缺陷 1:非 ASCII 会话名全部塌成同一个锁目录(最严重)**
|
||
- **现象**:`tr -c 'A-Za-z0-9._-' '_'` 把**每个非 ASCII 字符**压成 `_` ⇒ 「域锁-甲」「域锁-乙」「域锁-丙」全映射成 `______-___` ⇒ **同一锁目录**。
|
||
- **危害**:本平台**会话名全是中文** ⇒ 必现。两会话共用一把锁 ⇒ ① 后到者走"合并"分支(以为是自己)② 域声明被并进同一份 `DOMAINS` ③ hook 侧 `_name_owner_of_id` 也难区分 ⇒ **域隔离整体失效**。
|
||
- **修复**:ASCII 安全名直接留用(可读+兼容旧目录);含非 ASCII ⇒ 附 `cksum` 摘要 ⇒ `______-___-1601335095` / `-3564051901`。
|
||
|
||
**缺陷 2:trap 单槽位覆盖 ⇒ `.gate` 泄漏(我自己加固时引入)**
|
||
- **现象**:我给临时文件加了一个 `trap`,但**下面原来还有一个 `trap _gate_guard`** ⇒ bash 的 trap 是**单一槽位**,后者**覆盖**前者 ⇒ 必然漏一个。
|
||
- **实证**:`--claim-exec` 全部返回 `rc=1`「临界区 .gate 被长时间占用」——`.gate` 孤儿残留。
|
||
- **修复**:改成**一个** `_cleanup_guard` 同时清两样(临时文件 + `.gate`),全流程只注册一次。
|
||
- **⚠️ 附带修掉一个更危险的隐患**:`.gate` 可能是**别人的**(我们在 while 里等别人的临界区)⇒ 无脑 `rmdir` 会**删掉别人的互斥** ⇒ 加 `_GATE_MINE` 标志,只有"确实由本次调用创建"才允许清理(守 R9 精神)。
|
||
|
||
**回归**(中文会话名,全绿):
|
||
- 门禁:可锁定 `rc=0` ✅|机制层 `rc=1` ✅|未归类 `rc=1` ✅
|
||
- 域锁:甲占 `src/im` `rc=0` ✅|乙占 `src/net` 并行 `rc=0` ✅(**关键:中文名不再串目录**)|丙抢已占 `rc=1` ✅|甲追加合并 `rc=0` ✅(域数=2)
|
||
- hook 四场景:我改我域 放行 ✅|我改他域 拦截 ✅|他改我域 拦截+精确冲突域 `src/im` ✅|他改他域 放行 ✅
|
||
- 泄漏:失败路径/SIGTERM/SIGPIPE/成功路径 **四种全为 0 残留** ✅
|
||
|
||
**⚠️ hook 验收的判据前提(重要,⛔ 别踩)**:`_is_mine()` 要求**会话名与 session_id 同时吻合**。若测试时**不设** `DSH_SESSION_NAME`,且多把锁写着**同一个** `CODEBUDDY_SESSION_ID`(同进程批量抢锁必然如此)⇒ `my_name` 为空 ⇒ 退到「只看 id」分支 ⇒ **把别人的锁认成自己的** ⇒ 假绿放行。⇒ **验收 hook 必须带 `DSH_SESSION_NAME=<会话名>`**,否则测的是错的东西。
|
||
|
||
### ✅ 本单最终收口(域锁落地 + 收尾四缺陷全修)
|
||
|
||
| 项 | 结果 |
|
||
|---|---|
|
||
| 四脚本语法 | `handoff-guard.sh` / `preflight-lock.sh` / `lock-guard-hook.py` / `state.py` **全过** |
|
||
| 分层 ratchet | `check-layering.mjs` **✅ 无新增违规** |
|
||
| 开工门禁 | 可锁定 `rc=0` ✅|机制层 `rc=1` ✅|未归类 `rc=1` ✅ |
|
||
| 域锁隔离(中文名) | 甲 `src/im` `rc=0` ✅|乙 `src/net` 并行 `rc=0` ✅|丙抢已占 `rc=1` ✅|锁目录 **2 个互不串** ✅ |
|
||
| hook 四场景 | 我改我域放行 ✅|我改他域拦 ✅|他改我域拦+报冲突域 ✅|他改他域放行 ✅ |
|
||
| 泄漏(四路径) | 成功 / 冲突拒绝 / **SIGTERM×5** / **SIGKILL** → **全 0 残留** ✅ |
|
||
| 技能三处 md5 | 本机 = 文档库 = 服务器 **`5a6844ec…`** ✅ |
|
||
| 技能新增章节 | `dsh-auto-handoff-chain §3.5` 域锁四个「假绿」坑 + 验收前置条件 + 收尾顺序铁律 |
|
||
| 登记 | `README.md` / `INDEX.md` 已跟改并推服务器(md5 三处一致) |
|
||
| `MEMORY.md` | **7,796 / 7,800** ✅ 回上限内 |
|
||
| 域锁 | **已释放,锁总览空闲** ✅ |
|
||
|
||
**本单共修 4 个缺陷**(全部是「假绿」性质 —— 该拦的没拦):
|
||
① 【E】机制层未参与退出码 ⇒ 门禁对自己的目标文件假绿放行
|
||
② 非 ASCII 会话名塌成同一锁目录 ⇒ 中文会话名并行时域隔离失效
|
||
③ `trap` 单槽位互相覆盖 ⇒ `.gate` / 临时文件泄漏
|
||
④ `.gate` 清理未区分归属 ⇒ 可能删掉**别人的**互斥
|
||
|
||
## 14:0x · 回复 carbon 线对接文档:两处阻塞判定 + 最新接入方式(⛔ 本轮零代码改动)
|
||
|
||
**用户原话**:「插件接入和数据接入方案有更新,确认最新接入方式后,回复到对接文档中」
|
||
|
||
### 做了什么
|
||
|
||
把平台侧回复**追加**到 `E:\ProgramData\AI技能\dsh-plugin-carbon\对接文档_carbon插件-平台侧两处阻塞_20260922.md`
|
||
(原文 §0–§6 共 186 行**完整保留**,新增 `# 【平台侧回复】` §A–§G;27643 B / md5 `814f337e619ab0ee0e949b39b15763b2` / U+FFFD=0)。
|
||
|
||
### 🔴 本轮关键发现:接续入口已跑到第 9 棒(我上轮摘要里没有的)
|
||
|
||
读 `接续入口_插件投放与分库线_20260922.md` 才看到 —— **原计划里"要等拍板"的几项全都已落地**:
|
||
共享只读包库(S1/S2 上线)· 建库口接线(B 档 `ALTER ROLE dshs CREATEDB`)· 跨机装配(`plugin-assembly.ts` + worker `/plugins/apply`)· S5-a 兼容矩阵 · 迁移前 `pg_dump --schema-only` 备份 · S6 三段式 UI · 部署 47/106 + 真机 E2E 全绿。
|
||
**⇒ 教训:接手前必须读入口最新行,⛔ 别拿摘要里的旧状态当既成事实。**
|
||
|
||
### 判定(写进了回复)
|
||
|
||
- **事项一(用户自助开通被权限墙挡住)⇒ 在本平台不成立**。真缺陷 = **开源导出物 `dsh_ai1net/install.sh:275`** 的 `chmod 700`;本平台 `bootstrap-worker.sh:139-148` 幂等把 `users/` 设 **711** ⇒ 归**开源导出线**,本线不动。
|
||
- **事项二(handoff 停写)⇒ 属实,但注释前提②「handoff.json 无人消费」写错**(消费者 = `orchestrator.ts:65` 的 `WATCHDOG_TASK`;平台自己在 `restartProbe` 也用,只是写空命令)。
|
||
- 🔴 **两条都不必再等**:平台已改成「**admin 侧 root 物化依赖树 → 共享只读包库;用户侧只写依赖引用**」(`materializeShared()` + `plugin-assembly.ts:407` `file:${target}`)⇒ 事项一的失败路径(降权 uid 穿越 users/ 时 EACCES)**已不可达**;装配走 worker `/plugins/apply`、**刻意绕开 handoff** ⇒ 事项二不再是阻塞。**carbon 原文提的「乙案」正是平台已采纳的方向。**
|
||
|
||
### 路径口径(回应 carbon §5 的 ⚠️)
|
||
|
||
**两套命名 = 两个不同部署物,不是"生产 vs testlocal"**:源仓/本平台 = `dshs` / `DSHS_*` / `/opt/dshs` / `/etc/dshs.env`;开源导出物 = `dsh_ai1net` / `DSH_AI1NET_*` / `/opt/dsh_ai1net` / `/etc/dsh_ai1net.env`。**权威 = 前者**。
|
||
|
||
### 本轮另核实的(供后续引用)
|
||
|
||
- 装配协议**仍是 `file:`** —— 用户 09-23 已拍板改用 `link:`,但**尚未落地**(落地件列在入口 §5「已拍板待落地」4 处)。回复里已按"即将变更"告知 carbon。
|
||
- 可见面开关 `DSHS_SHARED_CATALOG_PUBLIC` **默认 false**(`config.ts:687`),`/api/plugins/shared/catalog` 关着返 **404**。
|
||
- 数据面权威 = `DB-03-插件数据面规范.md`(含 §三·补 谁建库 / §四·补 兼容矩阵 / §八·补 端侧边界)。
|