fix: expose dynamic scoring persistence

This commit is contained in:
Jesse_Chen
2026-07-19 07:50:23 +08:00
parent a8629043d8
commit 45b63d5cef
6 changed files with 430 additions and 7 deletions
@@ -5,6 +5,8 @@ import {
} from "./birth-time-journey-turn-protocol.ts";
import type { DynamicJourneyTurnState } from "./birth-time-journey-turn-protocol.ts";
import type {
DynamicScoringJobCommand,
DynamicScoringJobFailureCommand,
DynamicStoredRectificationCase,
LegacyStoredRectificationCase,
StoredRectificationCase,
@@ -68,6 +70,7 @@ function privateState(value: DynamicStoredRectificationCase): DynamicPrivateJour
function isStaleRpc(error: RpcError): boolean {
return error.message.includes("stale_birth_time_dynamic_turn")
|| error.message.includes("stale_birth_time_dynamic_scoring_job")
|| error.message.includes("stale_birth_time_legacy_upgrade");
}
@@ -84,6 +87,43 @@ export function createDynamicTurnPersistence(
return loaded;
}
async function savedDynamicScoring(
value: DynamicStoredRectificationCase,
expectedVersion: number,
result: RpcResult,
): Promise<DynamicStoredRectificationCase> {
if (result.error) {
if (isStaleRpc(result.error)) {
const current = await loadCase(value.userId, value.id);
throw new StaleJourneyTurnError(
value.id,
expectedVersion,
current?.turnVersion ?? 0,
);
}
throw new BirthTimeJourneyStoreError("update_case");
}
const version = rpcVersionSchema.safeParse(result.data);
if (!version.success || version.data !== expectedVersion + 1) {
throw new BirthTimeJourneyStoreError("update_case");
}
const loaded = await loadedDynamic(value.userId, value.id);
if (
loaded.turnVersion !== version.data
|| loaded.dynamicTurnState.turnVersion !== version.data
) throw new BirthTimeJourneyStoreError("load_case");
return loaded;
}
function scoringIdentity(command: DynamicScoringJobCommand) {
return {
p_job_id: command.jobId,
p_expected_version: command.expectedVersion,
p_evidence_fingerprint: command.evidenceFingerprint,
p_algorithm_version: command.algorithmVersion,
};
}
return {
async saveDynamicTurn(
value: DynamicStoredRectificationCase,
@@ -119,6 +159,38 @@ export function createDynamicTurnPersistence(
return loadedDynamic(value.userId, value.id);
},
async completeDynamicScoringJob(
value: DynamicStoredRectificationCase,
command: DynamicScoringJobCommand,
): Promise<DynamicStoredRectificationCase> {
if (!value.candidateResult) throw new BirthTimeJourneyStoreError("update_case");
const result = await client.rpc("complete_birth_time_dynamic_scoring_job", {
p_user_id: value.userId,
p_case_id: value.id,
...scoringIdentity(command),
p_public_turn_state: publicTurn(value, command.expectedVersion + 1),
p_snapshot: value.snapshot,
p_candidate_result: value.candidateResult,
p_private_state: privateState(value),
});
return savedDynamicScoring(value, command.expectedVersion, result);
},
async failDynamicScoringJob(
value: DynamicStoredRectificationCase,
command: DynamicScoringJobFailureCommand,
): Promise<DynamicStoredRectificationCase> {
const result = await client.rpc("fail_birth_time_dynamic_scoring_job", {
p_user_id: value.userId,
p_case_id: value.id,
...scoringIdentity(command),
p_failure_code: command.failureCode,
p_public_turn_state: publicTurn(value, command.expectedVersion + 1),
p_private_state: privateState(value),
});
return savedDynamicScoring(value, command.expectedVersion, result);
},
async upgradeLegacyActiveCase(
value: LegacyStoredRectificationCase,
): Promise<StoredRectificationCase> {
+13 -2
View File
@@ -133,16 +133,27 @@ export type LegacyStoredRectificationCase = StoredRectificationCaseBase
export type DynamicStoredRectificationCase = StoredRectificationCaseBase
& DynamicStoredFields;
export type StoredRectificationCase =
| LegacyStoredRectificationCase
export type StoredRectificationCase = LegacyStoredRectificationCase
| DynamicStoredRectificationCase;
export type DynamicScoringJobCommand = {
readonly expectedVersion: number;
readonly jobId: string;
readonly evidenceFingerprint: string;
readonly algorithmVersion: string;
};
export type DynamicScoringJobFailureCommand = DynamicScoringJobCommand
& { readonly failureCode: string };
export interface BirthTimeJourneyStore {
saveAssessment(value: PersistedJourneyAssessment): Promise<string>;
loadCase(userId: string, caseId: string): Promise<StoredRectificationCase | null>;
saveScoring(value: LegacyStoredRectificationCase): Promise<void>;
saveTurn(value: LegacyStoredRectificationCase, expectedVersion: number, actionId: string): Promise<StoredRectificationCase>;
saveDynamicTurn(value: DynamicStoredRectificationCase, expectedVersion: number, actionId: string): Promise<DynamicStoredRectificationCase>;
completeDynamicScoringJob(value: DynamicStoredRectificationCase, command: DynamicScoringJobCommand): Promise<DynamicStoredRectificationCase>;
failDynamicScoringJob(value: DynamicStoredRectificationCase, command: DynamicScoringJobFailureCommand): Promise<DynamicStoredRectificationCase>;
upgradeLegacyActiveCase(value: LegacyStoredRectificationCase): Promise<StoredRectificationCase>;
createScoringJob(value: LegacyStoredRectificationCase, expectedVersion: number, actionId: string, job: ScoringJobSpec): Promise<StoredRectificationCase>;
claimScoringJob(identity: ScoringJobIdentity): Promise<ScoringJobClaim>;