---
type: plan
node_id: plan:plan-issue12-telemetry-retention
title: "实现计划: issue12-telemetry-retention"
date: 2026-08-19
---

# 实现计划: issue12-telemetry-retention


正文: `2026-08-19-issue12-telemetry-retention.md`。实现 [[design:issue12-telemetry-retention]]。

五个任务: ① 截断函数 + emitter `text_cap` 必填 + 三构造点; ② 配置与装配; ③ `tools/telemetry_retention.py`(默认 dry-run); ④ README 生产部署模板 + 其真实 PG 机械化验收; ⑤ CHANGELOG 与 wiki。

**前置**: issue #13 须先合并(两条分支都改 `config.py`/`client.py`,且分区模板依赖 #13 的 `telemetry_schema_sql()` 与无冲突目标写入)。

写计划时挖出的实现陷阱: `digest_messages` 对 content 非 list 的消息**原样 append 同一个 dict**,遥测拿到的与调用方传入的、缓存 key 用的是同一份对象——`_cap_messages` 若就地改,会同时污染调用方 messages、后续重试请求体与缓存写入 key,且全程无报错。计划已为此设两条红线用例,并要求先写一版就地改的实现证明红线能抓住它。
- **审查留痕(Codex 计划审,2026-08-19)**: 报 5 项与本计划相关,**全部采纳**。最实质的一条是**三个公共 Client 的直接构造路**: `TelemetryEmitter` 的 `text_cap` 必填,而 `GatewayClient`/`EmbeddingClient`/`OcrClient` 的 `__init__` 都在内部构造 emitter,只改 `from_settings` 那条路会让直接构造的下游要么撞 `TypeError`、要么永远启用不了 cap。定稿: emitter 保持必填(库内部类,唯一构造者就是这三个 Client,必填保证无一处漏传),三个 Client 各加**带默认值 `None`** 的 `text_cap`(公共装配路,而默认值恰好等于全局缺省的不截断)。其余四条: `tools` 脚本的 PG 分支(分批删除、分区探测退出码 3、缺 asyncpg 退出码 2)原本一条测试证据都没有,已补三例集成用例(缺依赖那例用只含 `raise ImportError` 的临时 `asyncpg.py` 挂 `PYTHONPATH` 构造);设计要求的**截断覆盖面声明**(`tool_calls.function.arguments` 不在覆盖内)与 **SQLite 文件轮转建议**都漏了文档落点,已补进 Task 4;与 #13 的合并冲突面(`config.py` 的字段列表与 `_load_pgw` 返回键、`client.py` 的装配)措辞已强化为必须从 #13 合并后的 main 开分支。
