/** * Shared two-step commit for applying a worker's task result to the durable * remote task ledger (WFT-22) — used by both the WebSocket (`onTaskResultMessage`) * and long-poll (`handleTaskResultRequest`) completion paths. * * `Leased --> Completing --> Terminal` is committed as two separate durable * writes, not one, even though the caller already holds the full result: the * project brief models `Completing` as an observable intermediate state * specifically so a crash between the two writes leaves a durable, recoverable * marker ("Completing prevents visibility scanning or disconnect handling from * requeueing an attempt while its terminal result is being applied") that * WFT-23's recovery is designed to resume. Collapsing the two transitions into * one write would make that state unobservable and defeat the point of having * it. * * A submitter can itself resume that crash window: if the record is already * `completing` with the same `attemptToken` AND the same `pendingResultDigest` * — the worker retrying the identical result after a server crash between the * two writes, or a benign duplicate submission — this skips straight to * `commitTerminalResult` instead of rejecting a legitimate resubmission with * "expected task state leased". A `completing` record whose digest or token * differs is a genuinely different result and is rejected normally. * * On exhausted retries — specifically when the *second* write * (`commitTerminalFromCompleting`) exhausts its CAS retry budget after the * record is already durably `completing` — this attempts one best-effort * `Completing --> DeadLettered` write (WFT-24's `commitDeadLetter`, * `../task-ledger-transitions.ts`) so a sustained, operation-specific * storage write failure becomes an operator-visible dead letter instead of a * silently stuck record. If the *first* write (`beginCompletion`) exhausts * instead, the record never left `leased` and no dead letter is attempted — * the visibility scanner's ordinary expiry path already covers that case. * If the dead-letter write itself also fails, this falls back to the plain * `ok: false` result exactly as before WFT-24: the record stays `completing`, * a worker resubmitting the identical result can still resume through the * `resuming` branch above, and no data is lost. * * @module server/runtime/task-ledger-completion */ import type { Storage } from '../../storage/interface.ts'; import type { ServeOptions } from '../index.ts'; import { type RemoteTaskCompleting, type RemoteTaskDeadLettered, type RemoteTaskTerminal } from '../task-ledger.ts'; export type TaskLedgerCompletionInput = Readonly<{ operationId: string; attemptToken: string; status: 'completed' | 'failed'; value?: unknown; error?: string; }>; export type TaskLedgerCompletionResult = Readonly<{ ok: true; completing: RemoteTaskCompleting; terminal: RemoteTaskTerminal; }> | Readonly<{ ok: false; reason: string; deadLettered?: RemoteTaskDeadLettered; }>; /** * Dispatch {@link TaskResultDeadLetteredEvent} for a record `commitTaskLedgerCompletion` * dead-lettered. Shared by the WebSocket and long-poll completion paths so * both build the event from the same fields — `workflowId`/`activityName`/`queue` * come from the dead-lettered record itself (present on every `RemoteTaskBase`), * not from the caller's original request, so the event is accurate even if the * caller only had partial information. */ export declare function dispatchTaskDeadLetteredEvent(options: ServeOptions, operationId: string, deadLettered: RemoteTaskDeadLettered, workerId: string | undefined): void; export declare function commitTaskLedgerCompletion(storage: Storage, input: TaskLedgerCompletionInput): Promise;