# 生时校正匿名统计:隐私说明 任务书:`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,失败吞掉。