Merge pull request #79 from jesse-ux/codex/rectification-profile-completeness

fix(rectification): accept persisted birth dates
This commit is contained in:
jesse-ux
2026-08-03 16:16:04 +08:00
committed by GitHub
5 changed files with 54 additions and 2 deletions
+16
View File
@@ -2007,3 +2007,19 @@
- 相关记录:BUG-112、BUG-113
- 复发自:BUG-113
- 修复版本:待本次 staging 修复提交与部署验收
## BUG-115 | ISO 出生日期被 Agentic 资料门误判并触发 opening 重试循环
- 状态:resolvedlocal
- 首次发现:2026-08-03
- 最近更新:2026-08-03
- 影响面:`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 只接受纯 `YYYY-MM-DD`;失败回调清空 `rectificationSessionId` 却未设置现有的自动恢复暂停状态,resume effect 立即重新挂载聊天并再次发送 opening。
- 修复:在共享 profile loader 边界提取并复用现有日历日期校验;服务端资料失败时先设置 `rectificationError` 暂停自动恢复,资料成功保存后再清除暂停状态。
- 验证:回归覆盖 ISO 日期规范化、非法日历日期拒绝,以及 profile failure 在清空 Session 前设置自动恢复暂停状态。
- 防复发:数据库日期边界不得假设唯一 JavaScript 序列化形态;任何自动挂载请求的失败回调都必须先阻断对应的自动恢复条件。
- 相关记录:BUG-016、BUG-114
- 复发自:BUG-114
- 修复版本:待本次 staging 修复提交与部署验收
+2
View File
@@ -1816,6 +1816,7 @@ export default function Home() {
await persistProfile(profileDraft);
setProfile(profileDraft);
setProfileDraft(profileDraft);
setRectificationError("");
if (declarationChanged) {
setBirthTimeConsultationConsent(createBirthTimeConsultationConsentState());
setAccount((current) => current ? { ...current, rectificationCase: null } : current);
@@ -2073,6 +2074,7 @@ export default function Home() {
};
function handleRectificationProfileIncomplete() {
setRectificationError("profile_incomplete");
setRectificationSessionId(null);
setRectificationPendingQuestion(null);
const missingStep = missingProfileStep(profile);
@@ -1,5 +1,6 @@
import type { SupabaseClient } from "@supabase/supabase-js";
import type { AgenticRectificationContext } from "@/mastra/rectification-tools";
import { parseBirthDate } from "../birth-time-intake-model.ts";
/**
* Agentic rectification session support.
@@ -133,8 +134,10 @@ export async function loadAgenticRectificationProfile(
.single();
if (error || !data) throw new AgenticRectificationProfileError("profile_unavailable");
const birthDate = typeof data.birth_date === "string" ? data.birth_date.trim() : "";
if (!/^\d{4}-\d{2}-\d{2}$/.test(birthDate)) {
const persistedBirthDate = typeof data.birth_date === "string" ? data.birth_date.trim() : "";
const birthDate = persistedBirthDate.slice(0, 10);
if (!parseBirthDate(birthDate)
|| (persistedBirthDate.length > 10 && persistedBirthDate[10] !== "T")) {
throw new AgenticRectificationProfileError("missing_birth_date");
}
const activeTime = timeValue(data.active_birth_time);
@@ -41,3 +41,20 @@ test("incomplete profiles stay in the shared onboarding flow", () => {
);
assert.match(chat, /payload\?\.code === "profile_incomplete"/);
});
test("server profile failures pause automatic rectification resume", () => {
const resumeEffect = page.slice(
page.indexOf('useEffect(() => {\n if (!hydrated'),
page.indexOf('useEffect(() => {\n if (!hydrated || !accountId'),
);
const incompleteHandler = page.slice(
page.indexOf("function handleRectificationProfileIncomplete"),
page.indexOf("async function draftSynastryQuestionFromChart"),
);
assert.match(resumeEffect, /\|\| rectificationError\) return/);
assert.match(incompleteHandler, /setRectificationError\("profile_incomplete"\)/);
assert.ok(
incompleteHandler.indexOf('setRectificationError("profile_incomplete")')
< incompleteHandler.indexOf("setRectificationSessionId(null)"),
);
});
@@ -61,6 +61,20 @@ test("loadAgenticRectificationProfile derives birth fields, accuracy and baselin
assert.equal(profile.baselineActiveTime, "14:31");
});
test("loadAgenticRectificationProfile normalizes a persisted ISO birth date", async () => {
const { client } = fakeAccounting(fakeProfileRow({ birth_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(
() => loadAgenticRectificationProfile(client as never, userId),
(error) => error instanceof AgenticRectificationProfileError && error.code === "missing_birth_date",
);
});
test("loadAgenticRectificationProfile treats hospital source as minute accuracy", async () => {
const { client } = fakeAccounting(fakeProfileRow({
birth_time_source: "hospital",