Files
Jyotisha/docs/tasks/PROGRESS-rectification-telemetry-20260926.md
T
Jesse_ChenandClaude Opus 5.5 12cbe6f868
Independent Staging Quality Gate / validate (push) Failing after 6m44s
Independent Staging Quality Gate / publish (push) Skipped
docs(tasks): telemetry deployed, test:db passed in gate run 2938
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-09-26 18:56:15 +08:00

14 KiB
Raw Blame History

PROGRESS · 生时校正匿名聚合统计(2026-09-26)

  • 任务书:docs/tasks/TASK-rectification-telemetry-20260926.md
  • 分支:codex/rectification-telemetry-20260926,基线 origin/staging 7203d94e(fewer-probes-card 合入之后);提交前变基到 f74825a2(其后只多了离线研究脚本与文档,不涉及本单文件)
  • 执行:Claude 子代理(直接执行模式,产品授权)。未推送。
  • 产品新增能力,任务书未要求 BUG 号,不开 BUG 号。Skill 不 bump(用户可见行为不变)。

结论先行

任务 做了什么 状态
T1 迁移 + RLS + 保留期 frontend/supabase/migrations/20260926010000_rectification_telemetry.sql:两张新表(统计行 rectification_telemetry、去重台账 rectification_telemetry_reported_cases)、三个 SECURITY DEFINER 函数(写 / 清理 / 汇总)。只加不改 完成;test:db 本机无 Docker 未跑,以门禁 DB job 为准
T2 写入点 + 纯函数 + 白名单测试 frontend/src/lib/rectification-agentic/v9/telemetry.ts;挂在 GET /api/rectification/cases/[caseId],复用这次响应已算好的决策与卡片;setImmediate 后写、不 await、吞错 完成
T3 后台汇总页 「校正统计」/admin/rectification-telemetry + GET /api/admin/rectification-telemetry:宽度分布、提问数分布(含分类均值)、停止原因占比、门槛达标率、按周趋势、差距分布(=卡上是否显示百分比)、出生时间来源、版本 完成
T4 记录 CHANGELOG、DESIGN(后台节)、本文件、docs/testing/rectification-telemetry-20260926.md、docs/operations/rectification-telemetry-privacy.md、BLOCKED 完成

字段表(行里只有这些,外加 id 随机 uuid 和 recorded_week)

字段 取值 / 口径 来源
recorded_week 记录所在 ISO 周的周一(UTC),库里默认值生成 DB default
window_radius_minutes 出卡时搜索窗口宽度的一半,0–720 case.candidateRange
birth_time_source hospital_record / approximate / period_only / unknown(block_scan 阶段或来源为 unknown 记 unknown;family_exact、legacy_import、空都并入 approximate,与 normalizeBirthTimeSource 一致) case.birthTimeSource、case.stage
questions_total 已问过的问题数:焦点状态为 active / resolved / declined / skipped;superseded(未答就被换掉)不算 焦点列表
questions_targeted 问题 id 以 collect:targeted: 开头 同上
questions_guided 以 collect:guided: 开头(D1 之后每个校正最多 2 道,可直接看新流程是否生效) 同上
questions_dated_probe schema 带 probe_year 的点选探针 同上
questions_personality choice_kind = varga_style、tie_break_round、D9/D10 风格题,以及不带年份的引擎探针(09-09 起按性格题处理) 同上
questions_open 其余 collect:*(开放采集、邀请补经历、职业、其他) 同上
其他类 不单独存;= 总数 − 五类之和(时段选择、扩窗等),汇总页显示 派生
experiences_added 仍有效的经历件数(confirmed / draft / pending_confirmation) 证据台账
range_width_minutes 卡片范围宽度(卡片没有范围时用决策的可信范围),0–1440 range_delivery.range
candidate_count 仍在比较的候选分钟数 decision.separation.ranked
top_two_gap_points 卡片三列相对可能性第一与第二之差(百分点);只有一列记空 range_delivery.columns
stop_reason 见下 决策 + 焦点 + 推断回执
precision_gate_met 决策层本轮测得的 D1 门槛 decision.precisionGateMet
duration_seconds 从最早一轮到现在的秒数,封顶 1 年 轮次时间
algorithm_version / policy_version / skill_version 只允许 [A-Za-z0-9._:+-]{1,64},否则记空 快照 / Case

停止原因(先中先得):

  1. user_stopped:sessionOutcome = provisional_range_user_stopped(Case 为 paused,点「停止」或说不想继续)。
  2. converged:精度门槛达标。
  3. round_cap:决策层自己的熔断 budgetExhausted(8 轮 / 有效答题上限 / 平台期),从推断回执算,和决策用的是同一个函数(本单只加了 export)。
  4. user_no_more:stopReason = user_uncertainty_too_high(多半答「说不好」),或出卡前最后一道已关闭的采集题被用户答了「没有 / 记不清」。
  5. pool_exhausted:其余(题源问空,包括 probe_pool_exhausted、tied_first、七条线问完就出卡)。
  6. error:枚举保留,当前没有任何路径会写(产品里不存在「因错误结束一个校正」的事件)。

