docs: pin the truncation rule to arithmetic after Codex review

"Truncate at cap and append the ellipsis" admits both 501 and 500 total
length; the two would desync test assertions from the telemetry length
promise. Cap is now the total including the marker.
This commit is contained in:
2026-08-16 05:09:06 -04:00
parent 10fbc5441e
commit 707f8f7317
@@ -62,7 +62,7 @@ class PolyGatewayError(Exception):
| 函数 | 职责 | 关键防御 | | 函数 | 职责 | 关键防御 |
|---|---|---| |---|---|---|
| `summarize_body(text) -> str` | 折叠空白 → 截断至 `_ERROR_BODY_CAP` → 被截时补 `…` | 空/空白入参返回 `""` | | `summarize_body(text) -> str` | 折叠空白 → 按 §3.4 的机械规则截断 | 空/空白入参返回 `""` |
| `response_body(response) -> str` | 从 `httpx.Response` 取已缓冲文本 | `ResponseNotRead` → 返回 `""`,**绝不触发网络读** | | `response_body(response) -> str` | 从 `httpx.Response` 取已缓冲文本 | `ResponseNotRead` → 返回 `""`,**绝不触发网络读** |
- **折叠空白不是洁癖**:错误体常是缩进 JSON,直接拼进 message 会让一行日志炸成多行、遥测列不可读。 - **折叠空白不是洁癖**:错误体常是缩进 JSON,直接拼进 message 会让一行日志炸成多行、遥测列不可读。
@@ -90,7 +90,11 @@ message 形态:`"{源名} {标签}: {状态码} | {摘要}"`;**摘要为空时
### 3.4 常量取值 ### 3.4 常量取值
`_ERROR_BODY_CAP = 500`。Issue 给出的真实样本约 160 字符;既有遥测文本截断常量是 200(`embedding.py:72``ocr.py:72`),对错误体偏紧(会切掉稍长的 `error.message`)。500 能容下绝大多数网关错误体,最坏情形(5xx 重试 3 次)每次调用向遥测多写约 1.5KB,`TEXT` 列可承受 **机械规则(实现与测试逐字照此)**:`_ERROR_BODY_CAP = 500` 是**最终摘要的总长度上限**,含截断标记。折叠空白后长度 ≤ 500 则原样返回;超出则取前 **499** 字符并追加 `…`,总长恰为 500
> 这条必须写成算术而非叙述:`text[:500] + "…"`(501)与 `text[:499] + "…"`(500)都能被"截断至 cap 并补 `…`"这句话涵盖,而两者会让测试断言与遥测长度承诺对不上(Codex 审查 2026-08-16 提出)。
取值理由:Issue 给出的真实样本约 160 字符;既有遥测文本截断常量是 200(`embedding.py:72``ocr.py:72`),对错误体偏紧(会切掉稍长的 `error.message`)。500 能容下绝大多数网关错误体,最坏情形(5xx 重试 3 次)每次调用向遥测多写约 1.5KB,`TEXT` 列可承受。
message 与 `body_text` **共用同一变量**,不设两个长度:两份不同长度会让"遥测里看到的"与"下游 catch 到的"对不上,排查时反而多一层困惑。 message 与 `body_text` **共用同一变量**,不设两个长度:两份不同长度会让"遥测里看到的"与"下游 catch 到的"对不上,排查时反而多一层困惑。
@@ -118,7 +122,7 @@ message 与 `body_text` **共用同一变量**,不设两个长度:两份不同
### 4.3 幂等与重复 ### 4.3 幂等与重复
纯函数,同输入同输出;`summarize_body`已摘要文本再摘要是幂等的(长度已 ≤ cap) 纯函数,同输入同输出`summarize_body`自身输出再调用一次是幂等的:输出总长恒 ≤ 500(§3.4)且不含需折叠的空白,第二次调用走"原样返回"分支,不会出现 `…` 被反复追加
### 4.4 持久化与原子性 ### 4.4 持久化与原子性
@@ -184,7 +188,8 @@ Issue 的中转 400 场景确实指向这个方向,但当前只有一个使用
|---|---|---| |---|---|---|
| 1 | **端到端遥测**:mock transport 返回 400 + 真实样本体 → 断言 recorder 收到的 `error` 含摘要 | G1 | | 1 | **端到端遥测**:mock transport 返回 400 + 真实样本体 → 断言 recorder 收到的 `error` 含摘要 | G1 |
| 2 | 参数化状态码(400 / 401 / 404 兜底 / 429 普通 / 429 `insufficient_quota` / 500)→ 断言 message 含摘要且 `exc.body_text` 非空,**分类与既有断言逐一不变** | G2, G4 | | 2 | 参数化状态码(400 / 401 / 404 兜底 / 429 普通 / 429 `insufficient_quota` / 500)→ 断言 message 含摘要且 `exc.body_text` 非空,**分类与既有断言逐一不变** | G2, G4 |
| 3 | 超长体 → 截断至 cap 且以 `…` 结尾;`body_text` 与 message 中的摘要**逐字相同** | G3 | | 3 | 超长体 → `len(summary) == 500` 且以 `…` 结尾,前 499 字符与原文前 499 字符逐字相同;`body_text` 与 message 中的摘要逐字相同 | G3 |
| 3b | 长度恰为 500 / 501 的体 → 前者原样不带 `…`,后者截为 500 带 `…`(边界) | §3.4 |
| 4 | 多行缩进 JSON → 折叠为单行 | G3 | | 4 | 多行缩进 JSON → 折叠为单行 | G3 |
| 5 | 空体 / 纯空白体 → 不拼悬空分隔符,`body_text == ""` | §3.3 | | 5 | 空体 / 纯空白体 → 不拼悬空分隔符,`body_text == ""` | §3.3 |
| 6 | 非 JSON 体、非 UTF-8 字节 → 不抛异常,分类不变 | §4.2 | | 6 | 非 JSON 体、非 UTF-8 字节 → 不抛异常,分类不变 | §4.2 |