/** * Thin interpret driver — Phase 1 (#556), saga rollback hardened in Phase 4 * (#565, epic #551). * * One generic orchestrator that runs an arbitrary set of components on the * local in-process executor: it orders components by `dependsOn` (reusing the * same Kahn-layering approach `computeStackGraph` uses for `chant graph * --stacks`, see ../build.ts), then for each component in order runs its * `deploy` composition by dispatching every step to the `CapabilityRegistry` * by `kind`. It resolves the three wiring reference forms from * component.schema.json (`@Phase.field` prior-step references, * `@.publish.` cross-component artifact references, and * passes through `$env.*`/`stackOutput` values as opaque env config), runs * `parallel` phases concurrently, rejects a `gate` locally (matching * ../op/local-executor.ts's `LocalGateUnsupportedError`), and on terminal * failure unwinds every executed step in reverse via that step's capability * `rollback`, then runs `onFailure` phases in reverse order (best-effort), * mirroring the Op local executor's saga semantics. * * A step whose capability declares no `rollback` is never silently passed * over during unwind: it gets a `"rollback-opted-out"` record (see * `rollbackExecuted`), carrying the step's `noRollback` reason when the * composition declared one. This mirrors, at run time, the opt-out the * COMP003 lint rule (../lint/rules/comp/comp003-mutating-no-rollback.ts) * already requires at author time — the driver reports the same fact the * lint rule already made a component author state. * * This unwind runs entirely in-process: if the process crashes mid-rollback * on the local executor, nothing resumes it — there is no persisted run state * to resume from. That is the documented Temporal boundary (see * docs/components/orchestration.mdx#rollback-comes-free and * docs/guide/local-vs-temporal.mdx): the *mechanism* (reverse-order unwind * calling each capability's `rollback`) is capability-agnostic and works * identically on both executors, but *durable resume of a rollback already in * progress* is Temporal-only, the same boundary that already applies to * forward `onFailure` compensation on an Op. * * Contains zero per-component logic: nothing in this module branches on a * component or capability `kind`/name. All behavior is either generic * (ordering, phase/step dispatch, wiring resolution) or delegated to the * `CapabilityRegistry` the caller supplies. */ import type { CapabilityRegistry, DeployContext } from "./capability.js"; import type { RunProgressEvent } from "./run-progress.js"; export type { RunProgressEvent } from "./run-progress.js"; /** A wiring reference or literal value, as it appears in a step's fields (schema `WiringValue`). */ export type WiringValue = string | { stackOutput: { stack: string; name: string; }; } | Record; /** A single capability invocation — the leaf unit of a composition (schema `Step`). */ export interface DriverStep { kind: string; [param: string]: unknown; } /** A gate step — pauses for an external signal; unsupported on the local executor (schema `Gate`). */ export interface DriverGate { kind: "gate"; signalName: string; timeout?: string; description?: string; } /** One named phase of a deploy composition (schema `Phase`). A step may itself be a nested `Phase` (fan-out). */ export interface DriverPhase { phase: string; steps: Array; parallel?: boolean; onFailure?: DriverPhase[]; } /** The subset of the Component contract the driver needs to run a deploy (schema-shaped; see component.schema.json). */ export interface DriverComponent { name: string; dependsOn?: string[]; deploy: DriverPhase[]; /** Component-level saga compensation, run in reverse order on terminal failure (schema `rollback`). */ rollback?: DriverPhase[]; } /** Thrown when a component's composition contains a `gate` — gates need a durable runtime, matching chant's Op local executor. */ export declare class DriverGateUnsupportedError extends Error { readonly component: string; readonly signalName: string; constructor(component: string, signalName: string); } /** Thrown when a dependency cycle is found among `dependsOn` edges. */ export declare class DependencyCycleError extends Error { readonly cycle: string[]; constructor(cycle: string[]); } /** Thrown when a component's `dependsOn` names a component not present in the run set. */ export declare class UnknownDependencyError extends Error { readonly component: string; readonly dependency: string; constructor(component: string, dependency: string); } /** Thrown on terminal run failure; carries the partial run result for rendering/inspection. */ export declare class DriverRunFailure extends Error { readonly result: DriverRunResult; constructor(result: DriverRunResult); } export interface DriverStepRecord { component: string; phase: string; kind: string; status: "ok" | "fail" | "skipped" | "rolled-back" | "rollback-opted-out"; durationMs: number; output?: unknown; error?: string; } export interface DriverComponentResult { component: string; ok: boolean; records: DriverStepRecord[]; } export interface DriverRunResult { /** Component order actually attempted, in run order. */ order: string[]; /** Parallel-safe waves the order was derived from (see resolveComponentGraph). */ waves: string[][]; results: DriverComponentResult[]; ok: boolean; /** Name of the component that terminated the run, if any. */ failedComponent?: string; /** * The accumulated cross-component/cross-stack outputs after the run — each * component's `publish` output and, for an applied stack, its `cfn-deploy` * outputs, keyed by component name. Seeded from `options.componentOutputs` * and grown as components complete. The CLI's `--dump-outputs` serializes * this so a later, separate run (a downstream CI job) can `--seed-outputs` * it and resolve `stackOutput()`/`@.publish.*` references to a * component that ran in an earlier job. */ componentOutputs: Record>; } export interface ComponentGraph { /** Flat topological order — every dependency before its dependents. */ order: string[]; /** Waves: components in the same wave share no dependency and may run concurrently. */ waves: string[][]; } /** * Resolve run order and parallel-safe waves from each component's `dependsOn`. * Reuses the generic `topoSort` (../codegen/topo-sort.ts) for the flat order, * and the same Kahn-layering approach `computeStackGraph` uses for `chant * graph --stacks` (../build.ts) to additionally group independent components * into waves. This is a plain string-graph algorithm — no lexicon/AttrRef * machinery — since a component's `dependsOn` is already a flat name list. */ export declare function resolveComponentGraph(components: DriverComponent[]): ComponentGraph; /** * Resolve one wiring value against already-produced outputs. Three reference * forms, all resolved here rather than by any component-specific code: * - `@Phase.field` — a prior step's output within the same component, keyed * by phase name (`phaseOutputs`). * - `@.publish.uri|digest|key` — another component's published * artifact output (`componentOutputs`), per composition-and-wiring.mdx. * - `{ stackOutput: { stack, name } }` — a cross-stack output; Phase 1 has no * cross-stack apply-order integration (out of scope per #556), so this * resolves via the same `componentOutputs` map keyed by `stack`/`name`, * letting tests and callers seed cross-stack values the same way as * artifact outputs. `$env.*` and plain literals pass through unchanged — * env resolution is the caller's `DeployContext.vars`, not the driver's. */ export declare function resolveWiring(value: WiringValue, phaseOutputs: Record>, componentOutputs: Record>): unknown; /** * Deep-walk an object, resolving every string/stackOutput wiring value found. * Arrays and nested objects are walked too. * * Exported (not just used internally) so the durable Temporal path * (`@intentius/chant-lexicon-temporal`'s component workflow codegen, see * epic #551 #589) can resolve a step's wiring the same way inside a Temporal * *activity* — the workflow itself only accumulates `phaseOutputs`/ * `componentOutputs` and passes them through; resolution logic stays in one * place so local and durable execution can never silently diverge. */ export declare function resolveStepInput(input: Record, phaseOutputs: Record>, componentOutputs: Record>): Record; /** * Run one component's `deploy` composition. On terminal failure: unwind every * executed step in reverse via its capability's `rollback` (saga * compensation), then run the component's declared `rollback` phases in * reverse order (best-effort), matching ../op/local-executor.ts's `onFailure` * handling. Cross-component artifact outputs this component published (if * any) are recorded into `componentOutputs` under its own name so downstream * components can reference `@.publish.*`. * * `onProgress`, when supplied, is forwarded to every `runPhase` call (both the * forward `deploy` phases and, on failure, the component's own authored * `rollback` phases) so a `--progress-json` consumer sees `phase-start`/ * `step`/`phase-done` events for whichever phases actually ran. The saga * unwind step-by-step compensation (`rollbackExecuted` below) is not part of * the `RunProgressEvent` contract and stays silent — it isn't a `deploy` * phase, and its record statuses (`rolled-back`/`rollback-opted-out`) don't * map onto the `step` event's `running`/`ok`/`failed` shape. */ export declare function runComponentDeploy(component: DriverComponent, ctx: DeployContext, registry: CapabilityRegistry, componentOutputs: Record>, onProgress?: (event: RunProgressEvent) => void): Promise; /** * Collect the cross-component outputs one finished component exposes to its * downstream consumers, from every phase output it produced: * * - publish-family outputs (publish-image / publish-artifact / * load-image-on-host all return at least one of uri/digest/key — see * ./verbs/publish.ts) are namespaced under `publish`, so downstream * `@.publish.*` references resolve; * - stack outputs from an apply step (cfn-deploy returns * `CfnDeployOutput.outputs`) are merged at the top level, peer to * `publish`, so `resolveWiring`'s stackOutput branch — * `resolvePath(componentOutputs[stack], name)` — finds each output by * name (the cross-stack apply-order integration deferred in #556, closed * in #699). The entry is keyed by the component's own name, which by * convention is the stack name a `stackOutput` reference targets (see the * pilots and composition-and-wiring.mdx). * * Both halves are generic by output *shape*, not by capability `kind`, keeping * the driver free of per-capability branching. Returns `undefined` when the * component exposed nothing. * * Exported, like `resolveStepInput`, so the durable Temporal path * (lexicons/temporal/src/component-op/activities.ts) accumulates outputs via * this exact function rather than re-deriving it (#700). The resolver and the * accumulator are the two halves of one contract; sharing only the resolver * is how the cross-stack gap #699 closed locally could reopen durably. */ export declare function collectComponentOutputs(phaseOutputs: Record>): Record | undefined; /** * Record a finished component's outputs (`collectComponentOutputs`) into the * shared `componentOutputs` map under the component's own name, merging over * any seeded entry (`--seed-outputs`, or a durable parent workflow's * thread-through). Mutates and returns `componentOutputs`, so a Temporal * activity can hand the updated map back to its workflow over the JSON * boundary — every value in it is plain activity-result data, so the map is * serializable by construction. A no-op when the component exposed nothing. */ export declare function accumulateComponentOutputs(componentOutputs: Record>, componentName: string, phaseOutputs: Record>): Record>; export interface InterpretRunOptions { /** Target environment name, threaded into every capability's `DeployContext`. */ env: string; /** Environment config resolved ahead of the run (registry URLs, cluster names, ...) — passed through as `DeployContext.vars`. */ vars?: Record; /** Pre-seeded cross-component/cross-stack outputs (e.g. from a prior run, or a caller resolving `stackOutput` externally). Merged with outputs this run produces. */ componentOutputs?: Record>; /** * Opt-in structured progress observer (`chant run --components all * --progress-json`, see ./run-progress.ts). Called with `run-start`/ * `wave-start`/`component-start`/…/`run-done` events as the run executes; * never consulted for control flow, so leaving it `undefined` (the default * for every caller that didn't pass `--progress-json`) makes every * `onProgress?.(...)` call below a no-op and this function's behavior is * byte-for-byte the same as before this option existed. */ onProgress?: (event: RunProgressEvent) => void; } /** * Run a set of components to completion on the local in-process executor: * resolve dependency order and parallel-safe waves from `dependsOn`, then run * each wave's components (independent components within a wave run * concurrently; components across waves run in wave order), dispatching * every step to `registry` by `kind`. Stops the whole run at the first failed * component (after that component's own saga rollback completes), returning * a result with `ok: false`; throws `DriverRunFailure` carrying that result * so callers can choose to inspect or propagate it. * * Zero per-component logic: this function and everything it calls dispatches * purely on the generic `Component`/`Phase`/`Step` shapes and the registry — * no branch anywhere names a specific component or capability. */ export declare function runInterpretDriver(components: DriverComponent[], registry: CapabilityRegistry, options: InterpretRunOptions): Promise; //# sourceMappingURL=driver.d.ts.map