fix(account): answer profile writes with the derived birth-time truth
Independent Staging Quality Gate / validate (push) Failing after 10m56s
Independent Staging Quality Gate / publish (push) Has been skipped

A zero-uncertainty exact declaration is accepted server-side as the active
minute, but the account write only answered {ok:true}. Every save path then
kept the draft it submitted, so the first consultation after initialization
asked for unverified_birth_time against an accepted profile and was rejected
with mode_changed before billing.

The account route now returns the status and active minute it derived, and
every profile save adopts that result instead of its own local guess.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-17 22:13:03 +08:00
parent 6ce4e67186
commit fd415e1d11
10 changed files with 254 additions and 27 deletions
+16
View File
@@ -3893,3 +3893,19 @@
- 防复发:滚动容器留了 `padding-bottom` 时,它的 `position: sticky` 子元素永远无法贴到容器可视底边——sticky 受包含块内容盒夹持,调 `bottom` 偏移不解决问题。悬浮在输入框上方的控件应挂在输入框容器上(`bottom: 100%`),而不是挂在滚动容器里,这样才不依赖任何预留高度常量。另:BUG-218 当时已写明“浏览器内的视觉位置未经人工目视确认”,本条正是那句话对应的实际后果——纯源码合同测试能固定 DOM 与属性,固定不了几何位置,涉及定位的改动必须实测。
- 相关记录:BUG-218(引入该按钮与锚定逻辑)、BUG-252(曾把它记作 BUG-217 新增,并留下焦点丢失的待跟进项,本轮未处理)
- 修复版本:本地未提交候选
## BUG-264 | 初始化填报准确出生时间后,第一条咨询必定 409:客户端按提交的声明选路,服务端已把它升级为 accepted
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-08-17
- 最近更新:2026-08-17
- 影响面:`/` 初始化流程的出生时间与出生地点保存、`PATCH /api/account``POST /api/consult` 的出生真值校验。
- 用户现象:新用户在初始化里选择“我知道准确出生时间”并填到分钟,走完地点一步进入首页,发出第一条咨询即被拒绝,提示“出生时间状态已经变化 / 请刷新后重新选择使用填报时间、一般咨询或先完成校正”。刷新页面后同一条问题可以正常发出。
- 触发条件:声明为 `family_exact` 且误差为 0,经账户接口保存后不刷新页面直接发第一条咨询。
- 根因:账户写入会在服务端派生出生真值——零误差的准确时间被直接采用为 `active_birth_time` 且状态升级为 `accepted`——但 `PATCH /api/account` 只回 `{ok:true}`,页面又用提交的草稿覆盖 `profile`,草稿里状态仍是 `reported``time` 为空。于是客户端按未确认分钟选 `unverified_birth_time`,而咨询接口的未确认分支明确拒绝 `accepted`,在扣点前抛出 `mode_changed` 返回 409。派生规则写在服务端、客户端却各自推断同一件事,是这次不一致的入口。
- 修复:`PATCH /api/account` 随写入成功返回它派生的出生真值(状态与当前排盘分钟),新增 `resolveAppliedAccountBirthTime` 负责这一派生;`persistProfile` 返回按该真值对账后的档案,初始化出生时间、初始化地点、账户弹窗保存和默认星盘切换四条保存路径统一采用返回值,不再沿用本地草稿。服务端的真值校验保持严格,不为客户端的过期视图放宽。
- 验证:`account-api.test.ts` 锁定派生结果(零误差准确时间→`accepted`+分钟;改为时段声明→回落 `reported`;已确认与 legacy 分钟不被覆盖;纯改名不产生状态);`birth-time-intake.test.ts` 锁定客户端对账(含 `HH:mm:ss`、未知状态和缺字段时不动草稿);`profile-persistence.test.ts` 断言每条保存路径都采用返回档案,禁止回退到 `setProfile(profileDraft)``npm test` 1710 项中 1709 通过,唯一失败是并发跑 `database-*` postgres fixture 的既有抖动,单独串行复跑通过;`tsc --noEmit` 与目标文件 ESLint 清洁。未做的验证:**没有在 staging 真实新用户流程里目视复跑一遍**,需要一个未初始化的账户。
- 防复发:出生时间状态与当前排盘分钟由服务端唯一派生,客户端只能采用接口返回值;任何新增的档案保存路径都必须消费 `persistProfile` 的返回档案,源码合同测试会拦住用本地草稿覆盖 `profile` 的写法。咨询选路不得从未落库的草稿推导。
- 相关记录:BUG-018(首次保存未写入档案状态,同一类真值不一致)、BUG-017(`mode_changed` 的另一入口)
- 复发自:无
- 修复版本:本地未提交候选