import type { CommandEffectDescriptor, CommandEffectClassification, } from "../../contracts/external-contract-runtime.ts"; /** * §8.7–§8.9. Mutation effect certainty. * * The frontend never infers "not applied" from an HTTP status alone. Anything * observed after the request was dispatched but before a classified terminal * response is `MAYBE_APPLIED`, which forbids automatic resend. */ export type MutationEffectCertainty = | "NOT_STARTED" | "NOT_APPLIED" | "MAYBE_APPLIED" | "APPLIED_CONFIRMED"; export type PhysicalAttemptState = | "NOT_STARTED" | "PREPARING" | "READY_TO_SEND" | "DISPATCHED" | "RESPONSE_HEADERS" | "READING_BODY" | "VALIDATING" | "MAPPING_READY" | "SETTLED"; /** * `READY_TO_SEND` is recorded immediately before entering the `fetch()` * invocation expression and `DISPATCHED` immediately after the promise is * returned. A synchronous throw therefore leaves the attempt `NOT_STARTED`. */ export function certaintyForAbandonedAttempt( state: PhysicalAttemptState, isCommand: boolean, ): MutationEffectCertainty { if (!isCommand) return "NOT_STARTED"; switch (state) { case "NOT_STARTED": case "PREPARING": case "READY_TO_SEND": return "NOT_STARTED"; default: return "MAYBE_APPLIED"; } } /** * §8.7. The conservative certainty lattice for one logical execution. * * `PhysicalAttemptState` describes only the attempt in flight. A new retry that * has not been sent yet must never lower what an earlier attempt already * established, so the executor joins observations into a monotonic accumulator. */ const CERTAINTY_RANK: Readonly> = Object.freeze({ NOT_STARTED: 0, NOT_APPLIED: 1, MAYBE_APPLIED: 2, APPLIED_CONFIRMED: 3, }); export function joinMutationEffectCertainty( current: MutationEffectCertainty, observed: MutationEffectCertainty, ): MutationEffectCertainty { return CERTAINTY_RANK[observed] > CERTAINTY_RANK[current] ? observed : current; } export type ProblemEffectInput = Readonly<{ status: number; problem: Problem; descriptor: CommandEffectDescriptor | null; }>; export type ProblemEffectOutcome = Readonly<{ effect: MutationEffectCertainty; contractRuntimeFailure: boolean; }>; const CLASSIFICATIONS: ReadonlySet = new Set([ "NOT_APPLIED", "APPLIED_CONFIRMED", "MAYBE_APPLIED", ]); /** * §4.4. The classifier is package-owned and pure. A throw or an unrecognised * return value fails safe to `MAYBE_APPLIED` and is recorded as a contract * runtime failure rather than being silently treated as "not applied". */ export function classifyProblemEffect( input: ProblemEffectInput, ): ProblemEffectOutcome { if (!input.descriptor) { // A read operation carries no command effect; there is nothing to apply. return Object.freeze({ effect: "NOT_STARTED" as const, contractRuntimeFailure: false, }); } let classification: CommandEffectClassification; try { classification = input.descriptor.classifyProblem({ status: input.status, problem: input.problem, }); } catch { return Object.freeze({ effect: "MAYBE_APPLIED" as const, contractRuntimeFailure: true, }); } if (!CLASSIFICATIONS.has(classification)) { return Object.freeze({ effect: "MAYBE_APPLIED" as const, contractRuntimeFailure: true, }); } return Object.freeze({ effect: classification, contractRuntimeFailure: false, }); } /** §8.10. Certainty to UI intent. The copy itself is owned by the i18n catalog. */ export function projectCertaintyToUi( certainty: MutationEffectCertainty, ): "RETRYABLE" | "CHECK_STATUS" | "SUCCESS" { switch (certainty) { case "APPLIED_CONFIRMED": return "SUCCESS"; case "MAYBE_APPLIED": return "CHECK_STATUS"; default: return "RETRYABLE"; } }