From 9523ad23e85770798b4239ba8831decc1f565ef3 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Mon, 3 Aug 2026 17:28:04 +0800 Subject: [PATCH] fix rectification PostgreSQL birth dates --- docs/BUG_HISTORY.md | 6 +++--- frontend/src/lib/birth-time-intake-model.ts | 3 +++ frontend/tests/birth-time-intake.test.ts | 2 ++ frontend/tests/rectification-agentic-session.test.ts | 6 ++++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index a7462b3a..e7c313f7 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -2016,9 +2016,9 @@ - 影响面:`POST /api/rectification/agent`、首页账户资料重新加载、Session 自动恢复、`GET /api/account` 请求频率 - 用户现象:账户接口已返回完整出生日期、时间线索和地点,Agent opening 仍返回 `profile_incomplete`;页面随后重复请求 `/api/account` 和 `/api/rectification/agent`。部署首轮修复后,刷新页面还会错误回到“先完成出生资料”。 - 触发条件:数据库驱动把出生日期投影为 `YYYY-MM-DDT00:00:00.000Z`,同时当前活动 Session 是 `birth_time_rectification`。 -- 根因:Agentic profile loader 和首页 `readProfile()` 都把持久化日期直接交给只接受纯 `YYYY-MM-DD` 的资料完整性校验;失败回调清空 `rectificationSessionId` 却未设置现有的自动恢复暂停状态,resume effect 立即重新挂载聊天并再次发送 opening。 -- 修复:增加共享持久化日期规范化函数,由 Agent loader 与首页账户重新加载共同复用;服务端资料失败时先设置 `rectificationError` 暂停自动恢复,资料成功保存后再清除暂停状态。 -- 验证:回归覆盖数据库 ISO 日期在 Agent loader 与首页重新加载中的规范化、非法日历日期拒绝,以及 profile failure 在清空 Session 前设置自动恢复暂停状态。 +- 根因:Agentic profile loader 和首页 `readProfile()` 都把持久化日期直接交给只接受纯 `YYYY-MM-DD` 的资料完整性校验;首轮只兼容了账户 JSON 中的 ISO 字符串,但 staging 自托管 PostgreSQL 数据层在 Agent loader 内实际返回 JavaScript `Date`,因此服务端仍误判 `missing_birth_date`。失败回调清空 `rectificationSessionId` 却未设置现有的自动恢复暂停状态,resume effect 又会立即重新挂载聊天并再次发送 opening。 +- 修复:共享持久化日期规范化函数统一接受合法 `Date`、ISO 字符串和纯日期字符串,由 Agent loader 与首页账户重新加载共同复用;服务端资料失败时先设置 `rectificationError` 暂停自动恢复,资料成功保存后再清除暂停状态。 +- 验证:staging 只读诊断确认目标账户的 `birth_date` 在服务端为 `Date`,时间、地点和误差字段类型均有效;回归覆盖 PostgreSQL `Date`、数据库 ISO 日期、非法日期,以及 profile failure 在清空 Session 前设置自动恢复暂停状态;完整前端测试 1200/1200、lint 0 error、production build 通过。 - 防复发:数据库日期边界不得假设唯一 JavaScript 序列化形态;所有从账户持久化资料进入完整性校验的路径必须先走同一规范化函数;任何自动挂载请求的失败回调都必须先阻断对应的自动恢复条件。 - 相关记录:BUG-016、BUG-114 - 复发自:BUG-114 diff --git a/frontend/src/lib/birth-time-intake-model.ts b/frontend/src/lib/birth-time-intake-model.ts index b81572a4..71be1984 100644 --- a/frontend/src/lib/birth-time-intake-model.ts +++ b/frontend/src/lib/birth-time-intake-model.ts @@ -64,6 +64,9 @@ export function parseBirthDate(value: string): Date | undefined { } export function normalizePersistedBirthDate(value: unknown): string { + if (value instanceof Date) { + return Number.isNaN(value.getTime()) ? "" : value.toISOString().slice(0, 10); + } if (typeof value !== "string") return ""; const persisted = value.trim(); const date = persisted.slice(0, 10); diff --git a/frontend/tests/birth-time-intake.test.ts b/frontend/tests/birth-time-intake.test.ts index 69f81366..d044d61f 100644 --- a/frontend/tests/birth-time-intake.test.ts +++ b/frontend/tests/birth-time-intake.test.ts @@ -269,8 +269,10 @@ test("birth date values round trip leap days and reject invalid input", () => { }); test("persisted birth dates normalize database ISO values without accepting invalid dates", () => { + assert.equal(normalizePersistedBirthDate(new Date("1997-08-08T00:00:00.000Z")), "1997-08-08"); assert.equal(normalizePersistedBirthDate("1997-08-08T00:00:00.000Z"), "1997-08-08"); assert.equal(normalizePersistedBirthDate("1997-08-08"), "1997-08-08"); + assert.equal(normalizePersistedBirthDate(new Date("invalid")), ""); assert.equal(normalizePersistedBirthDate("1997-02-30T00:00:00.000Z"), ""); assert.equal(normalizePersistedBirthDate("1997-08-08junk"), ""); }); diff --git a/frontend/tests/rectification-agentic-session.test.ts b/frontend/tests/rectification-agentic-session.test.ts index 780d0ecd..7cc2a4b7 100644 --- a/frontend/tests/rectification-agentic-session.test.ts +++ b/frontend/tests/rectification-agentic-session.test.ts @@ -67,6 +67,12 @@ test("loadAgenticRectificationProfile normalizes a persisted ISO birth date", as assert.equal(profile.birth_date, "1997-08-08"); }); +test("loadAgenticRectificationProfile normalizes a PostgreSQL Date birth date", async () => { + const { client } = fakeAccounting(fakeProfileRow({ birth_date: new Date("1997-08-08T00:00:00.000Z") })); + const profile = await loadAgenticRectificationProfile(client as never, userId); + assert.equal(profile.birth_date, "1997-08-08"); +}); + test("loadAgenticRectificationProfile rejects an invalid persisted birth date", async () => { const { client } = fakeAccounting(fakeProfileRow({ birth_date: "1997-02-30T00:00:00.000Z" })); await assert.rejects(