/** * The controller one execution suspends through. * * A durable wait is two facts that must not be confused. The document has * published a request — that is journal state, and `suspendFor()` owns it. The * *execution* must now end without a Close, its executor lock held until every * finalizer has run, and the run settled `suspended`. That second half is * lifecycle ownership, and it belongs to whoever holds the lock. * * So this controller is the seam between them, and it is deliberately narrow: * it reports a wait to the lock owner and then does not return. It halts * nothing, settles nothing and writes nothing. The owner observes the notice as * one settlement candidate beside a canonical outcome and a foreground * interruption, and the owner is what tears the execution down. * * ## Why the operation never returns * * With no answer available — and #367 delivers none — there is nothing to * return, and returning anything would resume a document past a wait that never * ended. Raising would be worse: an ordinary error is something a document may * catch, and a caught suspension would leave a run executing past its own * suspension request. Remaining pending is what makes the halt the only way out * of the wait, and the halt is what leaves the root without a Close. * * ## The route composes; the position authorizes * * The controller is reached through a stable contextual name, so a component * carrying its own loaded copy of this package finds the controller the running * binary installed. That name is composition: middleware may refuse it for its * descendants, and nothing it returns is an answer. * * What authorizes entry is the retained request at the caller's exact current * durable position. Another durable operation can reproduce that request and * arrive there — replay identity is a type and a name, both public — and when it * does, it is standing at the same validated wait rather than at one of its own. * * There is deliberately nothing to hold and nothing to present. A capability * object has to be reachable to be used, and in this runtime anything reachable * by name is reachable by anyone who knows the name — which is selection, not * ownership. Position is not like that: a caller cannot stand somewhere it is * not. */ import { type Operation } from "effection"; import { type WorkflowSuspensionRequest } from "../suspension/api.js"; import type { WorkflowRunDatabase } from "../storage/api.js"; /** What one execution reported it is waiting for. */ export interface SuspensionNotice { readonly suspensionId: string; readonly request: WorkflowSuspensionRequest; } export interface SuspensionControllerOptions { /** * The run whose retained request at the caller's position admits a wait. * * Read here rather than trusted from the caller: the identifier presented has * to be the one this run derives for the position immediately behind the * caller's own, and the yield there has to be that request. */ readonly database: WorkflowRunDatabase; } export interface SuspensionController { /** Run `operation` as the execution this controller owns. */ own(operation: Operation): Operation; /** Settles once this execution reports a durable wait. */ readonly notice: Operation; /** Whether this execution reported a durable wait. */ reported(): boolean; /** * Whether this is the marker that ends a waiting execution. * * The marker leaves through the same path any other failure would, so a * finalizer that raises on the way out replaces it — which is exactly the * precedence a suspension needs. An execution that reported a wait and left * carrying something else did not reach that wait cleanly. */ entered(error: unknown): boolean; } export declare function createSuspensionController(options: SuspensionControllerOptions): SuspensionController;