与新流程(fewer-probes-card,7203d94e)的对应:引导题数量(D1 上限 2)直接是 questions_guided;「卡上是否显示百分比」(D4,差距 ≥ 5)没有单独存,由 top_two_gap_points 派生——汇总页的差距分布按 0–2 / 3–4 / 5–9 / 10+ / 只有一列分桶,5–9 与 10+ 即显示了百分比。这样以后阈值变了,历史行仍能按新阈值重算,也没有在任务书字段表之外另加字段。

偏离任务书之处(请验收方裁决)

  1. 不存用户 id(D4 更严)。任务书 D4 允许为去重与删除联动存用户 id;协调方的硬红线是行里不存任何用户 / 案例 / 会话 id。落法:统计行没有任何 id;去重另用 rectification_telemetry_reported_cases(case_id),外键 on delete cascade 到校正记录——账户删除 → 身份用户 → Case 级联 → 台账行删除。台账没有时间列也没有指向统计行的列,统计行无法关联回人;它本来就不含个人信息,所以账户删除后留下也不违反 D4 的目的。
  2. 只在「第一次出卡」时写,不覆盖放弃 / 超时(D2 的后半句未做)。产品里没有「放弃」或「超时结束」事件:Case 一直可续,POST /api/rectification/cases/[caseId]/close 前端没有调用方。没出卡就离开的会话目前不入统计。要覆盖需另开单(例如新开校正时把旧 Case 记为放弃,或定时扫描长时间无活动的 Case),届时可沿用 error / 新增 abandoned 枚举(加枚举要改 CHECK,属收紧/放宽约束,需按 §7.6 拆两轮)。
  3. 时间只到周。按周趋势只需要周;记录日期精确到天会让统计行能和 Case 的活动时间对上,所以只存周一日期。
  4. 权限用 admin.customers.read。现有权限里没有「只给产品负责人」的读权限;能看单个客户资料的角色看汇总不增加暴露面。若要只给 owner,需新增权限(动 RBAC 表),本单未做。

迁移(为什么向后兼容)

  • 只有 create table ×2、create index ×1、create or replace function ×3(均为新函数名)、RLS 开启与 grant / revoke(只作用于新对象)。没有改任何旧表、旧函数、旧权限。
  • 当前已部署的代码不引用这些对象;门禁「Migrate Staging Database」先迁移后部署,旧代码在迁移后照常运行;新代码若在迁移前运行,写入失败也只记一行日志(吞错),不影响用户。
  • 表权限:两张表 RLS 开启,anon / authenticated / app_runtime / admin_runtime / service_role 全部 revoke,没有 policy;写只能走 record_rectification_telemetry(service_role),读只能走 rectification_telemetry_summary(admin_runtime),清理 purge_expired_rectification_telemetry(service_role)。
  • 保留期 180 天:每次写入先删「周一早于今天 − 180 天」的行,汇总不读 180 天以前的行,另有手动清理函数。没有定时任务。
  • 写入函数先核对 Case 属于该用户;任一 CHECK 不过(枚举、范围、版本格式、五类之和不超过总数)整笔回滚,去重标记不会残留。

测试

新增

文件 条数 内容
frontend/tests/rectification-telemetry.test.ts 18 行键 = 白名单(新增字段必须改测试);写入参数 = 白名单 + 两个 id;行里只有整数 / 布尔 / 枚举 / 版本号,虚构姓名、生日、地点、原话、id、HH:MM 都不出现;各字段取值;非法版本与缺值;问题分类;来源分类;停止原因全分支(含 budgetExhausted 同源);资格(非交付、无卡、历史读、停止);调度:不在本 tick 写、同 Case 只写一次、只读 / 不合格不写、写失败 / 同步抛错 / build 抛错都不外抛且日志只有原因码;迁移源文本:表列 = 白名单、函数参数 = 白名单、只加不改、RLS、授权、180 天;GET 路由 void 调度不 await
frontend/tests/admin-rectification-telemetry-contract.test.ts 5 后台接口只读、只调汇总函数、不直接查表、admin 无表权限;菜单与权限;无导出;解析器固定形状;周数只收 4 / 12 / 26;标签覆盖全部停止原因、差距分桶与卡片 5 个百分点规则对齐
frontend/tests/database-rectification-telemetry.test.ts 1(Docker) 真实 PG17:列白名单、RLS、各角色零表权限、函数执行权限矩阵、admin 直接查表被拒、写入 / 重复写入 no-op、他人 Case 被拒、五种非法值整笔回滚后仍可写、180 天清理、admin 汇总内容且不含 id / 生日 / 地点、周数封顶 26、账户删除级联台账而统计行保留。本机 docker unavailable 跳过

改动的既有断言(原值 / 新值 / 原因)

