import { z } from "zod"; import type { RelayChannel } from "./identity_relay.js"; import type { SuspendSignal } from "./suspension.js"; /** First retry delay after a suspend_abort (doubles per attempt, capped below). */ export declare const ABORT_RETRY_INITIAL_MS = 30000; export declare const ABORT_RETRY_MAX_MS: number; /** What one satisfied wait resolves with — the per-kind payload, opaque to the coordinator. */ declare const wakeEntrySchema: z.ZodObject<{ kind: z.ZodEnum<{ sleep: "sleep"; human_input: "human_input"; workflow_call: "workflow_call"; }>; answers: z.ZodOptional>; child: z.ZodOptional>; }, z.core.$strip>; /** The wake value inside a wake injection — `kind` echoes the parked seam's reason; the * per-kind fields are opaque to the coordinator and interpreted by the seam. */ export declare const wakeValueSchema: z.ZodObject<{ kind: z.ZodEnum<{ sleep: "sleep"; human_input: "human_input"; workflow_call: "workflow_call"; }>; answers: z.ZodOptional>; child: z.ZodOptional>; satisfied: z.ZodOptional; answers: z.ZodOptional>; child: z.ZodOptional>; seq: z.ZodNumber; }, z.core.$strip>>>; }, z.core.$strip>; export type WakeValue = z.infer; export type WakeEntry = z.infer; /** The wake-injection payload as init relays it (snake_case, the platform env contract's * sibling): fresh tokens — the frozen ones expired while suspended — plus the * authoritative wall clock (the guest's own clock was stopped) and the wake value. */ export declare const wakePayloadSchema: z.ZodObject<{ run_token: z.ZodString; api_token: z.ZodOptional; wall_clock_ms: z.ZodNumber; wake: z.ZodObject<{ kind: z.ZodEnum<{ sleep: "sleep"; human_input: "human_input"; workflow_call: "workflow_call"; }>; answers: z.ZodOptional>; child: z.ZodOptional>; satisfied: z.ZodOptional; answers: z.ZodOptional>; child: z.ZodOptional>; seq: z.ZodNumber; }, z.core.$strip>>>; }, z.core.$strip>; }, z.core.$strip>; export type WakePayload = z.infer; /** What a suspending wait resolves to: the wake (the normal path), or — for a `sleep` seam * only — the abort that tells it to hold its remainder in-process (other reasons retry the * freeze internally and never surface an abort). */ export type FreezeOutcome = { kind: "wake"; wake: WakeValue; } | { kind: "aborted"; reason: string; } /** The caller withdrew the wait via its abort signal BEFORE it froze (register-without-release: * a held gate got its answer during the hold, so it resolves in-process instead of freezing). */ | { kind: "withdrawn"; }; export interface FreezeCoordinatorHooks { /** Runs at quiescence, immediately before the freeze is requested: flush billable runtime * (suspended time must never appear billed) and persist the workspace. Its own failure * aborts THIS freeze attempt (the seam holds/retries) — never the run. */ onBeforeFreeze?: () => Promise; /** Announce the park to the run's event stream — the LAST thing before the freeze request, once * the wait set is final (a wait can withdraw during onBeforeFreeze's flush, so the set isn't * known until then). Runs here, not inside onBeforeFreeze, because the announcement has to be * ON THE WIRE before the VM can pause: the host may snapshot the instant it sees the request. * Exactly one of `onAfterWake` / `onFreezeAborted` follows every call. Best-effort — a * telemetry failure must never hold up a suspension, so its errors are swallowed here. */ onSuspend?: (waits: readonly SuspendSignal[]) => Promise; /** Runs when a wake lands, before the seam resolves: swap the run/api tokens onto the * broker client and rebase the runtime meter past the frozen window. */ onAfterWake?: (wake: WakePayload) => void | Promise; /** Runs when a REQUESTED freeze aborts (`suspend_abort` — snapshot/store failure) before the * parked seam resolves: undo onBeforeFreeze's reversible effects (resume the paused runtime * flusher) since the run now HOLDS in-process instead of freezing. Not called for a failure * inside onBeforeFreeze itself — that hook owns its own unwind. Must not throw. */ onFreezeAborted?: () => void; } export interface FreezeCoordinatorDeps { channel: RelayChannel; now?: () => number; /** Injected delay (tests). Defaults to a real timer. */ delay?: (ms: number) => Promise; /** Yield until every already-queued continuation has run — see {@link FreezeCoordinator.drive}. * Defaults to a macrotask hop, which is where the microtask queue is drained. */ drain?: () => Promise; } export declare class FreezeCoordinator { private readonly deps; private readonly now; private readonly delay; private readonly drain; private hooks; /** Non-suspending runtime work in flight (the quiescence gate's count). */ private inFlight; private quiescenceWaiters; /** True from freeze request until wake/abort — new runtime work queues behind it. */ private freezePending; private gateWaiters; /** Every registered-but-unsettled suspending wait, keyed by its suspension seq. */ private readonly waits; /** Resolves the driver's park — non-null from suspend_request until wake/abort. */ private frozen; /** True while the freeze driver loop is running (at most one). */ private driving; constructor(deps: FreezeCoordinatorDeps); /** Late-bound per-run hooks (the flusher/broker/redactor exist only once a run is claimed). */ setHooks(hooks: FreezeCoordinatorHooks): void; /** * Run one unit of non-suspending runtime work under the gate: queue while a freeze is * pending (the work never starts, so nothing can be torn by the pause), then count it * in-flight so a suspending wait holds until it drains. The gate check and the count * increment share one synchronous segment — a freeze requested in between cannot miss us. */ trackWork(fn: () => Promise): Promise; /** A suspending seam's own wait is NOT "work" — it decrements around its park so the gate * sees true quiescence. (The host wraps every hook in trackWork; the suspending seams * call these around their freeze wait.) */ beginWork(): void; endWork(): void; /** * Register a suspending wait and resolve it when the platform says its condition is met. * The next thing this promise sees is a wake (possibly epochs later, through a restored * heap) or — for `sleep` only — the abort that tells it to hold its remainder (other * reasons retry the freeze internally). Concurrent waits COMPOSE: one freeze covers every * registered wait, and each resolves on its own condition, in whatever order the platform * satisfies them. */ suspendingWait(signal: SuspendSignal, abort?: AbortSignal): Promise; /** Relay handler: a wake injection landed. Validates, runs the after-wake hook (token * swap + meter rebase), confirms to init, and hands the payload to the driver, which * routes each entry to the wait that asked for it. A wake with nothing frozen is a * duplicate delivery — re-confirm (idempotent), never crash. */ onWake(payload: unknown): void; /** Relay handler: the snapshot attempt failed; the parked seams fall back to holding. */ onSuspendAbort(payload: unknown): void; /** Drop a wait whose caller withdrew before the freeze was REQUESTED. Once requested the * process is paused (or about to be) and the abort is moot — the wake resolves it instead. */ private withdraw; private settle; /** Settle every outstanding `sleep` wait with a freeze failure: a sleep holds its remainder * in-process rather than retrying (its deadline is absolute, so holding still lands on time). */ private settleSleeps; private startDriver; /** * The single freeze loop. Holds until quiescence, closes the gate, runs the pre-freeze hook, * requests ONE freeze covering every registered wait, and parks. Each wake settles the waits * it satisfies; anything still outstanding re-freezes on the next pass. Exits when no waits * remain (a later wait restarts it). */ private drive; /** Hand each satisfied condition to the wait that asked for it. Returns how many settled — * zero means the wake matched nothing outstanding (the driver backs off rather than * re-freezing into a loop). */ private routeWake; private awaitQuiescence; private releaseGate; /** The host-readable wake summary on the wire (logs/metrics/placement): the primary wait's * condition, plus the whole set when this freeze composes several. The opaque broker_signal * beside it carries the authoritative conditions. */ private wakeConditionOf; private summaryOf; /** The broker's suspend body: the primary wait inline (what a control plane that predates * composed waits persists) plus `waits` — every condition this freeze must be woken for, * which is the authoritative set. */ private brokerSignalOf; } export {};