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
co-authored by Cursor
parent 6ce4e67186
commit fd415e1d11
10 changed files with 254 additions and 27 deletions
+27
View File
@@ -304,3 +304,30 @@ export function resolveAccountBirthTimeApplicationPatch(
rectification_case_id: null,
};
}
export type AppliedAccountBirthTime = Readonly<{
status: string | null;
activeTime: string | null;
}>;
/**
* The birth-time truth the account owns after a successful write. Status and
* active minute are derived server-side, so a caller that keeps the declaration
* it submitted would consult under a mode the server no longer accepts.
*/
export function resolveAppliedAccountBirthTime(
current: AccountBirthTimeState | null,
applicationPatch: AccountBirthTimeApplicationPatch,
): AppliedAccountBirthTime {
const activeTime = normalizeApplicableBirthClock(
applicationPatch.active_birth_time !== undefined
? applicationPatch.active_birth_time
: current?.active_birth_time ?? current?.birth_time,
);
return Object.freeze({
status: applicationPatch.birth_time_status
?? current?.birth_time_status
?? (activeTime ? "confirmed" : null),
activeTime,
});
}
@@ -326,6 +326,31 @@ export function birthTimePersistenceValues(draft: BirthTimeDraft) {
};
}
const persistedBirthTimeStatuses = [
"reported", "assessing", "rectifying", "candidate", "accepted", "confirmed",
] as const satisfies readonly Exclude<BirthTimeStatus, "">[];
/**
* Reconciles a submitted declaration with the birth-time truth the account write
* returned. The server decides whether a declaration is already usable as the
* active minute, so consultation mode must never be derived from the draft alone.
*/
export function applyPersistedBirthTime<T extends BirthTimeDraft>(
draft: T,
applied: unknown,
): T {
if (applied === null || typeof applied !== "object") return draft;
const { status, activeTime } = applied as { status?: unknown; activeTime?: unknown };
const persistedStatus = persistedBirthTimeStatuses.find((candidate) => candidate === status);
if (!persistedStatus) return draft;
const clock = typeof activeTime === "string" ? activeTime.slice(0, 5) : "";
return {
...draft,
time: isBirthClockTime(clock) ? clock : "",
birthTimeStatus: persistedStatus,
};
}
export function describeBirthTimeDraft(draft: BirthTimeDraft) {
const [year, month, day] = draft.date.split("-").map(Number);
const date = `${year}${month}${day}`;