9.5 KiB
任务书 · 更新出生资料后星盘真值一致性与可诊断错误(2026-09-22)
0. 基线与串行关系
- 基线 commit:
1bc6a954c597e2817fe72bfedad93119ab19a527(当前origin/staging)。执行方开工前必须重新git fetch origin --prune,再以最新origin/staging为基线。 - 执行分支:
codex/chart-profile-update-consistency-20260922;工作树:.worktrees/chart-profile-update-consistency-20260922。 - 本单是星盘现场测试暴露的功能修复,不是整页重设计。与既有
TASK-chart-page-blocking-open-20260915.md、TASK-chart-vedastro-decouple-20260915.md、TASK-readonly-pages-fix-20260916.md有交集时必须串行:先核对这些分支是否已合入 staging;未合入时不得同时改同一符号,按“既有星盘打开链路 → 本单资料真值一致性”顺序执行。 - 本单不接入
chart_profiles作为/chart的主资料来源;/chart继续使用账户权威profiles。人物资料库属于普通聊天的另一条 subject binding 任务。 - 与既有星盘打开链路任务严格串行:先完成并核对
TASK-chart-page-blocking-open-20260915.md、TASK-chart-vedastro-decouple-20260915.md、TASK-readonly-pages-fix-20260916.md的 staging 状态,再处理本单真值/缓存;不得并行修改相同 chart-view loader、engine client 或错误映射符号。
1. 事故实证
会议现场反馈:用户已经更新出生时间,但打开星盘仍提示没有可显示内容,或点击后无法打开。按产品设计,填写出生资料后应能生成并查看星盘。
调查已确认的代码事实(按符号定位,不依赖脆弱行号):
frontend/src/app/(app)/chart/page.tsx只挂载客户端 chart 壳;frontend/src/hooks/use-chart-page.ts通过refreshChartPage()请求/api/chart-view。frontend/src/lib/chart-view-client.ts对响应做 JSON/Zod 合同解析;解析失败或请求失败会归并为通用chart_unavailable。frontend/src/lib/secondary-page-data.ts的chartCache是模块级单值缓存,没有账户/profile 版本键;资料 PATCH 成功后没有明确的 chart snapshot 失效边界。frontend/src/lib/chart-view-service.ts从profiles读取ACCOUNT_BIRTH_SELECT,再经server-owned-birth-profile.ts与 timezone resolver 组装排盘资料。- accepted/confirmed 资料存在
active_birth_date但缺active_birth_timezone_offset时,server-owned-birth-profile/birth-profile-timezone会抛出adopted_birth_calculation_incomplete或 timezone error;当前错误路径可能最终只显示通用不可用状态。 frontend/src/lib/account-profile-patch.ts的普通账户 PATCH 主要处理 active time/status,不会在所有声明变化后原子维护 adopted date/offset/provenance;迁移frontend/supabase/migrations/20260920020000_adopted_birth_date.sql的 trigger 会在特定字段变化时清理 adopted tuple。这需要实证核对,不能在任务书外擅改出生资料语义。loadChartView对 profile 查询错误的区分不足,数据库读取失败可能被误认为资料不完整。
相关历史:BUG-005、BUG-009、BUG-018、BUG-073、BUG-076、BUG-715~717、BUG-936。BUG-936 是首页 JS 死屏,不替代本单的 /chart 数据真值问题。
2. 根因
当前已确认的是“错误分类、资料更新一致性和客户端旧快照存在断点”;具体现场账号命中的单一数据库状态尚未证明。因此本单不得把某个假设写成已确认根因。优先验证并修复三层:
/api/accountPATCH、数据库 trigger/adoption 与profiles中 date/time/offset/provenance 的原子一致性;GET /api/chart-view对 profile/query/timezone/engine/schema 各类失败的结构化分类;- profile 更新、account refresh、chart request 和
chartCache之间的失效与竞态。
3. 决策记录(产品授权)
- 产品授权:填写出生资料后必须能够生成并查看星盘;不能用“资料还在,过一会儿再打开”掩盖永久错误。
- 保持现有安全语义:reported、accepted、confirmed、candidate、active provenance 不得混为一谈;confirmed 的普通资料编辑不得覆盖受保护的 active time。
- 失败要可理解、可诊断;不得把数据库错误、资料不完整、时区 adopted tuple 错误、引擎忙/超时和响应格式错误静默归成一个空状态。
- 本单不顺手改星盘算法、Dasha、校正算法、Node/Ayanamsa 业务规则,也不把
chart_profiles作为/chart真值。 - 若现有迁移/trigger 与上述边界冲突,执行方必须停在任务书与 BUG 记录中,不能自行放宽或删除保护。
4. 硬红线
- 不能信任客户端出生日期、时间、地点或 offset 覆盖服务端 profile 真值。
- 不得通过清空
active_birth_*、放宽 accepted/confirmed 校验或 fallback 到不一致声明值来“修复”页面。 - 不得把数据库 query error 当作
birth_profile_incomplete;日志不得记录姓名、出生日期/时间、坐标、完整请求体、Cookie、JWT、密钥或模型原文。 - 不新增 spinner、骨架屏或第二套滚动/加载机制;继续遵守
frontend/DESIGN.md与frontend/docs/VOICE.md。 - 不修改
scripts/jyotish_api_server.py增长红线,不改.gitea/workflows/**,不提升main。 - 若动表,必须通过向后兼容迁移并运行
npm run test:db --prefix frontend;删除/重命名/收紧约束必须另轮。 - 任何既有断言变更都要写“原值 / 新值 / 原因”;测试名不得减少。
5. 任务分解与验收标准
T1 · 端到端重放并锁定服务端真值
- 用 synthetic/golden fixture 覆盖 reported、accepted、confirmed、跨午夜 adopted date;不要写真实用户资料。
- 逐段核对
PATCH /api/account → GET /api/account → GET /api/chart-view,确认 status、reported time、active time、active date、timezone id/offset、provenance 的语义一致。 - 若发现 PATCH/trigger 清掉 adopted tuple 后仍留下可排盘的 active 状态,修复为原子、可解释的状态转换;confirmed 保护保持不变。
验收:同一 fixture 的 account 与 chart-view 返回相同的 server-owned date/time/offset;accepted/confirmed 不出现可排盘但 adopted tuple 不完整的状态;reported 仍按声明字段排盘;跨午夜使用 adopted date;confirmed 普通编辑仍不能覆盖 active time。
T2 · 锁定 chart-view 错误分类
- 为 profile query error、profile incomplete、adopted calculation incomplete、timezone resolver failure、engine 429、engine timeout、bad payload、response schema failure 建立稳定内部分类和用户可读映射。
- 保留 HTTP/API 兼容性,除非测试证明现有状态码无法表达安全边界;不要把 200 的结构化业务状态随意改成异常 500。
- 日志只记稳定类别、route、耗时和必要的 HTTP status,不记出生资料。
验收:每类错误有独立测试;数据库读取失败不再伪装成 profile incomplete;用户页面不再只显示无上下文的“没有可显示内容”;VOICE.md 规定的“失败不写过会儿再打开”口径保持一致。
T3 · 修复更新后的缓存与竞态
- account PATCH 成功后明确失效/重新验证 chart snapshot,至少按 account + profile version/真值指纹隔离;不得让旧 snapshot 在新资料已成功保存后长期冒充新结果。
- 检查
refreshAccount与 chart request 的顺序,避免并发请求读到半更新 profile;engine cache key 继续包含 date/time/offset、ayanamsa、node mode 等已有必要维度。
验收:资料更新后重新进入 /chart 取得新 date/time/offset;失败时旧 snapshot 不覆盖已确认失效的 profile;同账户不同资料不会互相命中;现有 secondary-page cache 合同不被削弱。
T4 · 补完整回归链路
至少覆盖:PATCH 后 chart-view 新资料、reported/accepted/confirmed、跨午夜、缺 active offset、query error、引擎 busy/timeout/bad payload、schema parse、旧 snapshot 失效、D1 planets/houses 合同。保留现有 chart-view-route、chart-view-engine、server-owned-birth-profile、adopted-birth-date、account-api、secondary-page-entry 测试名。
6. 让步顺序
- T1 服务端真值一致性;2. T2 错误分类;3. T3 缓存/竞态;4. T4 全链路回归。若时间不足,不能以只改文案代替 T1;缓存优化可延期,但必须把旧快照风险记入进度和
BLOCKED.md。
7. 开工前置命令
git status -sb
git fetch origin --prune
git worktree add -b codex/chart-profile-update-consistency-20260922 .worktrees/chart-profile-update-consistency-20260922 origin/staging
cd .worktrees/chart-profile-update-consistency-20260922/frontend
./node_modules/.bin/tsc --noEmit
npm run lint
npm test
npm run build
开工时再次执行 grep -n '^## BUG-' docs/BUG_HISTORY.md | tail -1,若最大号变化,以当时最大号为准,不复用本文件预估号。
8. BUG 编号起点
本轮写任务书时 docs/BUG_HISTORY.md 最大号为 BUG-995。实现方若确认本现象属于新 Bug,开工前再次核对最大号,按最大号 + 1 连续编号;若只是 BUG-073/076 或 BUG-715~717 的复发,必须关联原记录,不得伪装成无关新 Bug。
9. 交付记录
代码实现同轮必须更新 docs/BUG_HISTORY.md、docs/tasks/PROGRESS-chart-profile-update-consistency-20260922.md;若有用户可见状态或文案变化,更新 CHANGELOG.md。验收未闭环前不得写 resolved,不得声称 staging 已部署。