Files
Jyotisha/frontend/src/lib/birth-time-dynamic-candidate-confirmation.ts
T
2026-07-20 14:58:57 +08:00

77 lines
3.3 KiB
TypeScript

import { withConfirmedCandidate } from "./birth-time-evidence.ts";
import { replayedDynamicAction } from "./birth-time-dynamic-action-replay.ts";
import { withDynamicAction } from "./birth-time-dynamic-transitions.ts";
import { BirthTimeDynamicActionError } from "./birth-time-dynamic-actions.ts";
import { storedDynamicJourneyResponse } from "./birth-time-journey-response.ts";
import type {
BirthTimeJourneyPorts,
DynamicCandidateConfirmationCommand,
DynamicStoredRectificationCase,
} from "./birth-time-journey-service.ts";
import { StaleJourneyTurnError } from "./birth-time-journey-store-errors.ts";
function stale(stored: DynamicStoredRectificationCase, expectedVersion: number) {
return new StaleJourneyTurnError(stored.id, expectedVersion, stored.turnVersion);
}
export function createDynamicCandidateConfirmation(ports: BirthTimeJourneyPorts) {
return {
async confirm(input: DynamicCandidateConfirmationCommand) {
const stored = await ports.store.loadCase(input.userId, input.caseId);
if (!stored) throw new BirthTimeDynamicActionError("case_not_found");
if (stored.journeyProtocol !== "dynamic-choice-v2") {
throw new BirthTimeDynamicActionError("invalid_turn");
}
const receipt = stored.dynamicControl.lastActionReceipt;
if (replayedDynamicAction(stored, input.actionId, input.expectedVersion, () => (
stored.dynamicTurnState.nextAction.kind === "ready"
&& stored.dynamicTurnState.nextAction.activeTime === input.time
&& stored.snapshot.activeTime === input.time
&& receipt?.kind === "confirm_candidate"
&& receipt.actionId === input.actionId.toLowerCase()
&& receipt.turnVersion === input.expectedVersion
&& receipt.resultId === input.resultId
&& receipt.time === input.time
))) return storedDynamicJourneyResponse(stored);
const action = stored.dynamicTurnState.nextAction;
const candidate = stored.candidateResult;
if (
stored.turnVersion !== input.expectedVersion
|| action.kind !== "request_candidate_confirmation"
|| action.resultId !== input.resultId
|| !stored.dynamicTurnState.permissions.canConfirmCandidate
|| candidate?.resultId !== input.resultId
|| candidate.canApply !== true
|| candidate.winningSegment?.representativeTime !== input.time
) throw stale(stored, input.expectedVersion);
const transitioned = withDynamicAction(
stored,
{ kind: "ready", activeTime: input.time },
input.expectedVersion + 1,
);
const updated = {
...transitioned,
snapshot: withConfirmedCandidate(stored.snapshot, candidate, input.time),
currentChoiceQuestion: null,
dynamicTurnState: {
...transitioned.dynamicTurnState,
permissions: { canConfirmCandidate: false },
},
dynamicControl: {
...stored.dynamicControl,
lastActionReceipt: {
actionId: input.actionId.toLowerCase(),
kind: "confirm_candidate" as const,
turnVersion: input.expectedVersion,
resultId: input.resultId,
time: input.time,
},
},
} satisfies DynamicStoredRectificationCase;
return storedDynamicJourneyResponse(await ports.store.confirmDynamicCandidate(updated, input));
},
};
}