Files
PolyGateway/research-wiki/designs/issue12-telemetry-retention.md
T
iomgaa 39fcf2631d docs: fix the partitioning conflict the review caught
Postgres requires a partitioned table's unique constraints to cover the
partition key, so ranging on created_at forces the primary key to
(call_id, created_at) -- and ON CONFLICT (call_id) DO NOTHING then
matches no constraint at all. The retention design claimed INSERT stays
transparent under partitioning; that holds for the routing, not for the
conflict target, and telemetry would have failed outright on any
partitioned deployment. The write drops its conflict target, which is
byte-equivalent on a plain table and legal on both.

The cap design gains the three emitter construction sites it has to
touch and the relationship to the 200-char caps embed and OCR already
carry: they stay, and the new cap is the stricter of the two. Covering
all three call paths is deliberate -- their rows land in one table, and
issue #11 settled that argument already.
2026-08-19 08:59:30 -04:00

4.1 KiB

type, node_id, title, date
type node_id title date
design design:issue12-telemetry-retention issue #12: 遥测表的正文体量、保留期与访问控制 2026-08-19

issue #12: 遥测表的正文体量、保留期与访问控制

正文: 2026-08-19-issue12-telemetry-retention-design.md。状态: 待人类审批。同批交付 design:issue13-schema-mode

  • 选定方案: 三个子问题分层落点——(a) 正文体量: 新增 PGW_TELEMETRY_TEXT_CAP,缺省 None 即不截断,截断只发生在 TelemetryEmitter._record; (b) 保留期: README 分区 + pg_partman retention 模板 + tools/telemetry_retention.py 独立脚本(默认 dry-run),库本体不持有 DELETE/DROP 权限; (c) 访问控制: 纯文档,三角色划分 + REVOKE UPDATE, DELETE + 不可变性说明。
  • 只有 (a) 改库本体代码,且它是唯一预防性手段: 没写进去的数据不需要删。
  • 缺省不截断的理由(人类决策): 截断后遥测不再是审计证据、也无法复现重放,而这是既有下游正在依赖的行为,默认改动即破坏。代价是 issue 那句"无限期保留全部租户全文不应是默认状态"只解决一半——默认仍是全文,但下游第一次有了不写全文的手段。
  • 按每条文本切而不是切整串 JSON: 后者产出非法 JSON,让此后一切按 JSON 解析该列的分析全废(SQLite 的 messages 是 TEXT 列,不做任何 JSON 校验,坏数据静默存进去)。
  • 不复用 _http_errors.summarize_body: 它折叠空白 + 保头保尾,是为错误 JSON 设计的——折叠空白会破坏正文里的代码块与缩进,保头保尾服务的是诊断而非"不想存全文"。视觉标记口径一致,实现各自独立。
  • 红线: digest_messages 一个字节都不能碰(缓存 key 与遥测共用,middleware/cache.py:31),动它 = 全量缓存 miss + key 口径分叉。已设机械化验收: 同一组 messages 在 cap 开关两态下 build_cache_key 输出逐字节相同。
  • 权限张力: 既要 REVOKE DELETE 又要清理,就只能走 DROP PARTITION(owner 操作)而非 DELETE(应用角色)。这是分区方案不可替代的理由,不是性能偏好。
  • 文档必须进 README 而非 wiki: sdist 只打包 src/ 与 README(无 MANIFEST.in),wiki 里的模板下游 pip install 后读不到——56f3805 的教训。README 的模板 SQL 另设真实 PG 集成测试逐条执行,因为下游照抄错 SQL 就中招。
  • 被否决备选: 缺省即截断(所有现有下游遥测正文被静默削短);库内建 TTL/清理(库需 DELETE 权限,与 (c) 的 REVOKE 建议直接冲突,且"纯 asyncio 中立、无全局状态"铁律排斥库内定时任务);给 TelemetryRecorderpurge_before(ts)(冻结签名的端口扩展 + 同样的权限冲突);只写文档不改代码(下游唯一手段是不用遥测)。
  • 共同边界(建议入 ARCHITECTURE D15): 库对下游库只做 SELECT/INSERT(加可选建表),一切改结构与删数据的操作交给下游,库的义务是把需要执行的 SQL 明明白白告诉下游。本设计与 design:issue13-schema-mode 各实现它的一面。
  • 审查留痕(Codex,2026-08-19): 报 3 项,采纳 1 项、部分采纳 1 项、不采纳 1 项。① 阻断级的分区表与幂等冲突已采纳,修法归 design:issue13-schema-mode §4.6,本设计 §6.1 承接分区部署下的语义差异(缓存命中行复用历史 call_id,分区表上不再被幂等吞掉)。② text_cap 漏列 emitter 构造点——缺口成立(client.py:149/embedding.py:131/ocr.py:130 三处不改即 TypeError),已补;但其"覆盖 embed/OCR 属语义扩散"的价值判断不采纳: 三条链路的行落同一张表,只覆盖一条会让同表内一半受控一半不受控(issue #11 同款判断),且核实后 embed 与 OCR 各已有 200 字符自有上限,新 cap 与之是"取更严者",实际影响远小于顾虑。③ "缺省不截断只解决一半"是人类已定的 E-a 决策而非疏漏,不改;作为补偿,README 须给一段可直接照抄的合规下游推荐配置(cap + 分区 retention + 三角色),不把三件事散着让下游自己拼。