Ctrl K
ComunidadGuías…

docs/HANDOFF-2026-08-04.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 交接说明 — 2026-08-04

> **⚠️ 这不是最新的一份。** 本文写的是 v3.9.9–v3.9.11,当前一份是
> [HANDOFF-2026-10-15.md](HANDOFF-2026-10-15.md)。**它的状态表与"仍未做"清单请一律以最新那份为准** ——
> 交接是 append-only 的,旧的不删也不改结论。本文仍值得读的是:§3(它更正了之前几份里的四处声明)。
>
> 另:`ls docs/` 的排序**不等于**时间顺序(`-B.md` 的 `-` 排在 `.md` 的 `.` 之前,而
> `HANDOFF-2026-08-05.md` 的文件名日期是错的)。顺序只看索引 [README.md](README.md)。


**上一份**:[HANDOFF-2026-08-03-B.md](HANDOFF-2026-08-03-B.md)。那份的 §3 清单在本轮被
逐条验证过,结论与它记的**不完全一致** —— 见 §3。方法论(上一份 §4、以及
[HANDOFF-2026-08-03.md](HANDOFF-2026-08-03.md) §5)仍然有效,本文只补新增的。

---

## 0. 当前状态

```
master        3cc4704  == origin/master
版本          3.9.11
最新 tag      v3.9.11(产物 4 个,release run 全绿)
工作树        干净
门禁          3302 pass / 0 fail
```

本轮发了三个版本:**v3.9.9 → v3.9.10 → v3.9.11**,14 个 commit。

### 门禁数字权威表

散落在文档里的数字曾经互相矛盾(中间分支读数被当成发布基线)。以下在 tag 上实测,
**引用请用这张表**:

| ref | pass | 备注 |
|---|---|---|
| `v3.9.6` | 3119 | |
| `v3.9.7` | — | **从未打 tag**,折进 v3.9.8 |
| `v3.9.8` | 3246 | |
| `v3.9.9` | 3264 | |
| `v3.9.10` | 3285 | |
| `v3.9.11` | 3302 | = master |

测量脚本**必须在 ref 不存在时中止**:我第一次测时 `git checkout v3.9.7` 因无此 tag 而
失败、脚本没停,把 master 的数字报成了 v3.9.7 的,靠"两行数字一模一样"才发现。

---

## 1. 本轮做完了什么

### v3.9.10 — 两条真实账号跑出来的缺陷

**订阅取消对账号池不可见。** 一个订阅已取消的账号会静默吞掉所有 chat 请求,而 Dashboard
显示 `active` / `错误 0` / 宣传 163 个模型。实测三个 RPC 三种结果:

```
目录 RPC        200 → 167 个 selector   (不检查 entitlement)
GetUserStatus   403 → "Your subscription has been canceled"
GetChatMessage  CAPACITY ×2 → UPSTREAM_INTERNAL "an internal error occurred"
```

chat 的失败被归类 `UPSTREAM_INTERNAL` 且**刻意不记账号**(一次上游抖动不该打下整池,那条
规则是对的),所以唯一知道原因的 RPC 把它写进 `credits.lastError` 后没人读。

处置机制本来就有(`looksLikeBanSignal` 匹配、`reportBanSignal` 要求 30 分钟内两次),缺的
只是一条线。`refreshAllCredits` 15 分钟一轮,真取消会在相邻两轮命中两次;瞬时错误不匹配
正则(`an internal error occurred` / `CAPACITY` / `DEADLINE_EXCEEDED` 全 false)。

**恢复路径**:`PATCH /accounts/:id {resetErrors:true}`(Dashboard 的"重置"按钮)→ `active`。

**降级后谎报模型名,三条协议路由。** `STRICT_MODEL=0` 把未映射模型降级到免费 selector,
但响应把请求的**付费名**报回去。`messages.js` / `gemini.js` 对 `displayModel` 的引用数原本
都是 **0**。

