docs/HANDOFF-2026-08-04-B.md
Rama master · réplica de lectura del repositorio original.
# 交接说明 — 2026-08-04 (B)
> **⚠️ 这不是最新的一份。** 本文写的是 v3.9.13,当前一份是
> [HANDOFF-2026-10-15.md](HANDOFF-2026-10-15.md)。**它的状态表与"仍未做"清单请一律以最新那份为准** ——
> 交接是 append-only 的,旧的不删也不改结论。本文仍值得读的是:**§5** —— 这几份里最值得读的一节。
>
> 另:`ls docs/` 的排序**不等于**时间顺序(`-B.md` 的 `-` 排在 `.md` 的 `.` 之前,而
> `HANDOFF-2026-08-05.md` 的文件名日期是错的)。顺序只看索引 [README.md](README.md)。
**上一份**:[HANDOFF-2026-08-05.md](HANDOFF-2026-08-05.md)。那份文件名的日期是错的(工作
发生在 08-04),内容仍然有效,它的 §8 记的就是本份 §1 的前半。方法论沿用它的 §6 与
[AUDIT-LEDGER.md](AUDIT-LEDGER.md) 第九、十轮。
---
## 0. 当前状态
```
版本 / tag 3.9.13 / v3.9.13(tag 落在 master 上)
master == origin/master,已推
门禁 3401 pass / 0 fail(260 个文件,exit 0)
突变 四个 spec 共 35 条,全部行为符合预期
secret-scan EXIT=0 ← 但它**跳过 test/** 且只读被跟踪文件,见 §4
工作树 干净,只剩 master;远端也只剩 master
```
### 门禁数字权威表
| ref | pass | 新增 |
|---|---|---|
| `v3.9.11` | 3302 | |
| `v3.9.12` | 3342 | +40 |
| `v3.9.13` | **3401** | +59 |
数字请只引这张表。测量口径是 `npm run test:release`(逐文件进程隔离);全量 `npm test`
会因 `--test-force-exit` 输出竞态波动。
---
## 1. v3.9.13 发了什么
### 真实缺陷:caller 分片反噬 issue #37
`getApiKey` 的候选排序把 in-flight 最少的排前面,好让并发突发散开(#37)。末尾的 caller
分片本意是"在同等好的账号之间让每个 caller 确定性地偏好自己那一格",但门槛只判
`candidates[0]` 与 `[1]` 打平,却按 `hash % candidates.length` 在**整个数组**上取下标 ——
于是能提拔一个与谁都不平的账号,**包括在场最差的那个**。
并发突发下正是如此:正在服务的账号按 in-flight 排到最后,队首两个空闲账号打平、门槛放行,
交换把最忙的拉回 `[0]`。实测 pool=8:某 callerKey 连续 8 次选中同一账号,七个同伴 in-flight
全是 0。**一直看不见,因为结果完全由 `sha256(callerKey) % pool` 决定**,而钉这条行为的测试
用的 callerKey 恰好 hash 到最好的那一档。8 个真实形态里只有 2 个真的散开全池。
修法:置换限制在真正与 `candidates[0]` 打平的前缀内。**判据必须镜像排序的每一项** —— 第一版
漏了 `recentTroubleScore`(排序第二优先键),既能提拔被降级的账号,又因该项优先级高而让扫描
提前停止、**分片不足**。
### 新开关 `WINDSURFAPI_STRICT_USAGE_TOTAL`(默认关)
恢复 OpenAI 的 `total_tokens == prompt + completion`。默认关,因为两种失效不对称:恒等式被破
对绝大多数消费者只是观感;而把 cache_write 从 total 拿掉会**少报真实开销**。
**难点不是加开关,是成本统计读的是同一个对象。** 天真地加会让运维一旦选择合规、自己的账单
当场少报。所以拆开两个职责:`total_tokens` 只管线上契约,成本统计取
`max(total, prompt + completion + cache_creation_input_tokens)`。三个 usage builder 全覆盖。
### 工具化 + 两条积压的结论
`npm run mutate`(见 §3)、可选 git hooks、以及两条 sticky 积压的答案:
`clearCallerBindings` **没有正确的触发点**(断连是主动错误的触发点);RPM 满即清绑定的
**显然修法是净负面**(保留绑定会散射,4/3 个冷账号 vs 清绑定的 2)已 revert 并留测试钉住。
---
## 2. 接手第一步
```bash
cd /Users/dwgx/Documents/Project/WindsurfAPI
echo alive # 工具层活着吗(见 §5)
git status --short # 期望:干净
git log --oneline -1 # 期望:v3.9.13 那条或更新
git rev-parse master origin/master # 期望:相同
npm run test:release # 期望:3401 / 0
for s in test/mutations/*.json; do npm run mutate -- "$s"; done # 期望:四个全绿
```
**可选但建议**:`git config core.hooksPath .githooks`,拦住"直接在 master 上写 commit"。
本轮它拦过我三次,每次都是对的。范围见 §3。
---
## 3. 本轮新增的两个工具
### `npm run mutate -- test/mutations/<spec>.json`
突变验证从手搓循环变成脚本。三条**拒绝启动**的前置检查,每条对应一次真实事故:
| 拒绝条件 | 事故 |
|---|---|
| 工作树脏 | 循环自己的 `git checkout HEAD --` 会连未提交的修复一起还原,之后每次突变测的是"修复缺失"而不是"突变生效" —— 而那个失败**看起来正是突变被抓到**,所以读起来像成功 |
| baseline 不绿 / 条数不符 | 假 SURVIVED。本轮它两次抓到我自己的陈旧 spec |
| anchor 匹配次数 ≠ 1 | 匹配不上的 `replace` 是静默 no-op |
另外它会在突变后核对**测试条数**:条数变了**且什么都没失败**才中止 —— 那个 `&&` 是承重的,
少了它会在突变被真正抓到时误杀整轮(本轮真踩过)。
`expectCaught: false` 是**一等公民**,不是待修标记。"这条守不住"本身是结论。
### `.githooks/`(opt-in)
`pre-commit` 拒绝在 master 上写 commit;`post-commit` 对 `cherry-pick` / `revert` 事后警告。
**作用范围有九条断言钉住**(`githooks-scope.test.js`),因为那张表在同一轮里与实际行为偏离过
两次:
```
git commit / --amend 拦
git merge --ff-only 放行(不产生 commit,发版流程不受影响)
cherry-pick / revert pre-commit 拦不住;由 post-commit 事后警告
冲突的 merge / rebase 拦,且给的是那个状态下真能执行的命令
未 opt-in 完全不生效
```
---
## 4. 仍未做的
### 4.1 需你拍板
- **#240(warelik,66 天生产数据)**:`streamChatWithEmptyRetry` 里 rescue 迭代会推进共享的
`attempt`,**单向**饿死 empty-retry 预算。报告者明确把决定权留给维护者。这是本轮唯一没被
碰过的外部实质报告
- **Cascade 流式路径不记 per-account 消费**:既有缺口,已加测试钉住。补它要往热流式路径加
一个 `recordAccountSpend` 调用点
### 4.2 卡在真实上游
- **billing tag 号校准**(#239 最后一环):需付费号跑
`DEVIN_CONNECT_DEBUG_META=1 npm run calibrate:devin`
- **#236 GLM 工具调用停滞**:判据太模糊,需真实复现
### 4.3 已知的记账口径问题
**`secret-scan` 跳过 `test/`** 且只读被跟踪文件(`IGNORED_PREFIXES` 含 `'test/'`)。本轮约
2333 行新增里约 1100 行从未被它扫过。"secret-scan 干净"这句话要带上这个范围说。本轮已手工
扫过 `test/` 增量:只有自己写的 `cafebabe`/`deadbeef` 占位十六进制,无真实凭据。
**`RELEASE_NOTES_3.9.12.md` 里有一处 5.6x 的错引**(把 tag4 的 token 计数当成成本比例的依据;
真正的依据是同处的 "hit cost measured at 17.8% of miss")。**刻意不改** —— release notes 是
append-only 历史,且那份已作为 GitHub Release body 发布。记在这里免得被当成新发现。
---
## 5. 本轮学到的,按"最容易再犯"排序
台账第九、十轮有完整版。这里只列会直接害到下一个人的几条。
### 5.1 修复本身要过一遍复核
对抗复核六个 lens 攻本轮全部改动,**没有一条重大发现指向老代码 —— 全是这一轮新造的**。而每
一步都跑过突变验证。随后把裁决改窄成"审那六个修复本身",六条里**三条正中**。
一批修复是这一轮**唯一没人看过**的代码,而它们是在已经发现问题的紧张状态下写的。
### 5.2 一个守卫按一次复现写出来,就只对那一次复现正确
最清楚的例子:我按"截断"这一种复现写了"测试条数必须一致",然后无条件套用,于是在"真的被抓到
但条数也变了"的场景里把整轮杀掉。
**动作**:写下一个守卫后,问"**除了我复现的那一种,还有哪些情况会命中这个条件**",逐一给出
处置。答不出说明守卫没写完。
### 5.3 没看到信号,先问探针坏没坏
我给 post-commit 加了无条件 `echo`、跑 revert、没看到输出,就断言"git 对 revert 根本不跑
post-commit",并写进 commit message 和三处注释。**那次 revert 压根没产生 commit**,所以没有
钩子有机会运行。
台账第五轮就记过"复现失败要归因"。我记下了那条规则,然后在同一轮里对着一个坏探针违反了它。
### 5.4 引用必须支持结论,不只是"本身为真"
"cache_write 约为读的 5.6 倍"我引用了六处,每处都拿 tag4 的 token 计数当依据 —— 那组数只确定
了哪个 tag 承载 cache-write,**对成本一个字都没说**。比例本身站得住(17.8% of miss,1/0.178),
但我引的不是它。
**两句话各自都真、而推不出彼此,引用仍然是错的 —— 而且比不给引用更危险,因为它看起来可核。**
### 5.5 并发 agent 会吃掉未提交的改动
复核 agent 的 `git checkout HEAD -- src/x.js` 清理会把你未提交的编辑一起还原。本轮真发生了:
一个 commit 的 message 声称改了 `auth.js` 而 diff 里没有。
**动作**:让并发 agent 在 **worktree** 里跑(`isolation: 'worktree'`),或者改完立刻 commit;
commit 后核一次 `git show --stat HEAD -- <file>`。同理:**持有未提交改动时不要跑
`git reset --hard`** —— 我自己的测试清理也吃掉过一次。
### 5.6 N 项合取的谓词,spec 要逐项各有一条突变
`tiedWithFirst` 五项判据,spec 一开始只隔离了两项。逐个查下去四项都承重,其中 quota 那项
**没有任何 fixture 能让它说话**(种子账号没 `credits`,`quotaScore` 恒返回 100)。
**一项在所有 fixture 里恒等,不等于它不承重 —— 那只说明 fixture 不够。**
### 5.7 长任务与 workflow
- 长命令必须真正后台跑;本会话前半段工具层反复返回空输出,全在多分钟阻塞命令之后
- **给 workflow 的 judge 传结论而不是整份报告**。第一次把六份完整报告序列化(~25k tokens)
塞进 prompt,judge 6 次尝试全部停滞;裁掉散文后 resume 成功(lens 结果有缓存,只重跑 judge)