Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v
12 KiB
12 KiB
修复单:婚恋合盘在 staging 点「开始合盘」就失败,及验收遗留(2026-10-21)
基线
origin/staging=63984d4fc(已部署,/api/health的gitCommit一致)。开工前git fetch origin --prune。- 分支:
codex/synastry-staging-fix-20261021;工作树:.worktrees/synastry-staging-fix-20261021。 - 关联:#82(完整合盘报告)、#83(进对话问依据)、BUG-990(自托管兼容层缺查询写法)、BUG-1397、BUG-1398。
事故实证
| # | 现象(产品负责人 10-21 真机) | 根因(按符号定位) |
|---|---|---|
| 1 | P0:选好两个人点「开始合盘」,按钮下出现「报告生成暂时不可用」,一份报告都生成不了 | frontend/src/lib/synastry-full-create.ts 的 createSynastryFullReport 用 .gte("created_at", …) 数今天已生成的报告;自托管数据库客户端 frontend/src/lib/db/local-postgres-client-core.ts 的查询构造器没有 gte 方法(只有 eq / neq / gt / lte / like / in / is / not / or;内部类型里有 gte 但没实现,编译分支里也没有 >=)。调用时报 TypeError,被 app/api/reports/route.ts 的 POST 外层 catch 吞成通用 500「报告生成暂时不可用」。出错在预留点数之前,不扣点。单测用的是手写桩(Query 类型里自带 gte),所以测试全绿、门禁也绿。与 BUG-990 同一类:兼容层不支持的写法只在真库上暴露 |
| 2 | 关系时间线每段只显示阶段名和描述,看不出是哪段时间 | components/synastry/synastry-report-view.tsx 时间线只渲染 segment.phase / segment.text;TimelineSegment 里有 start / end(lib/synastry-full-timeline.ts)但从未显示。#82 任务书 E6 要求写起止年月 |
| 3 | 英文报告里「分析逻辑」和时间线依据不见了 | 67c1e6881 重排时给两处加了 language === "zh" ? … : null,改之前中英文都显示;DESIGN.md 没写这条 |
| 4 | 后台删除已发布模型后,绑定这个模型的用户旧对话不能再提问 | 0a5d93ab7 的 admin_delete_model_version;对话按 chat_sessions.model_id / model_config_version 解析模型(app/api/consult/route.ts 的 resolveModel → resolveSessionLanguageModel),解析不到就无法继续。PROGRESS-model-admin-draft-delete-20261020.md 也写明了这一后果 |
| 5 | 开关读取失败时显示「星盘档案暂时读不出来」(读的是开关不是档案);开关关闭时先闪一下选人页再变「合盘服务暂未开放」 | components/synastry/synastry-picker.tsx 的 load |
| 6 | docs/BUG_HISTORY.md 里 BUG-1397 整段写了两遍 |
006b067e8 |
| 7 | 点「开始合盘」后跳到等待页,侧栏亮的是「我的报告」而不是「婚恋合盘」,浏览器标签标题是「个人报告」 | components/synastry/synastry-picker.tsx 创建成功后 window.location.assign(\/reports/${id}`),「之前的合盘」也链到 /reports/:id;components/app-sidebar.tsx按pathname.startsWith(href + "/") 点亮;app/(app)/reports/[reportId]/page.tsx的metadata.title` 写死「个人报告」。#82 任务书 T8 只写「跳到阅读页」,没规定合盘报告的归属——任务书遗漏,不是执行错 |
| 8 | 合盘报告页在电脑上「不能下滑」:鼠标在正文两侧空白处滚轮不动 | app/globals.css 的 .synastry-report 自己是滚动容器(width: min(720px, 100%) + overflow: auto),两侧空白属于 .chat-panel(overflow: clip),滚轮落在空白处无处可滚。个人报告是整宽的 .personal-report-reader 滚动、内层居中。来自 67c1e6881 重排 |
| 9 | 第三节正文开头重复了标题(「沟通与理解 沟通与理解 沟通的天气…」) | 模型写稿时把节标题写进正文,保存与质检都没拦 |
| 10 | 等待页写「岳辰 × 林瑶」,报告标题写「林瑶 × 岳辰」,同一份报告两人顺序不一致 | 未定位:等待页用选人页传入的顺序,报告用 synastry-full-run.ts 存的 names;执行方开工时先定位 |
| 11 | 同一对人已经有一份合盘报告,再点「开始合盘」直接跳回上一份,没法再生成新的 | 设计如此:synastry-full-create.ts 的 createSynastryFullReport 按两人 id + 出生资料指纹查已有报告,ready / generating 命中即返回 reused: true(#82 决策 E3「再次打开读已有报告,不扣点」)。产品现推翻 |
决策记录(产品负责人 2026-10-21)
| # | 决定 |
|---|---|
| K1 | 第 4 条选 B:被删模型绑定的旧对话,下一轮自动改用当前默认模型继续,不报错;对话里不另行提示(模型名在设置里本来就能看到) |
| K2 | 第 2、3 条按 #82 原定:时间线每段显示起止年月;英文报告同样有「分析逻辑」和时间线依据(英文内容来自英文那一次生成,不翻译中文) |
| K4 | 第 7 条(10-21 产品确认):合盘报告有自己的地址 /synastry/:reportId,阅读组件仍复用同一个;侧栏点亮「婚恋合盘」,标签标题「婚恋合盘 · Jyotisha」。旧地址 /reports/:id 打开合盘报告时自动转到新地址(replace,不留历史),已分享的旧链接继续可用 |
| K5 | 第 8~10 条(10-21 产品确认)并入本单:整块区域可滚、正文居中;正文开头重复的节标题去掉并让质检拦住;两人顺序一律按选人页选择的顺序(甲在前) |
| K6 | 第 11 条(10-21 产品选「允许重新生成」):推翻 #82 决策 E3 的复用部分。 同一对人已有 ready 报告时,「开始合盘」照常新生成一份、照常扣点(synastry.full);旧报告保留,在「之前的合盘」里并列。同一对人已有 generating 报告时仍跳到那份等待页,不重复扣点。为防误扣,选好的两人已有报告时,按钮下点数说明后加一句「这两人已经合过 N 次,再生成会另扣 X 点」(N、X 读实际数据)。每日上限照旧 |
| K3 | 定价 synastry.full 19 点维持(已在 staging 生效) |
硬红线
- 修 P0 用补齐兼容层(
gte,顺手补对称的lt),不改业务查询去绕开;这是 AGENTS §7.6 要求的「新用到的查询写法必须 test:db 真查」。 - 新增一条防复发测试:扫描
frontend/src里对数据库查询构造器调用的方法名(.eq(.gte(等,限定在from(...)链上),断言每个都在local-postgres-client-core.ts的构造器上有实现。实现方式不限,但要能拦住「桩里有、真客户端没有」。 - 合盘创建链路必须有一条
npm run test:db真库测试:用真的自托管客户端走createSynastryFullReport到「预留成功、任务入队」为止(模型与 Python 计算可打桩),不得用手写Query桩。 - 动表的轮次真跑
npm run test:db;改既有断言写三栏;不改.gitea/。 - 修完推 staging 后,在 PROGRESS 里写明需要产品负责人真机复测的步骤(见 T6;复测步骤加一条:点「开始合盘」后侧栏仍是「婚恋合盘」);执行方不得写「已通过」。
任务分解
| # | 内容 | 验收标准 |
|---|---|---|
| T1 | P0:local-postgres-client-core.ts 补 gte、lt(构造器方法 + 编译成 >=、<);BUG_HISTORY 新编号,关联 BUG-990 |
test:db:新增兼容层测试真查 gte / lt;合盘创建真库测试(红线 3)通过 |
| T2 | 防复发扫描测试(红线 2) | 把 gte 方法临时删掉时该测试会红(PROGRESS 里写这一步的结果) |
| T3 | 时间线显示起止年月:每段标题行「2027 年 9 月 – 2029 年 3 月 阶段名」,英文「Sep 2027 – Mar 2029」;第一段起点是今天时写「现在」/「Now」 | 渲染测试用名人 golden |
| T4 | 英文报告恢复「分析逻辑」(Analysis)与时间线依据,折叠方式与中文一致;DESIGN.md 补一句 | 渲染测试:英文模式下每个正文节都有折叠块 |
| T5 | K1:对话解析不到绑定的模型时,改用当前默认模型继续,并把会话的 model_id / model_config_version 更新成新模型;没有任何可用模型时才报现有的「模型不可用」 |
单测:被删版本的会话下一轮用默认模型;无默认模型时仍报错 |
| T7 | K4:新增 app/(app)/synastry/[reportId]/page.tsx(复用 PersonalReportPage,标题「婚恋合盘 · Jyotisha」,保留 ?lang=);选人页「开始合盘」跳转与「之前的合盘」链接改新地址;报告中心列表里合盘卡片直接链新地址;/reports/:id 读到的是合盘报告时 router.replace 到 /synastry/:id(带上 lang);新地址打开的若是个人报告则反向转回 /reports/:id;合盘报告页左上「‹ 婚恋合盘」返回不变 |
渲染/路由测试:新地址下侧栏「婚恋合盘」为当前项;旧地址合盘报告触发 replace 到新地址且保留 lang;个人报告不受影响;DESIGN.md 补一句 |
| T8 | 最先做:合盘报告由外层整宽容器滚动(与 .personal-report-reader 同法),.synastry-report 只负责居中排版,不再自带 height:100%/overflow;手机与打印不退化 |
样式/渲染测试断言滚动容器是整宽外层;PROGRESS 写明桌面滚轮在两侧空白处可滚的人工步骤 |
| T9 | 保存前去掉每节正文开头与节标题相同的文字(中英文都做);质检规则增加「正文以节标题开头」判不合格并触发重写 | 单测:正文以「沟通与理解」开头时保存结果不含重复;质检对该样例报错 |
| T10 | 定位第 10 条根因;等待页、报告标题、报告中心卡片、对话标题四处两人顺序一致,按选人页顺序 | 单测:甲乙顺序贯穿创建→存储→阅读页标题 |
| T11 | K6:createSynastryFullReport 只在已有 generating 时复用,ready 不再复用;选人页按钮下提示已合次数与将扣点数;#82 任务书 T1 的 test:db 断言「同一对人同指纹第二次找到已有报告」按三栏改为「第二次新建一份、扣点;生成中时复用」;引擎计算结果(synastry_reports)可以继续复用,只是报告正文重写 |
test:db 真库:ready 后再建得到新 id、点数再减;generating 时再建返回原 id、点数不变;渲染测试:已有报告时显示提示句 |
| T6 | 选人页:开关读取失败显示「合盘服务暂时连不上,请稍后再试」;开关结果回来之前不渲染选人区(只渲染标题和副标题);删掉 BUG_HISTORY 里重复的 BUG-1397 一段;CHANGELOG、PROGRESS(含真机复测步骤:选两人生成一份、等待页、报告揭幕、切英文、从末尾进对话问一节) | 渲染测试;文档齐 |
已完成(Claude 直接执行,产品负责人 10-21 授权「P0 你改吧,其余交给执行方」)
T1、T2 已由 Claude 在 codex/synastry-gte-fix-20261021 完成并推 staging(BUG-1399,进度见 PROGRESS-synastry-gte-fix-20261021.md):构造器补 gte / lt、DataQueryBuilder 补方法、合盘创建改用共享类型、假连接池 SQL 测试、真库过滤测试、扫描测试。执行方从 T3 做起,另外仍要补红线 3 的「合盘创建主路径真库测试」(Claude 只做了查询级的真库测试)。
让步顺序
T1、T2 最先做,单独一个提交先推 staging(门禁绿、部署后告知产品负责人可以复测生成);T8 先单独推一次,T7、T9、T10、T11 随后。
开工前置命令
git fetch origin --prune
git worktree add -b codex/synastry-staging-fix-20261021 .worktrees/synastry-staging-fix-20261021 origin/staging
cd .worktrees/synastry-staging-fix-20261021
grep -n "gte\| lt(" frontend/src/lib/db/local-postgres-client-core.ts
grep -rn "\.gte(\|\.lt(" frontend/src --include=*.ts --include=*.tsx | grep -v "z\.\|number()"
grep -n "BUG-1[0-9][0-9][0-9]" docs/BUG_HISTORY.md | tail -3
验证:tsc --noEmit、npm run lint、npm test(失败名单与基线逐条一致)、npm run test:db、npm run build(/ 仍 Static,首屏 gzip ±2%)。
BUG 编号
BUG-1399 已由 Claude 的 P0 使用;执行方从 BUG-1400 起,开工时核对。