Files
Jyotisha/docs/tasks/TASK-people-archive-p1-fix-20260925.md
T

9.3 KiB
Raw Blame History

TASK · 星盘档案 P1 修复单:报告用错人(P0)、本人历史 500(P0)、切人时序(P1)(2026-09-25)

基线与现状

  • origin/staging = fabe0114,staging 已部署该版本(deploy run 2897)。两个 P0 在 staging 上是实况。修好之前不得提升 main。
  • 分支 codex/people-archive-p1-fix-20260925。本单最优先,其它修复单可并行但不得碰下列文件。
  • Claude 09-25 验收:全量前端测试(Linux / Node 20)3851 条,失败 61 条;与基线 514951d7 的 56 条相比多 5 条,全是需要 Node 22 mock.module 的新测试(本机 Node 20 报 ERR_MODULE_NOT_FOUND @/…),门禁 run 2895(Node 22)为绿,判为环境缺口,不算回归。next build:/ /chart /ephemeris /people 均 ○,首屏 gzip 130,933 B 与基线相同。数据库迁移审查:跨用户读 / 删、伪造"已校正"都不可行。

事故实证

P0-1 · 给别人生成报告,实际按户主资料计算且照常扣点

  • frontend/src/app/api/reports/route.ts POST:loadSubjectBirth(supabase, userId, requestedSubject) 解析到了正确的人,报告行也存了该人的 chart_profile_id;但路由总是传 jobs: createSupabasePersonalReportJobService(admin) → personal-report-route-core.ts 走排队分支返回 202。
  • 真正生成在后台:personal-report-worker-core.ts(约 316 行)const profile = await deps.loadProfile(job.userId) → personal-report-worker.ts loadProfile 读 profiles(户主);还带上户主的校正候选范围。worker 两个文件本轮没改。
  • subject-route-birth.test.ts 只测失败路径,没测经 worker 的成功路径。违反 P1 单红线 1、3 与 D5。

P0-2 · GET /api/sessions(默认本人)在自托管 Postgres 上 500

  • app/api/sessions/route.ts:29 SELF_SESSION_FILTER = "chart_profile_id.is.null,chart_profile_id.eq.self" 传给 .or();客户端 session-list-context.tsx:96 每次都带 subject=,默认 self。
  • 自托管适配器 frontend/src/lib/db/local-postgres-client-core.ts parsePostgrestOr 只支持 eq|neq|lt|gt|lte|gte。Claude 实测:parsePostgrestOr("chart_profile_id.is.null,chart_profile_id.eq.self") → THROWS: unsupported or filter: chart_profile_id.is.null。结果:侧栏「聊天记录暂时无法读取」,本人历史消失。
  • 与 BUG-990 同类(兼容层 not(col,'is',null) 未实现);BUG-990 的防复发写明"兼容层新增算子必须有真实 Postgres 覆盖,不得只加源码正则",本次 route 测试用的是 mock query builder,没拦住。

P1-1 · 整页加载后,在人物列表返回前"当前人物"一律是本人

  • current-subject.ts 只在 bindCurrentSubjectAccount(人物列表 fetch 完成后)才读 localStorage。于是:/people「和 TA 对话」整页跳 /?newChat=1,NewChatDeepLink 在账户和模型就绪时就触发,很可能早于 bind → 新对话绑成本人;「看星盘」「生成报告」整页跳转后先拉本人的盘 / 报告列表再切 → 闪现上一个人(违反红线 4)。

P1-2 · 已打开对话的消息可能被清空

  • use-session-management.ts 新增的订阅在每次 emit 时用第一页列表行(messagesHydrated:false, messages:[])替换 Home 的 sessions;bindCurrentSubjectAccount 在 id 就绪后总会 emit 一次。已加载的当前对话被换成空行,而 ensureSessionMessages 以 activeSessionId 为依赖、不重拉 → 对话内容变空;第一页之外或 URL 打开的会话被丢。SessionListProvider 每次 emit 还 setSessions([]) → 侧栏闪空。

P2

  1. 迁移回填把旧 jsonb 里客户端可随意写的 accepted / confirmed 与 active_* 抄进类型化列 → 未经校正的值变成"已校正"。
  2. 标题切换器的人物列表按页面加载缓存(loadSubjectCatalog),/people 增删改后不刷新;删掉的人仍可选。
  3. /people 删除确认在用量请求失败时显示「0 个对话和 0 份报告」,不可逆操作前给出错误数字。
  4. 今日星语是否加载用的是户主的 profileComplete / natalMinuteAvailable / profile.timezoneId;户主缺分钟时别人的卡片被压掉;客户端指纹用户主资料 + 人物 id,改别人资料后当天卡片不更新。
  5. 「和我合盘」把当前人物切成对方,后续对话可能绑成对方,而问题文本里的「我」是本人。
  6. ab6c55f8 在星历挂载时 cache.current.clear(); invalidateEphemerisPage() → 暖进也多拉一次 ?date=(两轮引擎);首屏刷新失败(429 / 坏包)后永远停在「这一天的星历还没拿到。」,无重试。
  7. fabe0114 约 11 处改动断言没有「原值 / 新值 / 原因」(session-list-provider、personal-report-api、settings-mvp-contract、chart-view-route、server-owned-birth-profile、secondary-page-entry 等),PROGRESS 写「见各测试文件」但文件里没有。