流式的修法:translator 在上游结果之前构造,所以从**第一个 chunk** 学真名(OpenAI 每个
chunk 都带 `model`,`message_start` 是懒发的)。

**一个反直觉的点**:上一份交接 §3.6 写的是"报实际跑的 selector"。**照做会把一个谎言换成
另一个** —— `selector` 在 AssignModel router 跳转之前就被捕获,而 router 名解析为
`mapped:false`,所以实际跑了 `claude-opus-4-8-medium` 的 `adaptive` 请求会被报成免费的
`swe-1-6-slow`。用的是跳转后的 `connectParams.model`。

### v3.9.11 — 守卫与 sticky 积压

**`stickyNoFallback` 永久楔死 caller。** 返回 `null` 却把绑定留在原地,后续每次请求重新
解析同一个死绑定再次拒绝。实测:disable 绑定账号后连续四次全 `null` 且绑定仍在。

修法区分**结构性消失**(清绑定)与**瞬时不可用**(拒绝并保留)。两半都钉了测试 —— 只修
第一半会把 flag 变成 no-op。

**路由错误分类的源码守卫可被绕过。** 判据是"绑定名出现 >1 次",任何第二次提及都能满足。
实测:接线改回读扁平 `err.type` + 留一个被 `console.log` 读到的死绑定 → 守卫 10/10 全绿。
新增行为断言,同一突变下 6 条失败。

### 新增测试文件

```
test/connect-degrade-honesty.test.js        降级后回显名(含 router hop 陷阱)
test/subscription-cancelled-signal.test.js  订阅取消信号的识别与处置
test/route-error-parity-behaviour.test.js   路由错误分类的行为断言
test/sticky-no-fallback-wedge.test.js       楔子的两半
test/sticky-concurrency-tension.test.js     首轮散射 vs 亲和的张力(钉当前选择)
test/models-injection-eviction.test.js      已删除账号的模型驱逐(v3.9.9)
```

`messages.test.js` / `gemini.test.js` 各补了流式与非流式的模型名守卫。

---

## 2. 最重要的一条:并发对抗把 8 条筛成 4 条

本轮用 16 个 agent 并发调查上一份交接的 8 条积压 —— 每条先复现、通过的再过一轮对抗反驳。

**4 条被反驳,其中一条基于伪造的复现数字。**

| 项 | 结论 |
|---|---|
| `strict-model-lie` | ✅ 通过 → 已修(v3.9.10) |
| `route-parity-guard` | ✅ 通过 → 已修(v3.9.11) |
| `sticky-double-flag-wedge` | ✅ 通过 → 已修(v3.9.11),但**锚点无效**,我自己重定位 |
| `sticky-thundering-herd` | ✅ 通过,但我自己复核后判定**不是缺陷**(见 §3) |
| `clear-caller-bindings` | ❌ **抓到伪造的复现数字**,且是已登记旧账 |
| `sticky-rpm-clear` | ❌ 机制真但归因错,严重性远低于所报 |
| `response-store-tenant-quota` | ❌ 复现真实,但那是**有文档有测试的既定策略** |
| `connect-dimension-guard` | ❌ 台账已 publish 这条限制,"不是发现" |

**如果照 8 条全做,会有 4 条是在改不该改的东西。** 对抗验证的价值不在于找缺陷,在于
**拦住不该做的工作**。

---

## 3. 对上一份交接的修正(接手时最要紧)

### 3.1 "两个 flag 全开才楔住" —— 错,一个就够

上一份 §3.10-3 记的是"需两个实验 flag 全开"。实测:

```
无 flag                    → 自愈
仅 stickyNoFallback        → 楔住     ← 一个就够
仅 stickyBindByUserOnly    → 自愈
两个都开                    → 楔住
```

`stickyBindByUserOnly` 与楔子**无关**。暴露面比记录的大 —— 只开一个文档化的 flag 就会
永久硬失败。已修 + 钉测试。

### 3.2 首轮 thundering-herd 不是缺陷,是设计张力

