/** * The ONE table-driven step-emission scaffold, shared by both orchestrators. * * Both entry points used to hand-repeat the same four-part shape once per * branch — resolve the step's inputs, build a prompt, write the step contract, * log the written step and return — so a change to the emit contract had to be * applied in every branch by hand with no compiler signal when one was missed. * This module removes the repetition structurally: a branch contributes a * HANDLER that returns a PLAN (what to emit), and this scaffold owns the single * emission call site that turns a plan into a written, logged step exactly once. * * Shaped from the outset for BOTH shapes: * • `emit(key, ctx)` — branch-per-step-key dispatch (the audit entry point's * `result.kind` chain), with a fallback for a key the table does not carry; * • `emitFirstApplicable(keys, ctx)` — the numbered early-return gates (the * contract pipeline's shape): each gate handler returns a plan to emit, or * `null` to fall through to the next gate, and the fallback closes the walk. * Both funnel through `emitPlan`, which no caller can bypass without adding a * second emission site — the thing the single-call-site test refuses. * * `handledKeys` is derived from the table's OWN KEYS, never a hand-listed * literal beside it, so a drift guard can import a real set instead of * reconstructing one by reflection over a branch chain. * * Provider/host/OS-agnostic: the scaffold knows nothing about what a step IS. * `write` and `log` are supplied by the adopting orchestrator, so the writer * (a step-contract writer, a blocked-step writer, a delegated renderer) and the * transport (stdout today) stay entirely on the adopter's side. */ /** Produce the plan for the step to emit. Never writes or logs — that is the scaffold's. */ export type StepEmissionHandler = (ctx: TCtx) => TPlan | Promise; /** * A gate handler: the plan to emit, or a DECLINE. * * THE DECLINE PREDICATE, stated once for both entry points: a handler declines * by returning `null` OR `undefined` (`plan == null`). Keyed dispatch and the * ordered gate walk read it identically — an `undefined`-returning handler must * not be treated as "emit nothing to write" by one and "decline" by the other. * A declining keyed handler falls through to the fallback; a declining gate * hands on to the next gate. */ export type StepGateHandler = (ctx: TCtx) => TPlan | null | undefined | Promise; export interface StepEmissionScaffold { /** The table's own keys. Derived — never a second, hand-listed copy. */ readonly handledKeys: ReadonlySet; /** Dispatch one step key; an unhandled key (or a `null` plan) takes the fallback. */ emit(key: string, ctx: TCtx): Promise; /** Walk gates in order; the first non-`null` plan is emitted, else the fallback. */ emitFirstApplicable(keys: readonly string[], ctx: TCtx): Promise; /** * Emit a plan the table did not produce. For a path that runs BEFORE any step * key exists (a config-load failure that must still leave a fresh step * contract on disk): it is deliberately not a second emission site — it is * the same one, reached with a hand-built plan. */ emitPlan(plan: TPlan): Promise; } export interface StepEmissionScaffoldOptions { /** Step key → handler. The key set IS the handled-kinds set. */ table: Readonly>>; /** Reached when no row handled the key (or no gate applied). */ fallback: StepEmissionHandler; /** Turn a plan into the written step. The ONLY writer the scaffold calls. */ write: (plan: TPlan) => TStep | Promise; /** Announce the written step (stdout contract). Called exactly once per emission. */ log: (step: TStep) => void; } export declare function createStepEmissionScaffold(options: StepEmissionScaffoldOptions): StepEmissionScaffold; //# sourceMappingURL=stepEmissionScaffold.d.ts.map