P3:接口 5 人预检在自托管上无效(适配器忽略 count/head,靠触发器兜底,触发器计数无锁);非 uuid 人物 id → 数据库错误 / 500 而不是 404;报告 POST 同时收 subjectId 与 chartProfileId 可能不一致;chart-library-panel.tsx 已无入口仍保留且 Python 合同测试还在断言它;/people 编辑时表单标题仍是「添加一个人」。

决策记录

  • D1 worker 必须按报告行的 chart_profile_id 经 loadSubjectBirth / resolveSubjectBirth 取出生资料(红线 3:唯一解析入口);校正候选范围只在本人时使用(沿用 consult 口径)。人物已删 → 任务失败并退点(沿用既有失败退点路径),不回落本人。
  • D2 兼容层 parsePostgrestOr 增加 is 算子(is.null / is.true / is.false,编译为 is null / is true / is false,不得写成 = null),与 BUG-990 的 not … is 同口径;必须有真实 Postgres 测试覆盖。不改路由语义。
  • D3 当前人物在模块加载时同步从 localStorage 读出(按上次账户 id 键;账户未知时先读"最后账户"键),bindCurrentSubjectAccount 只做校验与纠正;新建对话深链、星盘 / 星历 / 报告首屏请求等 bind 完成再发。
  • D4 订阅 emit 改为合并进 sessions:保留已 hydrated 的会话与消息;人物未变不 emit;Provider 不再 setSessions([]) 清空,换人时用新人物的列表整体替换一次即可。
  • D5 回填纠偏:新迁移把由 jsonb 回填而来的 birth_time_status ∈ {accepted, confirmed} 降为 reported、active_* 置空(这些值从未经过校正流程),写明理由;不动户主 profiles。
  • D6 其余 P2 / P3 按上表逐条修;「和我合盘」不改当前人物(合盘对话绑定本人,问题里写对方名字);删除确认在用量未知时禁用删除并提示「暂时查不到这个人的对话数量,稍后再试」。

硬红线

  1. 失败名单与开工基线(fabe0114)逐条一致、新增 0,必须在 Node 22 + Linux 跑(门禁环境);进度记录附对比命令与结果。推 staging 前必须跑全量(上一轮只跑定向,门禁红了 3 次)。
  2. P0-1、P0-2 各自至少一条真实路径测试:P0-1 用 worker 成功路径(注入 fake engine,断言引擎收到的是他人的出生资料、户主数据未出现);P0-2 用 npm run test:db 真实 Postgres 断言 subject=self 返回存量 null / self 行、不返回他人行。
  3. 每条改动的既有断言写三栏,包括补齐 fabe0114 缺的约 11 处。
  4. 不改已验收的产品口径(5 人、无关系、标题切换、历史按人);不改 workflow。

任务分解

  • T1 worker 按人取资料 + 失败退点(P0-1)。
  • T2 兼容层 is 算子 + 真实 Postgres 测试(P0-2)。
  • T3 当前人物同步读取与请求闸门(P1-1)。
  • T4 会话列表合并而非替换、Provider 不清空(P1-2)。
  • T5 回填纠偏迁移(P2-1);切换器在 /people 变更后刷新(P2-2);删除计数失败禁用(P2-3);今日星语按当前人物判定与指纹(P2-4);合盘不切人(P2-5);星历暖进不重复拉取 + 首屏失败可重试(P2-6);补三栏(P2-7)。
  • T6 P3 逐条;删除 chart-library-panel.tsx 与只测它的断言。
  • T7 记录:BUG 新号(P0-1 报告用错人、P0-2 本人历史 500 复发自 BUG-990),BUG-1030 状态改回 investigating 直至真机清单;PROGRESS;更新 docs/testing/people-archive-p1-20260924.md(加:给朋友生成报告后打开报告核对出生资料;本人侧栏历史可见;/people 点「和 TA 对话」新对话标题旁是 TA 的名字;在别人的对话里刷新页面后仍是 TA)。

开工前置命令

git fetch origin --prune
node -v   # 必须 22.x;不是就先切到 22 再跑测试
git worktree add -b codex/people-archive-p1-fix-20260925 .worktrees/people-archive-p1-fix-20260925 origin/staging
git worktree add --detach .worktrees/p1fix-baseline origin/staging
cd .worktrees/p1fix-baseline/frontend && npm test > /tmp/p1fix-base.log 2>&1
cd ../../people-archive-p1-fix-20260925/frontend && ./node_modules/.bin/tsc --noEmit && npm run lint
npm run test:db   # 需 Docker

BUG 编号

写单时最大 BUG-1030;本单从 BUG-1031 起,开工时核对。