UNPKG

workflow

Version:

Workflow SDK - Build durable, resilient, and observable workflows

79 lines (61 loc) • 3.99 kB
--- title: Durable hook resume description: resumeHook() durably writes hook_received and only then publishes the workflow wake, so a resolved call can never be lost to a disposal race. --- # Durable hook resume ## Motivation The previous lazy path published the serialized hook payload on the workflow queue and left the queue consumer to create `hook_received`. If the hook was disposed after `resumeHook()` returned but before the consumer write committed, that write was rejected and the acknowledged queue delivery could not resume the workflow: the caller was told the resume succeeded, and it was lost. `resumeHook()` now resolves only after both the durable event write and the workflow wake have succeeded, in that order. ## Design The dispatch is strictly serial: 1. The hook is resolved by token. An unknown token throws `HookNotFoundError`. 2. The producer writes `hook_received` durably into the run's event log. A client-minted `resumeId` and payload digest ride the write when the backend supports atomic resume claims, so transport-level retries of the same write converge on exactly one committed event. A write refused because the hook was disposed or the run ended throws `HookNotFoundError`. 3. Only after the write is acknowledged does the producer publish the workflow wake. The wake carries no payload — the payload lives in the event log — so nothing rides on the queue message but the trigger. Publication is retried a bounded number of times. Because the event is committed before the wake exists, a disposal or run completion racing the queue delivery cannot erase a resume the caller was told succeeded: the delivery replays the committed event from the log. - `ResumedHook.resilientResume` remains on the type for source compatibility and is no longer set. The internal `resumeHookDurable()` entry point is removed; `resumeHook()` itself now provides the durable guarantee. A resolved call proves that the event is durable and the wake was accepted. `HookNotFoundError` proves this invocation committed no event. Any other thrown error is ambiguous only in *dispatch*, never in durability: a wake failure after the write leaves the event committed, and any later wake of the run (from any source) delivers it. A fresh `resumeHook()` invocation mints a new `resumeId`, so blindly retrying a failed call can append a second `hook_received`; callers that need at-most-once behavior across separate invocations must deduplicate on their own request key. ## Behavior change: resumes against an ended run The lazy path never observed the server's rejection — it published a message and resolved, so a resume against a run that had already ended reported success (reachable whenever the hook record outlives its run, e.g. token retention). The durable write restores the check: **a resume against an ended run now throws `HookNotFoundError`**, and a late webhook delivery to a finished run answers 404 where it previously answered 202. Senders that treat 4xx as terminal will stop retrying such deliveries; that is the correct signal, since nothing can resume an ended run. A transient write conflict (HTTP 409, e.g. an event-slot conflict that escaped the server's internal retry budget under contention) is no longer re-keyed to `HookNotFoundError`. It surfaces as a retryable error, and its rejected transaction committed nothing, so retrying the resume is safe. ## Compatibility Nothing about the queue message changes: the wake has the same shape the sequential path always published, so no consumer, backend, or server coordination is needed and either side can roll back independently. Consumers continue to accept legacy `hookInput` messages from older producers, materializing their payload before replay. This permits rolling upgrades without a coordinated producer and consumer deployment. `WORKFLOW_DISABLE_LAZY_HOOK_RESUME` no longer gates anything and is ignored: there is no lazy path left to disable.