上一份 §3.10-1 把它列为待修。现象复现了(8 次同 caller 取号 → 8 个不同账号、0 sticky
hit),但**散射是明确写在代码里的意图**:

```
// Pick the account with the fewest in-flight requests first (so a burst
// of concurrent calls spreads across accounts ... — see issue #37).
```

把首轮绑到一个账号会违反 #37,并把并发串行到单账号 RPM 上限。而代理**分不出**"一个会话
的 8 个并行工具调用"(想要一个账号)和"8 个独立会话"(想要 8 个账号)。

两侧此前都没记过这个张力。交付改成:两侧互相引用地写下 + 测试钉住当前选择,让未来改动
任何一侧都是自觉的。**不要把它当缺陷去"修"** —— 先读 `sticky-session.js` 的 CONCURRENCY
注释。

### 3.3 `STRICT_MODEL=0` 那条测试并未把缺陷固化成契约

上一份说 `devin-connect-strict-model.test.js` "把这个行为断言成期望行为"。核实后不成立:
那条测试只断言"opt-out 不得 400"(这正是 opt-out 的正当用途),对 `.model` 的断言数为
**0**。所以谎言是没有断言覆盖的,修它不需要跟测试打架。已修。

### 3.4 门禁数字三处错

`3245 / 3195 / 3254` 都是中间分支读数被当成发布基线。权威表见 §0。两份 release notes、
两个已发布的 GitHub Release body、三条 issue 回复都已更正。

---

## 4. 仍未做的

按可行性排序。

### 4.1 可离线做

- **`connect-dimension-guard` 词法切片**:只检查 `finalizeConnectAccount` 那一段源码,把
  冷却写到别处可逃。对抗复核判定为"台账已 publish 的已知限制",不算新发现,但缺口是真的。
  修法参照本轮 `route-error-parity-behaviour.test.js` 的做法 —— 从源码形状改成行为断言
- **RPM 满即清绑定**:上一份 §3.10-2。彻底修需 queue-on-pin。注意与 §3.2 的张力相关
- **`clearCallerBindings` 无生产调用点**:事实成立(已登记三处代码 + 三处文档),但"该由
  哪个事件调用"是设计决定,不是接线问题

### 4.2 卡在真实上游

