/** * `resolveExecutableRegistration()` — the single async helper every * execution entry point that can launch or resume a workflow * (`start`/`startOrSignal`/`schedule`/`fork`/`resume`/recovery/bulk-retry) * funnels through to turn a workflow `type` into a `RegistrationEntry` it * can actually run (WFT-15/16). * * Fast path: an eagerly `engine.register()`-ed type resolves synchronously * from `internals.registrations` and never touches `internals.sources` — * this is what makes "starting the eager workflow does not import the lazy * module" true. Slow path: a `engine.registerSource()`-registered type is * resolved via `resolveWorkflowSourceForExecution()`, which awaits the * loader (single-flight, shared with any other concurrent caller for the * same key) and installs the result durably before this returns — so no * workflow handler ever runs while its definition is still a catalog * candidate. * * @module core/engine/dynamic-source-execution */ import type { Engine } from './index.ts'; import type { EngineInternals } from './internals.ts'; type RegistrationEntry = EngineInternals['registrations'] extends Map ? Entry : never; /** The outcome of {@link resolveExecutableRegistration}. */ export type ExecutableRegistration = { entry: RegistrationEntry; /** The dynamic source revision resolved, or `undefined` for an eager registration. */ revision: string | undefined; }; /** * Turn workflow `type` into an executable {@link RegistrationEntry}, awaiting * dynamic-source resolution when `type` is not eagerly registered. Throws * {@link WorkflowNotRegisteredError} when `type` is neither an eager * registration nor a registered source (the pre-existing error — see the * call site below for why); {@link DynamicWorkflowSourceUnavailableError} * when a registered source's target revision is ambiguous or its load * fails; {@link EngineDisposedError} unwrapped when the engine is disposed * during resolution. */ export declare function resolveExecutableRegistration(engine: Engine, internals: EngineInternals, type: string, onRevisionChosen?: (revision: string) => void): Promise; /** * The shared classification `resolveExecutableRegistrationForRevision()`'s * throw-vs-proceed decision and the ADR-0002 workflow-lease reclaim- * eligibility gate (`index.ts`'s `isWorkflowTypeRegistered`) both delegate * to, so the two decisions can never drift out of sync — an engine that * cannot locally resolve a run's exact pin must never win that run's claim * either (see `workflow-claim-reclaim-target.ts`'s own doc for why that * would strand the run under a permanently-failing `onReclaimed` drive). * * - An eager registration is always resolvable, regardless of `revision` * (eager has no ambiguity to pin against). * - `type` with no eager registration and no registered source at all is * never resolvable. * - A dynamic source with `revision === undefined` (legacy/pre-pinning) is * resolvable only when it has exactly one registered candidate — * unambiguous by construction. Two or more is ambiguous; zero is * impossible (an empty `byRevision` map does not exist — the type * wouldn't be in `sources.byName` at all). * - A dynamic source with a defined `revision` is resolvable exactly when * that revision is among this process's currently registered candidates. */ export declare function canResolveRevisionLocally(internals: EngineInternals, type: string, revision: string | undefined): boolean; /** * Resolve workflow `type` against its EXACT pinned `revision` — the * `WorkflowState.revision` a run persisted at start (WFT-17) — instead of * whichever revision the catalog currently considers active. This is the * resolver `resumeWorkflowFromStorage()` (`lifecycle/resume.ts`) uses for * EVERY resume (both `recoverAll()`'s batch and a standalone * `engine.resume(id)`), so "recovery never falls back from a missing exact * revision to the active revision" (WFT-17/WFT-18's acceptance criterion) * holds uniformly, not just inside the batch preflight. * * Classification: * - An eager registration is always resolved from `internals.registrations` * regardless of `revision` — eager has no ambiguity to pin against (the * process runs whatever code it has loaded), so a stale or absent pin on * an eager type is harmless and must not block the common redeploy path. * - A dynamic source with `revision === undefined` (a legacy, pre-pinning * record) falls through to the ordinary {@link resolveExecutableRegistration} * active-pointer resolution when the type has at most one registered * candidate (unambiguous by construction), or throws * {@link WorkflowRevisionUnavailableError} with `reason: 'legacy-ambiguous'` * when it has two or more (there is no way to tell which one the run * actually started against). * - A dynamic source with a defined `revision` throws * {@link WorkflowRevisionUnavailableError} with `reason: 'not-registered'` * when that exact revision is not among this process's registered * candidates (covers both "never registered at all" and "was the sole * candidate, but a different one is registered now") — otherwise it is * loaded and installed exactly like {@link resolveExecutableRegistration}'s * own dynamic path. * - `type` with neither an eager registration nor any registered source * throws the pre-existing {@link WorkflowNotRegisteredError}, matching * {@link resolveExecutableRegistration}. * * `onRevisionChosen` (WFT-21, Codex review round 5, P1) is forwarded, * unchanged, to {@link resolveExecutableRegistration} for the * `revision === undefined` legacy path ONLY — the same synchronous, * before-any-await hook that function already documents. A caller resolving * a fork's legacy (pre-revision-pinning) source run needs this to reserve * an `inFlightStartsByRevision` slot the INSTANT the sole candidate's * revision is chosen, not after this whole async call returns: reserving * only afterward (as `fork()` did through round 4) leaves the resolver's * own await — loading the source, when not already cached locally — as a * window where a concurrent `removeWorkflowRevision()` can observe zero * references, delete and finalize that sole candidate, and then have this * same resolution's `loadAndInstallSourceRevision()` silently reinstall it * via `catalog.install()`, papering over a removal that already reported * success. The `revision !== undefined` (pinned) path never invokes this * hook — a caller resolving a KNOWN revision already has it synchronously * up front and can reserve before ever calling this function, exactly as * `fork()`'s own early `reserveInFlightStart(internals, sourceState.type, * targetRevision)` already does. */ export declare function resolveExecutableRegistrationForRevision(engine: Engine, internals: EngineInternals, type: string, revision: string | undefined, onRevisionChosen?: (revision: string) => void): Promise; /** * Wraps `callbacks.resolveExecutableRegistration(type)`, remapping ONLY the * "no source registered at all" case (a {@link WorkflowNotRegisteredError}) * to `notFoundError` — a caller's own, more-specific "no workflow * registered" message (naming the workflow ID it was * resuming/forking/retrying, for example) instead of the generic one. Every * other error (including a source found but unresolvable) propagates * unwrapped. Shared by `resume.ts`, `transition.ts`'s `fork()`, and * `bulk-operations.ts`'s retry path so each keeps a single, low-complexity * call site instead of its own try/catch. */ export declare function resolveExecutableRegistrationOrRenamedNotFound(resolve: (type: string) => Promise, type: string, notFoundError: () => Error): Promise; /** * {@link resolveExecutableRegistrationOrRenamedNotFound}, pre-bound to * `bulk-operations.ts`'s retry-failed message shape. Resolves against the * failed run's own EXACT pinned `revision` (`WorkflowState.revision`, WFT-17) * via {@link resolveExecutableRegistrationForRevision} — never the catalog's * active pointer — so a bulk retry's pre-reactivation concurrency-admission * lookup agrees with the exact-revision resolve the subsequent * `engine.resume()` performs. Resolving against the active pointer here * instead would let a retry commit the failed->running reactivation batch * against the WRONG registration's `concurrency` config, and then — if the * pinned revision is unavailable while a different revision is active — * only discover that after the reactivation already committed, leaving the * run stranded `running` with no generator ever resuming. Kept here (with * the bulk of the logic off `bulk-operations.ts`, already at its own * oxlint `max-lines` ceiling) rather than inlined at that one call site. */ export declare function resolveExecutableRegistrationForRetry(internals: EngineInternals, type: string, revision: string | undefined, workflowId: string): Promise; /** * Sync-only fallback lookup for PER-INSTANCE call sites that run after a * workflow is already executing (or is being swept from storage) and must * never trigger a new resolve — finalizer, constraint, retention, and * search-attribute-schema resolution (`termination/finalizer-registration.ts`, * `constraints.ts`, `workflow-retention-deadline.ts`, `listing.ts`). Reads * the eager registration first (an eager type resolves the same regardless * of `revision` — see {@link canResolveRevisionLocally}'s doc). Otherwise: * * - A defined `revision` reads `internals.sources.resolved.get(type)?.get(revision)` * directly — the running instance's OWN exact pin — with NO fallback to a * different revision when that exact one is not locally resolved (returns * `undefined` instead, matching {@link resolveExecutableRegistrationForRevision}'s * "never silently substitute a different revision" contract). * - `revision === undefined` (a legacy pre-pinning record) falls back to * `internals.sources.lastResolvedRevisionByName` ONLY when * {@link canResolveRevisionLocally} classifies that as unambiguous — * exactly one registered candidate for `type`. With two or more * registered candidates this is genuinely ambiguous for a SPECIFIC * instance (WFT-19 review round 6, Codex, P1): silently substituting * whichever sibling revision this process last resolved could validate * `setAttributes()` against a sibling's schema, run a sibling's * finalizer, or — worst, since purge is irreversible — purge a run early * under a sibling's shorter retention window. Returns `undefined` in that * case instead, forcing the caller's async fallback * (`resolveExecutableRegistrationForRevision()`) to reach the * `legacy-ambiguous` classification and fail closed the same way it * already does for resume/recovery. {@link resolveLastKnownDynamicRegistration} * is the deliberate exception for the one genuinely TYPE-level caller * that wants the permissive last-resolved-wins answer even when * ambiguous. * * Returns `undefined` when no source has a matching entry. */ export declare function getResolvedDynamicRegistration(internals: EngineInternals, type: string, revision: string | undefined): RegistrationEntry | undefined; /** * TYPE-level, sync-only "whatever this process last resolved" lookup — * used ONLY by `retention.ts`'s `resolveWorkflowTypeRetention()`, an * across-all-registered-types OVERVIEW API (`getRetentionOverview()`) with * no single running instance to pin against, unlike every caller of * {@link getResolvedDynamicRegistration} above. Deliberately does NOT fail * closed when 2+ candidates are registered for `type`: for an overview, * showing the last-resolved candidate's policy is a more useful signal * than showing nothing, and — unlike `getResolvedDynamicRegistration`'s * per-instance callers — no irreversible action (purge, finalizer run, * schema validation) hangs off this call (WFT-19 review round 6, Codex). */ export declare function resolveLastKnownDynamicRegistration(internals: EngineInternals, type: string): RegistrationEntry | undefined; export {};