文件 原值 新值 原因
frontend/tests/database-local-business.test.ts(public 表白名单) profiles 后直接 redemption_attempts 中间加 rectification_telemetry、rectification_telemetry_reported_cases 本迁移新增两张表;该断言要求与 pg_tables 一致(Docker 测试,本机跳过)
tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources admin/products 后直接 admin/roles 中间加 admin/rectification-telemetry 新后台页是新的 app 路由,能力审计扫描到

两处都在代码里写了三栏注释。未弱化任何断言。

前端全量(Node 22.14.0,/exec-daemon/node)

  • 基线:origin/staging 7203d94e 同代码日志 4012 条 / 24 失败(全部 Docker / DB)。
  • 本分支:4036 条 / pass 3984 / fail 24 / skipped 28。失败名单与基线逐条 diff 为空(新增失败 0);测试名列表无消失,新增 24 个名字(18 + 5 + 1)。
  • tsc --noEmit 0 错;npm run lint 0 error(126 warning,均为既有文件,本单改动文件单独 eslint 0 输出)。

Python 门禁集合

  • python3 -m pytest $(cat gate-pytest-args.txt):948 passed / 1 skipped,与基线一致,0 失败。

构建

  • npm run build -- --webpack:成功;/ 仍 ○ Static;新增 ƒ /admin/rectification-telemetry、ƒ /api/admin/rectification-telemetry。
  • rootMainFiles gzip:zlib 默认级 131,255 B / 9 级 130,950 B;基线 130,933 B → +0.25%(默认级)/ +0.01%(9 级),在 ±2% 内。增量来自 webpack runtime 里多了新后台页 chunk 的映射,首页代码未变。
  • 构建产生的 frontend/frontend/ 已删除,未提交。

截图(虚构汇总数据)

后台布局要求服务端管理员会话,本机没有;截图用一个临时页面(未提交,已删除)把同一个 AdminApp + RectificationTelemetrySummaryView 渲染出来,next start + Chrome 无头,CDP 拦截 /api/admin/session 与 /api/admin/rectification-telemetry,后者返回经真实 parseRectificationTelemetrySummary 处理过的虚构汇总。控制台 0 error。

  • docs/testing/rectification-telemetry-20260926/telemetry-data-1280.png:桌面,98 个虚构会话
  • docs/testing/rectification-telemetry-20260926/telemetry-data-390.png:手机宽度(按周趋势表横向滚动)
  • docs/testing/rectification-telemetry-20260926/telemetry-empty-1280.png:无数据时

首轮截图后按手机宽度把分布表「占比」列从固定 200px 改为 36% 并重截,上面三张是改后版本。最终干净构建复测 gzip 不变(131,255 / 130,950 B)。

环境缺口

  • 无 Docker:npm run test:db 未跑(database-rectification-telemetry.test.ts 与 database-local-business.test.ts 的新表白名单都待门禁 DB job;run 号推送后补记)。
  • 无受控后台账号:登录态、真实数据按 docs/testing/rectification-telemetry-20260926.md 待产品走。
  • 未部署。

没做的

  • 放弃 / 超时结束的会话不入统计(见偏离 2)。
  • error 停止原因无写入路径。
  • 没有定时清理任务(写入时清理 + 汇总只读 180 天内)。

验收(Claude,2026-09-26)

基于 origin/staging f74825a2 独立复跑(Node 22.14):tsc 0;lint 0 error;npm test 4036 / 24 fail,失败名单与 7203d94e 基线(4012 / 24)逐条一致;Python 门禁集退出 0;next build --webpack /、/chart、/ephemeris、/people 仍 ○ Static,rootMainFiles gzip(level 9)130933 B,±0%。迁移逐段审读:只加表/索引/函数,begin/commit 与授权写法与 0925 系列迁移一致,RLS 开、运行时角色无表权限、写只经 SECURITY DEFINER RPC、读只出聚合。

偏差裁定:

  1. 统计行不存用户 id、改用按 case_id 级联删除的去重账本——比任务书 D4 更严,认可。
  2. 放弃/超时的会话不记录——产品里没有这个事件,认可;汇总只覆盖「出过卡」的会话,读数时须记住这个偏差。
  3. 周粒度日期、admin.customers.read 权限——认可。

环境缺口:npm run test:db 以本次推送的门禁 DB job 为准(run 号见下方补记);管理员真实登录走查见 docs/testing/rectification-telemetry-20260926.md。

补记(2026-09-26):40d7930a 的门禁 run 2937 三次都挂在 npm ci(镜像挂起,900s 超时,非代码问题);产品侧修复 CI(e801fcf5)后门禁 run 2938 通过:Python 948 passed / 1 skipped,前端含 DB 4049 / 0 fail / 0 skip,其中 database-rectification-telemetry 在真实 PostgreSQL 通过;migrate run 2940 应用 20260926010000_rectification_telemetry.sql;deploy run 2941,/api/health gitCommit = e801fcf5。剩余:管理员登录态真实页面走查。