/** * Pure conditional-transition functions for the durable application command * mailbox (WFT-84) — one function per legal edge of the mailbox state machine. * * These functions are storage-agnostic. They take the currently decoded * {@link ApplicationCommandRecord} and proposed inputs and return either the * next record to persist or a stable rejection reason. They never read or write * storage and never see encoded bytes. * * That last point matters for correctness, not just tidiness. * `storage.conditionalBatch` compares whole values byte-for-byte, and * `encode(decode(bytes))` is not guaranteed to reproduce the original bytes. The * caller that commits a transition must keep the raw `Uint8Array` it read and * pass those exact bytes as the compare-and-swap `expectedValue`, using * `next` only for what to write. * * The legal edges are: * * ```text * (none) --admit--> accepted | available * accepted --release(now>=due)--> available * accepted --claim(now>=due)--> claimed * available --claim--> claimed * claimed --acknowledge--> applied * claimed --reject(final)--> rejected * claimed --reject(retry)--> accepted (backoff, attempts remain) * claimed --reject(retry)--> dead-lettered (attempts exhausted) * claimed --expire--> accepted | dead-lettered * claimed --cancel--> cancellation-requested * cancellation-requested --settle--> cancelled * cancellation-requested --expire--> cancelled (cleanupPending) * accepted | available --cancel--> cancelled * any non-terminal --deadline--> dead-lettered * ``` * * @module core/mailbox-transitions */ import type { JSONValue } from './json.ts'; import { type MailboxTransition } from './mailbox-transition-helpers.ts'; import type { ApplicationCommandAccepted, ApplicationCommandAvailable, ApplicationCommandCancelling, ApplicationCommandClaimed, ApplicationCommandFailure, ApplicationCommandLeasedRecord, ApplicationCommandRecord, ApplicationCommandTerminalRecord, ApplicationCommandWaitingRecord } from './mailbox-types.ts'; import { type ValidatedCommandInput } from './mailbox-validation.ts'; export { computeRetryBackoffMs, isTerminalCommandRecord, nonTerminalCommandRecord, } from './mailbox-transition-helpers.ts'; export type { MailboxTransition, MailboxTransitionRejection, } from './mailbox-transition-helpers.ts'; /** * Build the record for a freshly admitted command. * * A command with no delay is admitted straight to `available`; one with a delay * is admitted `accepted` and released later. Both are durable receipts. */ export declare function createAdmittedCommandRecord(input: ValidatedCommandInput, context: { readonly namespace: string; readonly resourceId: string; readonly commandId: string; readonly sequence: number; readonly now: number; }): ApplicationCommandAccepted | ApplicationCommandAvailable; /** * Release a delayed command for delivery once its `availableAt` has passed. */ export declare function releaseWaitingCommand(record: ApplicationCommandRecord, now: number): MailboxTransition; /** * Lease a waiting command to one attempt. * * The lease never outlives the absolute command deadline: `visibilityExpiresAt` * is clamped to it, so an attempt cannot hold work past the ceiling admission * set. */ export declare function claimWaitingCommand(record: ApplicationCommandRecord, options: { readonly now: number; readonly attemptToken: string; }): MailboxTransition; /** * Extend a lease and record liveness for the current attempt. * * Renewal is attempt-fenced and clamped: it can never move * `absoluteDeadlineAt`, so an indefinitely renewing claimant still hits the * command ceiling. `lastActivityAt` is transport liveness and stays separate * from `progress`, which is caller-supplied semantic progress and is never used * for fencing. */ export declare function renewCommandLease(record: ApplicationCommandRecord, options: { readonly attemptToken: string; readonly now: number; readonly progress?: JSONValue | undefined; }): MailboxTransition; /** * Settle a claimed command successfully. * * A command whose cancellation was already requested settles as `cancelled` * rather than `applied`: the claimant finished cleanup, so cleanup is settled, * but the durable cancellation request is not overwritten by a success. * * The caller's `outcome` is still retained on that `cancelled` receipt. A * claimant that completed the work before it observed the cancellation really * did produce a result, and discarding it would lose evidence a reader may need * to decide whether the effect already happened. */ export declare function acknowledgeCommand(record: ApplicationCommandRecord, options: { readonly attemptToken: string; readonly now: number; readonly outcome?: JSONValue | undefined; }): MailboxTransition; /** * Settle a claimed command as failed, optionally scheduling a retry. * * A retry that still has attempts left returns the command to `accepted` at its * original FIFO position with a backoff; one that does not is dead-lettered * with `attempts-exhausted`. A cancellation-requested command always settles as * `cancelled` — a failed cleanup is still cleanup. */ export declare function rejectCommand(record: ApplicationCommandRecord, options: { readonly attemptToken: string; readonly now: number; readonly retry: boolean; readonly failure: ApplicationCommandFailure; readonly retryBackoffMs: number; readonly maxRetryBackoffMs: number; }): MailboxTransition; /** * Record a durable cancellation request. * * An unclaimed command cancels immediately with nothing to clean up. A claimed * one keeps its lease and moves to `cancellation-requested`, so only the * current attempt can settle it. Requesting cancellation twice is idempotent: * the second request rejects with `not-leased` against a record already in * `cancellation-requested`, which the caller reports as the same outcome. */ export declare function requestCommandCancellation(record: ApplicationCommandRecord, options: { readonly now: number; readonly reason?: string | undefined; }): MailboxTransition; /** * Whether a waiting or leased record is past its absolute command deadline and * must be terminalized before anything else can happen to it. */ export declare function isCommandPastDeadline(record: ApplicationCommandRecord, now: number): boolean; /** Narrowing helper for callers that already proved a record is waiting. */ export declare function asWaitingRecord(record: ApplicationCommandRecord): ApplicationCommandWaitingRecord | null;