Files
Jyotisha/docs/operations/rectification-telemetry-privacy.md
T
Jesse_ChenandClaude Opus 5.5 d0bfc1fc3d feat(rectification): anonymous aggregate telemetry + admin summary page
One row per rectification Case, written once when the range card is first
delivered (GET /api/rectification/cases/[caseId], fire-and-forget after the
response is built). Numbers and closed enums only: no user / case / session
id, birth data, names, text or timestamps finer than the ISO week. Dedupe via
a separate case_id ledger that cascades with the Case (and account deletion).

Migration 20260926010000 is additive: two RLS tables with no runtime table
grants, SECURITY DEFINER write (service_role), purge (service_role) and
aggregate-only summary (admin_runtime) functions; 180-day retention.

Admin: 「校正统计」 page + GET /api/admin/rectification-telemetry
(admin.customers.read), aggregates only, no per-row view or export.

TASK-rectification-telemetry-20260926. test:db not run locally (no Docker).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-09-26 15:36:29 +08:00

4.3 KiB
Raw Blame History

生时校正匿名统计:隐私说明

任务书:docs/tasks/TASK-rectification-telemetry-20260926.md。迁移:frontend/supabase/migrations/20260926010000_rectification_telemetry.sql。

存什么

每个校正会话第一次给出范围卡时记一行,只有下面这些字段(白名单写死在 frontend/src/lib/rectification-agentic/v9/telemetry.ts 的 RECTIFICATION_TELEMETRY_FIELDS,由 frontend/tests/rectification-telemetry.test.ts 钉住;加字段必须改测试):

字段 含义 类型
recorded_week 记录所在 ISO 周的周一(UTC) 日期,只到周
window_radius_minutes 出卡时搜索窗口的一半宽度 0–720
birth_time_source hospital_record / approximate / period_only / unknown 枚举
questions_total 及五个分类 定向 / 引导 / 带年月探针 / 性格 / 开放的提问数(总数含其他类) 0–1000
experiences_added 用户讲过、仍有效的经历件数 0–1000
range_width_minutes 卡上范围宽度 0–1440
candidate_count 仍在比较的候选分钟数 0–1440
top_two_gap_points 第一名与第二名差几个百分点 0–100
stop_reason converged / pool_exhausted / user_no_more / round_cap / user_stopped / error 枚举
precision_gate_met 精度门槛是否达标 布尔
duration_seconds 从第一轮到出卡的秒数 0–1 年
algorithm_version / policy_version / skill_version 版本号(只允许 [A-Za-z0-9._:+-],不合规的记空) 版本 id

不存什么

  • 不存用户、校正记录(Case)、会话编号;不存出生日期、时间、地点、姓名、邮箱;不存用户原话、证据摘要、模型输出;不存 IP;时间不细于周。
  • 候选时刻、范围起止时刻(HH:MM)也不存,只存宽度。
  • 写入失败的日志只有一行 [rectification-telemetry] write skipped reason=<错误码>,不带任何字段值、编号或错误原文。

去重与账户删除(任务书 D4 的落法)

  • 任务书 D4 允许为去重和删除联动存用户 id。本实现更严:统计行里不存任何 id。
  • 去重用另一张表 rectification_telemetry_reported_cases,只有一列 case_id,外键指向校正记录并 on delete cascade。账户删除 → 身份用户删除 → 校正记录级联删除 → 这张台账的行一并删除。
  • 台账没有时间列,也没有指向统计行的列,统计行无法再关联回任何人;账户删除后留下的统计行本来就不含个人信息,所以不需要(也无法)按人删除。
  • 同一个校正会话只记一次,以第一次出卡时的状态为准;之后继续补经历、采用、确认都不改这一行。

谁能读写

  • 两张表都开启 RLS,且不给任何运行角色表权限(anon / authenticated / app_runtime / admin_runtime / service_role 全部 revoke)。
  • 写:只能通过 record_rectification_telemetry(SECURITY DEFINER,只授权 service_role);函数先核对该 Case 属于该用户,再写台账和统计行,任一 CHECK 不过则整笔回滚。
  • 读:只能通过 rectification_telemetry_summary(weeks)(SECURITY DEFINER,只授权 admin_runtime),返回中位数、分布、按周趋势和版本分布,不返回任何单行。后台接口 GET /api/admin/rectification-telemetry 需要 admin.customers.read 权限,没有逐条列表、没有导出。

保留期

  • 180 天。每次写入先删除「所在周的周一早于今天 180 天」的行;汇总函数也不读 180 天以前的行;运维可调用 purge_expired_rectification_telemetry()(只授权 service_role)手动执行同一删除。
  • 没有定时任务:长时间没有新写入时,过期行在库里但汇总看不到,下一次写入即删除。

写入时机与性能

  • 写入点是 GET /api/rectification/cases/[caseId](每轮结束后前端都会刷新它),只在当前决策为交付结果(sessionOutcomeAllowsDelivery)、卡片有候选列、且最近活动在 2 小时内时记录;翻看很久以前的历史不会补记。
  • 复用这次响应已经算好的决策和卡片,不再重复计算;写库在响应构建完之后另起(setImmediate),不 await,失败吞掉。