Files
dstalk/agents/POSTMORTEM.md
XiuChengWu 28ae90a6cc
Some checks failed
CI / Determine matrix (push) Has been cancelled
CI / ${{ matrix.os }} / ${{ matrix.build_type }} (push) Has been cancelled
CI / Sanitizer (ASan+UBSan) / ubuntu-24.04 (push) Has been cancelled
CI / Coverage (gcovr) / ubuntu-24.04 (push) Has been cancelled
W23: close mailroom metadata and network validation tests
- Refresh agents STATUS to W22.6 and exclude mailroom from metadata scans
- Add mailroom dispatch checklist and defensive rules
- Register F-23.D-1 and tag network input validation defense-in-depth
- Update network plugin tests for header length limits
- Fix LSP test metadata and remove orphan anthropic_internal.hpp

Verification:
- cmake --build build --config Release: 0 error, 0 warning
- ctest --test-dir build --output-on-failure: 10/10 passed
- ctest --test-dir build -R dstalk_smoke_test --output-on-failure: passed
- python scripts/check_agents_metadata.py --strict: passed

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:56:45 +08:00

175 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# dstalk 项目踩坑记录 (Postmortem)
> **性质**: 历史记录,非设计文档。每个条目基于已发生的事故。
> **受众**: 全员。新员工/新会话第一份应读材料。
> **更新规则**: 每次新事故发生后,由 QA 追加一条记录并更新汇总表。
---
## PM-001: clang-cl 增量构建 stale .obj 文件
- **首次发现**: Wave 5 / CEO 验收阶段 / `plugins/lsp/src/lsp_plugin.cpp``plugins/tools/src/tools_plugin.cpp`
- **症状**:
```
# 编译通过,但运行行为与源码不一致;报错行号指向磁盘上已不存在的旧代码
# clang-cl 报告的 warning/error 行内容与当前源文件内容不符
```
- **根因**: clang-cl 增量构建的时间戳/依赖追踪偶尔遗漏源文件的磁盘修改,重复使用过期的 `.obj` 缓存。并非 CMake 依赖图错误——`cmake --build` 认为目标已是最新,跳过重编译。
- **影响范围**: 任何使用 clang-cl 增量编译的 `.cpp` 文件lsp_plugin 和 tools_plugin 至少各中招一次。
- **修复方法**:
```bash
rm -f build/**/<file>.cpp.obj # 删除可疑 .obj 强制重编译
cmake --build build --config Release
```
彻底保险:`cmake --build build --config Release --clean-first`(但耗时)。
- **防御性规则**(同步到 WORKFLOW.md §6:
- **R-STALE-OBJ**: CEO 验收编译前,若运行结果与源码不符,先怀疑 stale obj`rm -f build/**/<suspect>.cpp.obj` 重试。
- **检测方法**:
- 对比 `.cpp` 的 mtime 与 `.cpp.obj` 的 mtimeobj 更旧但 `ninja -n` 显示 "no work to do" 时判定 stale。
- CI 中关键构建PR merge / release使用 `--clean-first`。
---
## PM-002: Boost.JSON header-only 链接错误
- **首次发现**: Wave 3 (胡桐 W3) / Wave 7 (王测 W7) / 5 个插件翻译单元
- `plugins/deepseek/src/deepseek_plugin.cpp`
- `plugins/anthropic/src/anthropic_plugin.cpp`
- `plugins/session/src/session_plugin.cpp`
- `plugins/lsp/src/lsp_plugin.cpp`
- `plugins/tools/src/tools_plugin.cpp`
- **症状**:
```
# MSVC linker (Release /MD):
error LNK2001: unresolved external symbol "class boost::json::value ..."
error LNK2001: unresolved external symbol "class boost::json::object ..."
# 多个 boost::json 符号未解析
```
- **根因**: Boost 1.86 废弃了 `BOOST_JSON_HEADER_ONLY` 宏——即使定义了该宏,编译器也忽略它,导致 header-only 模式不生效。缺失的 out-of-line 实现(`boost::json::value`、`boost::json::object` 等)在链接时找不到符号。
- **影响范围**: 任何 `#include <boost/json.hpp>` 且未同时 include `<boost/json/src.hpp>` 的翻译单元。
- **修复方法**:
```cpp
#include <boost/json.hpp>
#include <boost/json/src.hpp> // 必须!提供 out-of-line 实现
```
每个使用 `<boost/json.hpp>` 的 `.cpp` 文件中,**恰好一次** include `<boost/json/src.hpp>`。
- **防御性规则**:
- **R-BOOST-JSON-SRC**: 任何插件 `#include <boost/json.hpp>` 时必须同时 `#include <boost/json/src.hpp>`;新增插件 CI 检查是否有缺失。
- **检测方法**:
- `grep -rl '<boost/json.hpp>' plugins/ | xargs grep -L '<boost/json/src.hpp>'` — 有前者无后者的文件即缺陷。
- CI Release 构建必须链接成功(/MD 下 Boost.JSON 符号靠 src.hpp 提供,不链接 boost_json 库)。
---
## PM-003: 跨 DLL 堆释放(/MT 必崩,/MD 侥幸)
- **首次发现**: Wave 2.1 / 陈风 W2.1 (安全审计触发 + B3 评审)
- `plugins/file_io/src/file_io_plugin.cpp` — 用 `::malloc` 分配,调用方用 `std::free` 释放
- `plugins/tools/src/tools_plugin.cpp:58` — `std::free(host 分配的指针)`
- `plugins/session/src/session_plugin.cpp:166` — 同上模式
- `tests/smoke_test.cpp` — 三处 `std::free` 应改为 `dstalk_free`
- **症状**:
```
# /MD (动态 CRT): 运行正常(共享 ucrtbase.dll不同 DLL 的 malloc/free 落在同一个堆——侥幸)
# /MT (静态 CRT): 立即崩溃 / heap corruption
HEAP CORRUPTION DETECTED: after Normal block (#XXX) at 0x...
# 或静默内存损坏,延迟在无关位置崩溃
```
- **根因**: Windows 每个 DLL 拥有独立 CRT 堆(/MT或共享同一个 CRT DLL 堆(/MD。插件在自身 CRT 堆上 `malloc` 得到的指针,传给 host 后 host 调用 `free` —— host 的 CRT 尝试释放一个不属于它的堆上的地址 → 未定义行为。当前项目使用 `/MD` 故不立即触发,但这是一个精度定时炸弹(任何人改 CRT 链接方式就会引爆)。
- **影响范围**: 所有插件与 host 之间传递动态分配内存的场景。涵盖 `malloc`/`free`/`strdup`/`new`/`delete`。
- **修复方法**:
```c
// 错误(跨 DLL 堆):
char* buf = (char*)::malloc(256);
// ...
std::free(buf); // 释放者与分配者不同 CRT 堆
// 正确(统一堆):
char* buf = (char*)g_host->alloc(256);
// ...
g_host->free(buf); // 通过 host_api 函数指针回到同一个堆
```
详见 `docs/reference/plugin-abi.md` §2-§3。
- **防御性规则**:
- **R-NO-RAW-ALLOC**: 插件代码严禁直接调用 `malloc`/`free`/`strdup`/`new`/`delete` 处理跨 DLL 边界数据;必须使用 `host->alloc`/`host->free`/`host->strdup`。
- **R-TEST-MT**: CI 至少一个配置使用 `/MT` 构建并跑 smoke test确保跨堆问题在任何 CRT 模式下暴露。
- **检测方法**:
- `grep -nP '\b(std::)?(malloc|free|strdup|realloc)\b' plugins/**/*.cpp` — 任何命中需审计是否是私有堆操作(不跨 DLL 边界)。
- `grep -nP '\bnew\b.*\bdelete\b'` 同审计。
- ASan (`-fsanitize=address`) 在 Windows 下对跨堆释放敏感,可捕获 heap-use-after-free / alloc-dealloc-mismatch。
---
## PM-004: plugin_loader 单插件失败导致全部初始化终止
- **首次发现**: Wave 2.1 / 陈风 W2.1 (side-effect 发现) → Wave 9.8 / 黄岭 W9.8 (正式修复)
- `dstalk-core/src/plugin_loader.cpp` — `initialize_all()` 函数
- **症状**:
```
# 假设加载 A→B→C 三个插件B 的 on_init 返回 -1:
# 旧行为:
[ERROR] Plugin 'B' init failed (code -1)
# A 已初始化成功,但 B 失败后 initialize_all 立即 return -1
# C 完全未被尝试初始化——即使 C 不依赖 B
# 症状: "明明只坏了一个插件,为什么其他插件也不工作?"
```
- **根因**: 旧版 `initialize_all()` 使用 fail-fast 模式 —— 任何一个 `on_init` 返回非零,整个函数立即返回 `-1`,剩余插件全部跳过。没有"部分成功"的概念。
- **影响范围**: 所有依赖于 `initialize_all()` 正常完成的调用方CLI、GUI、测试。一个低优先级插件失败会让整个应用退化。
- **修复方法** (黄岭 W9.8):
- 将 fail-fast 改为 fail-continue单个 `on_init` 失败 → `fprintf(stderr, ...)` + 标记 `failed_names` + `failed_count++` → `continue` 下一个。
- 依赖已失败插件的下游插件自动跳过并 `[WARN]`。
- 返回值语义: `0` = 全部成功;`>0` = 失败的插件数;`<0` = 严重错误(循环依赖 / host_api null
- 代码位置: `plugin_loader.cpp:202-256`。
- **防御性规则**:
- **R-LOADER-CONTINUE**: 插件加载/初始化**永不 fail-fast**;单点故障不得级联阻断无关插件。
- **R-LOADER-RETVAL**: `initialize_all` 返回值 >0 表示部分成功(调用方必须区分 "完全失败" 和 "部分退化")。
- **检测方法**:
- 单元测试:加载 3 个插件,中间一个 mock 为 "always init fail",验证第 1、3 个正常初始化,且返回值为 1。
- smoke test 验证所有 9 个插件加载(当前预期 9/9 通过)。
---
## PM-005: git push --force 未在子代理 prompt 中禁止
- **首次发现**: Wave 1+ / CEO 规则层面 / 无特定代码文件
- **症状**:
```
# 子代理在独立会话中执行 git 操作,可能使用 --force 推送
# 远端历史被改写,其他协作者的本地分支与远端不一致
# 没有 git hook 阻止,也没有 prompt 模板明确禁止
```
- **根因**: CEO 规则明确禁止 `push --force`,但该规则只在主会话中有效。子代理的 prompt 模板(`WORKFLOW.md` §9没有包含此禁忌子代理不知道这条规则。
- **影响范围**: 所有通过 `Agent` 工具派出的子代理。任何子代理执行 `git push` 时都可能误用 `--force`。
- **修复方法**:
1. 子代理 prompt 模板中增加:"禁止 git push --force / --force-with-lease禁止 git reset --hard origin/*"
2. 可选:服务端 git hookGitHub branch protection禁止 force push 到 master。
- **防御性规则**:
- **R-NO-FORCE-PUSH**: 每个子代理任务 prompt 必须包含 "禁止 git push --force / --force-with-lease";如需改写历史仅限本地 `git rebase -i`(未推送的提交)。
- **检测方法**:
- GitHub branch protection rule: master 分支禁止 force push。
- 每次 CEO 验收后检查 `git reflog` 是否有 force push 痕迹。
---
## 防御性规则汇总表 (§6)
| 规则 ID | 事故 | 规则一句话 |
|---------|------|-----------|
| R-STALE-OBJ | PM-001 | 运行结果与源码不符时先怀疑 stale obj`rm -f build/**/<file>.cpp.obj` 重编 |
| R-BOOST-JSON-SRC | PM-002 | `#include <boost/json.hpp>` 的 .cpp 必须同时 `#include <boost/json/src.hpp>` |
| R-NO-RAW-ALLOC | PM-003 | 插件代码严禁 `malloc`/`free`/`strdup`/`new`/`delete` 跨 DLL 边界;统一用 `host->alloc/free/strdup` |
| R-TEST-MT | PM-003 | CI 至少一个配置用 `/MT` 构建并跑 smoke test |
| R-LOADER-CONTINUE | PM-004 | 插件加载/初始化永不 fail-fast单点故障不级联阻断 |
| R-LOADER-RETVAL | PM-004 | `initialize_all` 返回值 >0 = 部分成功,调用方必须区分 |
| R-NO-FORCE-PUSH | PM-005 | 子代理 prompt 必须禁止 `git push --force` / `--force-with-lease` |
| R-MAIL-SCOPE | Mailroom v1 | 子代理不得读写超出自身 inbox 之外的他人邮箱内容,仅可投递新邮件 |
| R-MAIL-NO-DELETE | Mailroom v1 | 接收者不可物理删除邮件,只能移到 `agents/mailroom/archive/W<n>/`;删除仅限 CEO |
---
## 变更历史
| 日期 | 版本 | 变更 |
|------|------|------|
| 2026-05-27 | 1.0 | W10.4 初始化。记录 PM-001 至 PM-005共 5 条事故 7 条防御性规则。 |