- **billing tag 号校准**(#239 的最后一环):`credit_cost` / `committed_acu_cost` 只有声明
  顺序推测。默认只有 `cache_read_tokens=5,cache_write_tokens=4` 是 paid-verified。接线已
  完成,有付费号跑一次 `DEVIN_CONNECT_DEBUG_META=1 npm run calibrate:devin` 就能定
- **`rateTable` 真实响应**:`withCatalog: true` 会去取,但从未见过真实响应(`GetUserStatus`
  403)
- **#236 GLM 工具调用停滞**:判据太模糊,需真实复现

### 4.3 产品取舍,需作者拍板

- **`total_tokens` 含 `cache_write`**,因此不等于 `prompt + completion`。复核认为破坏
  OpenAI 契约;但 Cascade 路径(#118)早已刻意如此并写明理由。改它会同时改两条路径并
  推翻既有决定

---

## 5. 本机环境

- **账号池里那个号(`d4ec0148` / `xc xcf`)订阅已取消。** 它能拉目录(167 个 selector)但
  跑不了 chat。留着有价值 —— 它是"订阅取消但目录 RPC 仍可用"这个形态的活样本
- 该号现在会在两轮刷新后被自动标 `banned`(本轮新增的行为)。要用得换个有效订阅的号,
  或点 Dashboard 的"重置"
- 服务默认端口 **3003**,`.env` 里有 `DASHBOARD_PASSWORD` 与 `API_KEY`。Dashboard API 需要
  **两层鉴权**:`Authorization: Bearer <API_KEY>` + `x-dashboard-password`
- Dashboard API 前缀是 **`/dashboard/api`**,不是 `/api`

---

## 6. 本轮新增的方法论

上一份 §4 仍然有效。这里只补它没有的。

### 6.1 长任务必须后台跑 + 短轮询

本会话前半段工具层反复返回**空输出**,全部出现在多分钟阻塞命令之后(`npm run test:release`
单次 15 分钟以上)。改成 `nohup ... &` + 轮询标记文件后再没出现过。

十项机制逐个测过(stdout / stderr / exit code / cd / git / node / python / heredoc)都正常
—— 问题是**用法**,不是工具坏了。

### 6.2 "先提交,再突变" —— 本轮违反了三次

突变循环里的 `git checkout -- <file>` 会把同一文件里**尚未提交的真修复**一起还原。之后的
突变测的是"修复缺失"而不是"突变生效",而那个失败**看起来像突变被抓到**。

三次都是我自己踩的。纪律:修复先 commit,再跑突变。

### 6.3 harness 静默失效的三种新形态

除了上一份记的 zsh 词分裂,本轮又踩到三种:

1. **`import(...?query)` 创建独立模块实例** —— 我拿 sticky 模块的新实例写绑定,而 auth.js
   用它自己的静态导入读 → 统计不涨、绑定读不到。是"守卫的守卫"(misses 必须 >0)暴露的
2. **`setExperimental('key', true)` 是 no-op** —— 它收**补丁对象**。四种 flag 组合的复现
   结果因此完全一样,我差点据此否掉整条真缺陷
3. **测错了路径** —— 我第一版路由测试驱动的是"抛出错误",而真实调用点在流式 translator
   的 `error()` 里处理 error chunk。12 条全红,但红的原因与契约无关

**共同判别动作**:每个复现/突变结果都要能回答"这个数字是怎么产生的"。答不出就是 harness
在骗你。

### 6.4 注释声称承重时,要问"经由哪个调用者才承重"

本轮两次:

- `forgetCloudModelCatalog` 内部的顺序只在**直接调用**时承重,经由 `removeAccount` 时上游
  已经先丢了目录 → 顺序无关。我的注释声称它承重,而突变验证显示零测试失败
- `messages.js` 的 `!messageStarted` 条件**不承重**(`startMessage()` 自己有 early-return,
  且 `this.model` 只被读一次)。承重的等价物在 `gemini.js`,那里每一帧都重读

**动作**:写下"顺序/条件很重要"时,立刻问"经由哪个调用者时它才重要",然后给那条路径
写测试。否则注释就是一句没有守卫的断言。

### 6.5 复现失败要归因,不能当成"缺陷不存在"

第一次复现 MODELS 泄漏得到 `delta +0`,看起来像"没这个缺陷"。真实原因是单条目快照被缩水
确认守卫隔离、`applyCloudModels` 压根没执行。**一次没能复现,和一次证明缺陷不存在,不是
同一件事。**

同一形态:散射断言原本归因于 issue #37,但移除那个比较器后测试仍全绿 —— 成因在候选**过滤**
(RPM 预留让刚服务过的账号对下一次不合格),我没隔离出来。已把断言消息改成钉结果而不是
机制。

---

## 7. issue 状态

5 个 open,**全部是我最后发言,没人在等回复**。

| issue | 状态 |
|---|---|
| #234 | 主体已修(v3.9.8 / v3.9.9 / v3.9.10)。`STRICT_MODEL=0` 那条已随 v3.9.10 闭合 |
| #235 | 费率表接线已随 v3.9.8 发布。剩 tag 校准那部分 |
| #239 | 接线完成,等 ACU tag 号校准(需付费号) |
| #236 | 判据太模糊,等报告者用 OpenCode 直连复现 |
| #208 | 自己开的公告帖 |

**回复风格**:作者本轮明确指出我的 issue 回复太长(每条 80 多行)、像 AI 不严谨。问题在于
把工作笔记(我踩了什么坑、方法论、"这是第 N 次出现某形态")写给了外人。**维护者的回复应该
是结论 + 证据 + 下一步,十几行。** 那些属于台账,不属于 issue。