/** * Shared obligation-engine primitives — the single source for how BOTH * orchestrators express and select ordered obligations, so the engine * vocabulary cannot drift between audit-code and remediate-code (A3). * * An *obligation* is one named unit of progress carrying a precomputed * satisfaction `state`. The engine owns only the ordered *selection* (the scan * below); each orchestrator derives its own obligation states — audit-code from * the artifact-staleness DAG, remediate-code from persisted status + sidecar * files — and maps the selected obligation to an executor. * * This module is A3's seed: it centralizes the vocabulary + the priority scan * that audit-code already had and remediate-code re-derived inside an imperative * cascade. The richer transition/emit advance loop (needed to absorb * remediate-code's internally-recursive control flow) is added here when * remediate-code adopts the engine, so the API is proven by a real consumer * rather than designed in a vacuum. See `spec/a3-a4-engine-unification-plan.md`. */ import { z } from "zod"; /** * Satisfaction state of a single ordered obligation. `missing` and `stale` are * the *actionable* states the scan selects on; `present`, `satisfied`, and * `blocked` are non-actionable. */ export declare const ObligationStateSchema: z.ZodEnum<["missing", "present", "stale", "blocked", "satisfied"]>; export type ObligationState = z.infer; /** A single ordered obligation carrying its precomputed satisfaction state. */ export declare const ObligationSchema: z.ZodObject<{ id: z.ZodString; state: z.ZodEnum<["missing", "present", "stale", "blocked", "satisfied"]>; reason: z.ZodOptional; }, "strict", z.ZodTypeAny, { id: string; state: "blocked" | "missing" | "satisfied" | "present" | "stale"; reason?: string | undefined; }, { id: string; state: "blocked" | "missing" | "satisfied" | "present" | "stale"; reason?: string | undefined; }>; export type Obligation = z.infer; /** * Return the first obligation — in `priority` order — that is actionable * (`missing` or `stale`), or `undefined` when every listed obligation is * satisfied / non-actionable. Obligations carry their precomputed `state`; the * engine owns only this ordered scan so the selection cannot drift between * callers. * * Generic over `T extends Obligation` so callers keep their domain obligation * type (e.g. audit-code's `AuditObligation`) as the return type. Ids in * `priority` with no matching obligation are skipped; obligations whose id is * absent from `priority` are never selected (priority is the authority on order * *and* membership). */ export declare function findFirstActionableObligation(priority: readonly string[], obligations: readonly T[]): T | undefined; /** * A *definition* of an ordered obligation for the transition/emit `advance` loop * below — distinct from the precomputed-state `Obligation` *value* the bare * `findFirstActionableObligation` scan consumes. A definition is an id plus two * functions: * * - `derive(state)` computes the obligation's current satisfaction state from the * orchestrator state. It stays orchestrator-specific: audit-code reads its * artifact-staleness DAG; remediate-code reads persisted status + sidecar-file * existence. Only `missing`/`stale` are actionable. * - `execute(state, ctx)` performs the one bounded unit of work and returns an * `ObligationOutcome` — either a `transition` (state advanced; the loop re-scans * within the same call) or an `emit` (a host-actionable step; the loop returns * it). * * Generic over `S` (orchestrator state), `Ctx` (per-orchestrator execution * dependencies — the engine stays agnostic; each orchestrator picks its own `Ctx` * rather than the engine imposing a union) and `Step` (the host-actionable step * type). */ export interface ObligationDef { id: string; derive(state: S): ObligationState; execute(state: S, ctx: Ctx): Promise>; } /** * The result of executing an obligation. * * - `transition`: the state advanced (in place or replaced); `advance` re-scans * without a host round-trip. This is the generalization over the bare scan that * absorbs remediate-code's internally-recursive cascade (e.g. * planning→implementing→re-scan folded into one call). * - `emit`: a host-actionable step; `advance` stops and returns it. `state` * carries the (optionally mutated) state to persist alongside the step — omit it * when the executor left the state unchanged. */ export type ObligationOutcome = { kind: "transition"; state: S; } | { kind: "emit"; step: Step; state?: S; }; /** An engine instance: an ordered `priority` + the obligation definitions. */ export interface ObligationEngine { priority: readonly string[]; obligations: readonly ObligationDef[]; } /** * Derive every obligation's state from `state` and return the first actionable * definition in `priority` order, or `undefined` when none is actionable. Reuses * the single `findFirstActionableObligation` scan so the ordered-selection * semantics (priority is the authority on order *and* membership; only * missing/stale are actionable) cannot drift from the bare-scan callers. The * engine itself does no IO — any IO lives inside each obligation's `derive`. */ export declare function findNextObligation(priority: readonly string[], obligations: readonly ObligationDef[], state: S): ObligationDef | undefined; /** * Backstop on consecutive transitions inside `advance` — catches a never-clearing * (cyclic) transition obligation. Far above any legitimate transition chain (the * deepest real remediate-code fold is a handful of transitions per call). */ export declare const DEFAULT_MAX_TRANSITIONS = 100; /** * Headroom between a consumer's own GRACEFUL step cap and the engine bound * derived from it. Stated HERE, once, because the relationship between the two * numbers is a property of the engine's contract, not of any one consumer: a * consumer that stops its fold at its own cap must be able to prove the engine * bound cannot fire first, and it can only do that if the two are derived from * a single formulation rather than written independently. * * The value is the slack the engine needs to observe a consumer's final, * cap-spending step and the emit that follows it. */ export declare const ENGINE_TRANSITION_HEADROOM = 2; /** * The engine bound DERIVED from a consumer's graceful cap — the second half of * the bounded-call invariant this module owns. * * Consumers call this instead of restating a constant, so raising a graceful * cap can never silently push the fold past the engine bound: there is no * second number anywhere to remember to re-derive. */ export declare function deriveEngineBound(cap: number): number; /** * The outcome of an `advance` run. * * - `step` non-null → an obligation emitted a host-actionable step; `state` is the * state to persist alongside it. * - `step` null, `stopped` undefined → no obligation is actionable: the run is * complete. * - `step` null, `stopped: "cycle"` → a transition revisited an already-seen state * signature, so the fold is not converging; the caller surfaces a graceful * terminal rather than looping. Only possible when `opts.stateSignature` is * supplied. * - `step` null, `stopped: "bound"` → the fold spent `maxTransitions` * transitions without reaching an emit or completion. Also non-convergence, * detected by counting rather than by signature. * * `stopped` being ABSENT is what means "the run is complete". A caller that * branches on `step` alone therefore cannot tell completion from * non-convergence, and would report a wedged fold as a finished one — so both * stopped values must be handled explicitly. `lastObligationId` names the * obligation the fold was executing when it stopped, so a caller can say WHICH * obligation is spinning without parsing it out of a message. */ export interface AdvanceResult { state: S; step: Step | null; stopped?: "cycle" | "bound"; /** The obligation in flight when `stopped` was set; absent otherwise. */ lastObligationId?: string; } /** * Drive the engine from `state`: repeatedly select the highest-priority actionable * obligation and execute it. A `transition` outcome advances the state and the * loop re-scans within the same call (one host round-trip can fold through several * transitions); an `emit` outcome stops the loop and returns the host-actionable * step. When no obligation is actionable the run is complete and `step` is `null`. * * **Cycle termination.** A transition obligation that never clears its own * actionable state would loop forever. Two backstops: * - `opts.stateSignature(state)` (preferred) records the signature of every state * the loop scans from; a transition landing on an already-seen signature — * including a *no-progress* transition that leaves the signature unchanged, or a * multi-obligation A→B→A state cycle — stops the loop with `stopped: "cycle"`. * This is the precise cycle condition ("a transition revisited a state already * scanned this run") that the blunt count only approximates, and it terminates * *gracefully* (the caller renders a terminal) instead of throwing. It also * handles non-monotonic folds (e.g. audit-code's selective deepening grows the * work-set before it shrinks): each distinct round is a new signature, so only a * genuine revisit stops it. * - `maxTransitions` is the absolute backstop for callers that supply no * signature — it stops the loop with `stopped: "bound"` after that many * consecutive transitions. * * BOTH backstops terminate GRACEFULLY. The bound used to throw, which forced * every consumer to recognize a wedged fold by matching text in an error * message and to recover the spinning obligation's id with a regex over that * same prose — so the engine's bound was part of its contract while its only * signal was a sentence. A structured stop lets a consumer pause resumably at * the bound the way it already pauses for any other non-actionable outcome. * * `emit` and natural completion both terminate the loop and are never bounded. * * This is a strict generalization of the bare scan: an engine whose obligations * only ever `emit` stops after exactly one unit (audit-code's emit-only, * host-looped contract); `transition` outcomes add the in-call folding both * orchestrators use to avoid host round-trips on deterministic pass-throughs. */ export declare function advance(engine: ObligationEngine, state: S, ctx: Ctx, opts?: { maxTransitions?: number; stateSignature?: (state: S) => string; }): Promise>; //# sourceMappingURL=obligationEngine.d.ts.map