文档: 补上「为什么只补 inserted」的真正机制 —— 迁移器只补了一半(§18)
§3/§4 一直说「只改一处」,但从没说清为什么顶层 user/message 缺 id 就不用管。 早先给的理由(补了会 seq gap)已被 §17 作废,那句话一度**没有理由**。 真正的机制(可推广,不只是这一个 repair 的解释): - 补 id 的 normalizeLegacyMessage() 的 switch 只有 3 个分支 (user/message、assistant/message、tool/result),**没有 agent/inbox/spliced**; 顶层消息缺 id 迁移器自己会补(legacy-message:<sid>:<seq>)。 - 校验 id 的 messageValue() 对 spliced.inserted[] 严格要求 id+role。 ⇒ 顶层缺 id 能自愈;inserted[] 缺 id 直接拒绝整条会话。不是取舍,是只补了一半。 实测 43 个文件零例外:其中 43 个文件的顶层 user/message 也缺 id(共 270 条), 只补 inserted(故意不碰 user/message)后,产物里 user/message 仍缺 id = 0。 另:id 的约束是 nonEmptyString(无格式校验),从代码层面解释了 §17.1。 给下一位的一句话:判断「该补哪些字段」要同时读校验器与迁移器 —— 校验器管拒绝什么,迁移器管自愈什么,需要手工补的是两者之差。
This commit is contained in:
@ -356,3 +356,50 @@ mail-* v0 需修复的会话: 40
|
||||
3. 真正该记的教训不是"数据有隐患",而是**"校验会改写入参,所以校验与落盘必须分对象"**——
|
||||
它已经分别制造过 §10(dry-run 40 vs apply 3)和本节(假 `seq gap`)两次假结论。
|
||||
4. 仍然成立的独立缺陷:`subagent/descriptor ... version 2`(29 个非邮件会话),与本问题无关。
|
||||
|
||||
## 18. 补上 §3/§4 一直没解释清的那句「为什么只补 `inserted`」
|
||||
|
||||
§3 说「只改一处」,但**从未说清为什么顶层 `user/message` 缺 id 就不用管**。
|
||||
早先给的理由("补了会 seq gap")已被 §17 作废,于是这句话一度**没有理由**。
|
||||
现在补上真正的机制——它是可推广的,不只是这一个 repair 的解释。
|
||||
|
||||
### 18.1 迁移器里有一处**不对称**
|
||||
|
||||
`dsh-session-format-v0-to-v1/lib/index.js` 里两个函数各管一半:
|
||||
|
||||
- **补 id 的**:`normalizeLegacyMessage()` 的 `switch` 只有 **3 个分支** ——
|
||||
`user/message`、`assistant/message`、`tool/result`。
|
||||
**没有 `agent/inbox/spliced` 分支**(全文件 `grep` 确认)。
|
||||
⇒ 顶层消息缺 id,迁移器**自己会补**(`legacy-message:<sid>:<seq>`)。
|
||||
- **校验 id 的**:`messageValue()` 对 `spliced.inserted[]` 里每条消息
|
||||
**严格**要求 `id` + `role`(`nonEmptyString(message["id"], ...)`)。
|
||||
|
||||
⇒ **顶层缺 id 能自愈;`inserted[]` 缺 id 直接拒绝整条会话。**
|
||||
这就是"只需补一处"的真正原因——不是取舍,是迁移器只补了一半。
|
||||
|
||||
### 18.2 实测(43 个文件,零例外)
|
||||
|
||||
```
|
||||
样本(任一缺失): 43
|
||||
其中顶层 user/message 也缺 id 的文件: 43(共缺 270 条)
|
||||
spliced.inserted 共缺: 272 条
|
||||
|
||||
只补 inserted(**故意不碰** user/message)后跑 current 档:
|
||||
产物里 user/message 仍缺 id 的文件数: 0 ← 迁移器全部自愈
|
||||
```
|
||||
|
||||
⇒ 「补 `user/message` 无害」(§17.2)与「只需补 `inserted`」(§3)
|
||||
**两条都对,而且现在是同一件事的两面**:补了也无害,因为迁移器反正会覆盖成它自己的 id。
|
||||
|
||||
### 18.3 顺带:`id` 只要求「非空字符串」
|
||||
|
||||
`messageValue()` 对 id 的约束是 `nonEmptyString`,**没有格式校验**。
|
||||
所以 `recovered-splice-<seq>-<n>` 这种确定性 id 完全合法——
|
||||
这也从代码层面解释了 §17.1(伪造 id 不污染严格校验)。
|
||||
|
||||
### 18.4 给下一位的一句话
|
||||
|
||||
**判断"该补哪些字段"时,要同时读「校验器」和「迁移器」**:
|
||||
校验器管"拒绝什么",迁移器管"自愈什么",
|
||||
**真正需要手工补的是两者之差**(这里 = 校验器要求 − 迁移器自愈 = `inserted[]`)。
|
||||
只读校验器会以为要全补;只读迁移器会以为不用补。
|
||||
|
||||
Reference in New Issue
Block a user