Status: COMPLETED
This planning document describes the state machine engine implementation. All milestones (M0-M4) have been completed. The state machine is now the default engine, and
CheckExecutionEngineinsrc/check-execution-engine.tsis a compatibility wrapper that re-exportsStateMachineExecutionEngine.Note: Line references in section 1 ("Current Engine Map") are historical and refer to the legacy implementation that has since been refactored into
src/state-machine/. Seesrc/state-machine/runner.tsandsrc/state-machine/states/for the current implementation.
This note captures the current execution flow of the Visor engine, the surfaces where a feature flag can live, and an initial proposal for a bespoke state-machine-based Runner v2. The user-provided research artifact referenced in the request is not available inside this workspace yet, so there are a few open questions that we should fill in once we can read it.
Safety Model & Criticality (Summary)
The engine follows a NASA‑style safety model. Every check participates in a safety policy that depends on its criticality:
- criticality (field on each check): external | internal | policy | info
- external: mutates outside world (e.g., GitHub ops, HTTP methods ≠ GET/HEAD)
- internal: alters routing/fan‑out (forEach parents, on_* with goto/run, memory used by guards)
- policy: enforces permissions/policy (strong fail_if/guarantee)
- info: read‑only compute
Defaults derived from criticality:
- Critical (external/control‑plane/policy): require
assume(preconditions) andguarantee(postconditions);continue_on_failure: false; retries only for transient faults; tighter loop budgets; suppress downstream side‑effects when contracts fail.- Non‑critical: contracts recommended; may allow
continue_on_failure: true; standard budgets and retry bounds.Expressions apply at distinct phases:
if(plan-time scheduling) →assume(pre‑exec) → provider →guarantee/fail_if(post‑exec) → transitions/goto.See also: docs/guides/fault-management-and-contracts.md for the full checklist and examples.
CheckExecutionEnginewires together the git analyzer, provider registry, failure evaluator, snapshot journal, memory store, telemetry, and GitHub clients (src/check-execution-engine.ts:228-332).- Per-run state is stored in multiple mutable maps/sets such as
forwardRunGuards,executionStats,outputHistory,runCounters, andjournal;resetPerRunStatemust be called before each grouped execution to avoid leakage (src/check-execution-engine.ts:258-306). executeChecksis the legacy CLI-style entry point that still routes through the PR reviewer after a repo scan; it loads memory, tags, GitHub checks, and eventually callsexecuteReviewChecks(src/check-execution-engine.ts:3645-3815).
- Modern callers (CLI, GitHub Action, SDK, test runner) invoke
executeGroupedCheckswhich handles tag/event filtering, debug visualization and GitHub context capture (src/check-execution-engine.ts:3964-4250). executeGroupedCheckschooses between three paths:executeDependencyAwareCheckswhen every requested check has config (src/check-execution-engine.ts:3976-3986).executeSingleGroupedCheckwhen exactly one configured check remains (src/check-execution-engine.ts:4291-4470).- Provider fallbacks (
src/check-execution-engine.ts:3990-4079) or reviewer fallback for legacy “focus” runs.
- GitHub Action mode adds grouped comment posting and per-check CheckRun updates via
GitHubCheckService.
executeDependencyAwareChecksorchestrates “waves” of checks:- Expand the requested set with transitive dependencies, event/tag filters, and session reuse metadata (
src/check-execution-engine.ts:5132-5320). - Build/validate the dependency graph using
DependencyResolver(src/check-execution-engine.ts:5321-5368,src/dependency-resolver.ts:1-150). - Maintain
wavecounters that reschedule plan execution whenever routing (on_fail,on_finish,goto) requests a forward run. A wave resets dedupe state and replays the DAG (src/check-execution-engine.ts:5384-5440). - For each topological level, spawn tasks up to
maxParallelism, honoring session reuse barriers, fail-fast, and debug pauses (src/check-execution-engine.ts:5442-6188). - After each pass, inspect flags like
onFailForwardRunSeen/onFinishForwardRunSeenand rebuild the graph if goto changed the event or dependency set (src/check-execution-engine.ts:6824-7072).
- Expand the requested set with transitive dependencies, event/tag filters, and session reuse metadata (
- Supporting helpers:
executeWithLimitedParallelismimplements the generic async task pool (src/check-execution-engine.ts:3861-3926).shouldFailFastinspects issue severities to stop the pool early (src/check-execution-engine.ts:8942-8958).
runNamedCheckis the central dispatcher used by dependency levels, routing hooks, and inline executions. It evaluatesifguards, enforces per-scope run caps, callsexecuteCheckInline, evaluatesfail_if, records stats/history, and schedules routing targets (src/check-execution-engine.ts:1516-2050).executeCheckInlineensures dependencies are materialized (recursively running missing ancestors), handles forEach fan-out, commits results into the execution journal, and records output history (src/check-execution-engine.ts:1047-1513).scheduleForwardRunis the goto implementation that replans subsets of the DAG, filtering by event triggers and respecting per-run dedupe guards (src/check-execution-engine.ts:412-520).on_finishorchestration is split intorunOnFinishChildren,decideRouting, andcomputeAllValidhelpers undersrc/engine/on-finish/*. The main engine wires them up after all forEach items and dependents settle, reusing snapshot projections for context (src/check-execution-engine.ts:2200-2350).shouldRunCheck(and theFailureConditionEvaluator) powerif,fail_if, and manual gating for forEach dependents (src/check-execution-engine.ts:4801-4870).
MemoryStoreand helper functions provide scriptable scratch storage for checks and on_finish contexts (see injections incomposeOnFinishContext,src/check-execution-engine.ts:900-1010andsrc/engine/on-finish/utils.ts:1-160).ExecutionJournal/ContextViewcapture per-scope results for snapshot-based dependency evaluation; journals are reset each grouped run (src/check-execution-engine.ts:170-210andsrc/snapshot-store.ts).- Telemetry emitters (
emitNdjsonSpanWithEvents, OTEL spans,addEvent) are sprinkled throughrunNamedCheck, forEach loops, and failure evaluators. - GitHub Check integration wires into
executeChecksandexecuteGroupedChecksto create/update CheckRuns and annotate output (src/check-execution-engine.ts:3670-3780,src/github-check-service.ts).
- Engine state is scattered across mutable maps/sets, which makes it hard to reason about transitions (e.g.,
forwardRunGuards,postOnFinishGuards,gotoSuppressedChecks). - Waves are implicit booleans rather than explicit states; nested goto/forward-run flows toggle flags that outer loops poll (
src/check-execution-engine.ts:5384-5455,src/check-execution-engine.ts:6824-6880). - Routing logic (on_success/on_fail/on_finish) is interwoven with execution; suppression guards (one-shot tags, goto suppression when re-running for dependents) make control flow difficult to extend.
- Debug visualizer and telemetry rely on ad-hoc hooks inside loops, increasing the risk of regressions when adjusting flow control.
| Capability / nuance | Current implementation | State machine accommodation |
|---|---|---|
Per-check if / fail_if gating |
shouldRunCheck, evaluateFailureConditions (src/check-execution-engine.ts:1516-2050, 8975-9057) |
Model as routing sub-states: every CheckCompleted event flows through a deterministic Routing state that evaluates conditions before enqueuing follow-up events. |
forEach fan-out & on_finish loops |
executeCheckInline + on_finish helpers (src/check-execution-engine.ts:1047-1513, 2200-2350; src/engine/on-finish/*) |
Represent forEach items as scoped dispatch records; let WavePlanning schedule per-item events and treat on_finish as a specialized routing transition capable of emitting WaveRetry. |
goto / goto_event / forward-run dedupe |
scheduleForwardRun + guard sets (src/check-execution-engine.ts:412-520) |
Replace guard sets with queue-level deduping: before enqueuing ForwardRunRequested, consult a hashed tuple (target, event, scope) to avoid re-scheduling. |
| Session reuse & provider contexts | Session metadata assembled in executeDependencyAwareChecks; enforced via runNamedCheck sessionInfo |
Store session provider relationships inside EngineContext.checks metadata so the dispatcher can force sequential execution whenever checks share a session. |
| Memory store & outputs history | MemoryStore, ExecutionJournal, scoped snapshots threaded through the legacy engine |
Keep memory/journal in EngineContext; have state transitions commit snapshots, ensuring history is still authoritative for on_finish, forEach gating, and goto history queries. |
| Debug visualizer pause/resume | pauseGate/DebugVisualizerServer gating inside executeGroupedChecks (src/cli-main.ts:820-880) |
Have LevelDispatch consult a pause controller before spawning tasks and mirror all EngineEvents to the debug server for visualization. |
| GitHub CheckRuns & PR comments | initializeGitHubChecks, completeGitHubChecks*, reviewer.postReviewComment |
Treat these as side effects of Init and Completed states so both engines emit identical updates; attach engine_mode metadata to CheckRuns for observability. |
| Human input provider prompts | CLI message plumbing + provider execution context | ExecutionContext plumbing is untouched; state machine just continues passing it into providers. |
| Nested workflows | Workflow provider executing its own DAG (src/providers/workflow-check-provider.ts) |
Covered in §3.6 — the state machine will compile workflows into child graphs and run them under the same scheduler. |
| Telemetry spans / metrics | emitNdjsonSpanWithEvents, OTEL instrumentation sprinkled through current engine |
Emit spans when entering/exiting each state (state_from, state_to, engine_mode attrs) so trace density stays similar but more structured. |
- Commander definition lives in
src/cli.ts; new flags are added to bothsetupProgramandparseArgsbuilders (src/cli.ts:19-141). - Parsed options are consumed in
src/cli-main.ts: after config discovery the engine is instantiated andexecuteGroupedChecksis called (src/cli-main.ts:784-910). CliOptionstype insrc/types/cli.tsmust include any new flag (src/types/cli.ts:17-74).
src/index.tscreates aCheckExecutionEnginearound line 716 and always runs grouped execution (src/index.ts:680-820).- Inputs are read via
@actions/core.getInput; introducing an input such asstate-machineor honoringVISOR_STATE_MACHINEwould happen alongside other inputs near the top ofrun()(src/index.ts:40-140).
- The published SDK (
src/sdk.ts:1-120) exposesrunCheckswhich instantiates the engine directly and callsexecuteChecks. - Dev/test scripts (
scripts/dev-run.ts,scripts/simulate-gh-run.ts) and sample SDK programs also instantiate the engine manually (seerg "new CheckExecutionEngine"). - The YAML test runner (
src/test-runner/index.ts:100-220and:780+) needs a flip to point at the new engine when the flag is turned on so regression suites can exercise both paths.
- Introduce an
EngineMode = 'legacy' | 'state-machine'option accepted byCheckExecutionEngine(constructor orexecute*methods). Default stays'legacy'. - CLI: add
--state-machine(and envVISOR_STATE_MACHINE=1) that setsengineModebefore instantiating the engine. The flag needs to propagate to all CLI commands (visor,visor test,visor validate, etc.), sohandleTestCommandinsrc/cli-main.tsshould accept/pass it through to the YAML test runner, ensuring regression suites exercise both engines. - GitHub Action: follow the CLI behavior if either a new input (
state-machine: true) orVISOR_STATE_MACHINEenv var is present; bubble the mode into the Action comment/telemetry payload so we can detect it in logs. - SDK/test runner/scripts: accept an optional
engineModeoption so programmatic callers can participate in canaries without CLI flags. - Telemetry/debug: add the mode to spans (e.g.,
visor.run.engine_mode) to keep traces filterable. - Initial implementation can keep mode selection localized to a helper such as
createExecutionEngine({ mode })so that non-flagged call sites continue using the current class.
- Make control flow explicit (well-defined states, transitions, and events).
- Preserve existing semantics (dependency gating, forEach fan-out, routing) while simplifying mental load.
- Unlock incremental features (pause/resume, deterministic tracing, easier retries) without relying on xstate or third-party interpreters.
| State | Responsibility | Exits |
|---|---|---|
Init |
Capture CLI/action options, hydrate config, construct helpers, reset journals. | PlanReady once config + inputs validated. |
PlanReady |
Build dependency graph, expand checks, compute per-step metadata (tags, sessions, fan-out). | WavePlanning (success) / Error. |
WavePlanning |
Inspect outstanding events (forward runs, goto, manual triggers) and queue the next wave’s DAG snapshot. | LevelDispatch when a wave is ready, Completed when no work remains. |
LevelDispatch |
Pop the next topological level, spawn tasks up to maxParallelism, hand them to CheckRunning. |
CheckRunning for each task, WavePlanning once the level finishes. |
CheckRunning |
Run provider + check logic, emit stats, assemble CheckResult. |
Routing with success/failure payload, or Error. |
Routing |
Evaluate fail_if, on_success, on_fail, on_finish triggers; enqueue new events (ForwardRun, WaveRetry, GotoEvent). |
WavePlanning when new work scheduled, Completed if this was the final sink. |
Completed |
Finalize stats, flush telemetry/CheckRuns, surface grouped results. | terminal |
Error |
Capture fatal issues and unwind (shielding CLI/action from partial states). | terminal |
Events flowing between states include: PlanBuilt, WaveRequested, LevelDepleted, CheckComplete, CheckErrored, ForwardRunRequested, OnFinishLoop, and Shutdown.
EngineContextstruct holding: immutable config snapshot, dependency graph, check metadata (tags, triggers, session provider, fan-out mode),ExecutionJournal, output/memory stores, telemetry sinks, and persistence hooks.RunStatestruct capturing: current engine mode, pending events queue, active wave number, active levels, outstanding tasks, global flags (e.g.,failFastTriggered), GitHub check bookkeeping, and debug server hooks. The struct is designed to be serializable so we can persist/resume executions.DispatchRecordcapturing per-check data (scope path, provider id, start ts, attempts, forEach item index) to tie stats/telemetry to state transitions.- Event queue implementation (simple array or deque) so routing can push
ForwardRun/GotoEventevents instead of toggling booleans. The queue doubles as the source for a structured event log, enabling time-travel debugging.
TypeScript sketch:
type EngineMode = 'legacy' | 'state-machine';
interface EngineContext {
mode: EngineMode;
config: VisorConfig;
dependencyGraph: DependencyGraph;
checks: Record<string, {
tags: string[];
triggers: EventTrigger[];
group?: string;
sessionProvider?: string;
fanout?: 'map' | 'reduce';
providerType: string;
}>;
journal: ExecutionJournal;
memory: MemoryStore;
telemetry: TelemetrySink;
gitHubChecks?: GitHubCheckService;
persistence?: {
saveState: (state: SerializedRunState) => Promise<void>;
loadState?: () => Promise<SerializedRunState | null>;
};
}
interface RunState {
wave: number;
levelQueue: ExecutionGroup[];
eventQueue: EngineEvent[];
activeDispatches: Map<string, DispatchRecord>;
flags: {
failFastTriggered: boolean;
forwardRunRequested: boolean;
};
stats: Map<string, CheckExecutionStats>;
historyLog: EngineEvent[]; // append-only log for time-travel debugging
}
type EngineEvent =
| { type: 'ForwardRunRequested'; target: string; gotoEvent?: EventTrigger; scope?: ScopePath }
| { type: 'WaveRetry'; reason: 'on_fail' | 'on_finish' | 'external' }
| { type: 'CheckScheduled'; checkId: string; scope: ScopePath }
| { type: 'CheckCompleted'; checkId: string; scope: ScopePath; result: ReviewSummary }
| { type: 'CheckErrored'; checkId: string; scope: ScopePath; error: SerializedError }
| { type: 'StateTransition'; from: EngineStateId; to: EngineStateId }
| { type: 'Shutdown'; error?: SerializedError };
interface DispatchRecord {
id: string;
scope: ScopePath;
provider: string;
startMs: number;
attempts: number;
foreachIndex?: number;
sessionInfo?: { parent?: string; reuse?: boolean };
}
type SerializedRunState = {
wave: number;
levelQueue: ExecutionGroup[];
eventQueue: EngineEvent[];
flags: RunState['flags'];
stats: CheckExecutionStats[];
historyLog: EngineEvent[];
};Persistence/time-travel strategy (debugger-only for now):
- When the debug visualizer is enabled, after every state transition the engine appends to
historyLog, streams the event over the WebSocket, mirrors it tooutput/debug-events/<session>.jsonl, and (optionally) flushes the minimalSerializedRunStateviapersistence.saveState. By default we persist undertmp/visor-state/<session>.json(override via config/env if needed). Outside of debugger mode we skip persistence/log mirroring to avoid overhead. - During a debug resume, the engine loads the last serialized state, reconstructs in-memory maps, and continues dequeuing events, ensuring retries and routing decisions survive restarts within the debugging session.
- Scaffolding: introduce
EngineModeflag plus a skeletonStateMachineExecutionEnginethat simply proxies to the legacy runner; wire the flag through CLI/Action/SDK/tests. - State-core: implement new state machine that supports dependency execution without routing (no goto/on_finish yet) and hide it behind the flag for targeted tests.
- Routing parity: port
scheduleForwardRun,runNamedCheck, andon_finishsemantics into state transitions; reuse existing helper functions where practical to avoid regressions. - Observability: add structured tracing for state transitions so we can debug the new engine with the debug visualizer and OTEL spans.
- Canary & cleanup: run regression suites in both modes, flip CI to exercise the state machine on dedicated jobs, and deprecate legacy-only code once parity is proven.
- Need access to the user’s research doc (currently outside the workspace) to reconcile requirements or additional tasks that were listed there.
- Confirm how the debug visualizer wants to tap into the new state transitions (current server polls spans in
executeGroupedChecks). We likely need a small event bus that mirrorsEngineEvents to the WebSocket server and OTEL spans (e.g.,visorevent.state_transitionwith{ from, to, checkId }attributes). - Decide whether GitHub Action inputs should expose a first-class
state-machineboolean or rely on env vars. - Determine whether we want to version the engine externally (e.g.,
engine: v2in config) once the flag stabilizes, or keep CLI-only toggles.
Once the missing research document is accessible we should merge those findings into this plan, update the open questions list, and refine the migration steps accordingly.
Visor already supports nested workflows via type: 'workflow' checks and the WorkflowRegistry/WorkflowExecutor (src/workflow-registry.ts, src/providers/workflow-check-provider.ts). Today those executions run entirely inside the provider, which means:
- The outer engine treats the workflow check as a single node even though the workflow definition contains its own dependency graph, inputs, overrides, and potentially recursive workflow references.
- Nested workflow iterations cannot benefit from core-engine features like pause/resume, telemetry, or goto semantics unless the workflow provider re-implements them.
For the state-machine engine we should:
- Expose a “subgraph” capability so a workflow definition can be compiled into an internal DAG and scheduled as a child
EngineContext. That keeps a single state abstraction whether we are running top-level checks or workflow steps. - Carry parent scope into the child
RunStateso results from workflow steps register under meaningful journal scopes (workflow:stepA@item1). - Allow workflows to emit their own
ForwardRunRequested/WaveRetryevents that bubble up to the parent queue. This prevents nested workflows from deadlocking when they need to re-run ancestor steps. - Document limits (depth, fan-out) so that arbitrarily nested workflows do not starve the scheduler. We can enforce a
maxWorkflowDepth(default 3) inRunState.flags.
Implementation strategy:
- Start by projecting a workflow definition into the same
DependencyGraphstructure the main engine uses (the registry already validates steps and dependencies). - When the workflow provider is invoked in state-machine mode, hand the projected graph to the engine instead of running it privately; the provider becomes a thin adapter that returns the child engine’s aggregated
ReviewSummary. - For compatibility, keep the current “self-contained workflow execution” path in legacy mode until all workflows are verified under the state machine.
| Milestone | Description | Test strategy |
|---|---|---|
| M0 – Flag plumbing & proxy ✅ DONE | Add EngineMode, CLI flag/env plumbing (including visor test), and a proxy state-machine runner that simply delegates to the legacy engine so tooling can toggle the mode. |
Update Jest/YAML harnesses to accept engineMode. CI continues running legacy-only while we ensure the flag wires through all commands. |
| M1 – Core state machine (no routing) ✅ DONE | Implement Init → Plan → Wave → Level → Check → Completed transitions covering dependency expansion, fail-fast, stats, GitHub checks, debug pause, but still delegate routing (goto, on_finish) to legacy helpers. |
Run the entire suite twice (legacy + state-machine) via a CI matrix; YAML tests remain unchanged—they're just invoked under both modes. Add targeted unit tests for queue/dispatch logic. |
| M2 – Routing & forEach parity ✅ DONE | Port scheduleForwardRun, on_fail, on_success, on_finish, and full forEach fan-out into the state machine; ensure flags/guards map to structured events. |
Keep dual-mode CI. Add focused e2e tests covering routing loops, fail_if gating, and forEach retries, plus assertions on emitted EngineEvents. |
| M3 – Nested workflows ✅ DONE | Allow the workflow provider to hand child DAGs to the state machine, enforce depth/fan-out limits, propagate journal scopes. | Re-run existing workflow YAML suites under both modes; add a couple of dedicated unit tests that assert depth enforcement and parent/child event propagation. |
| M4 – Observability & default flip ✅ DONE | Stream EngineEvents to the debug visualizer, enrich OTEL spans/check runs with engine_mode, remove legacy guard maps, and make the state machine the default once confidence is high. |
Continue running a reduced legacy suite in CI until full deprecation; monitor telemetry dashboards for regressions before removing legacy mode entirely. |
- YAML-based regression suites must not change; they encode behavior, not engine internals. Re-run them with the new test runner (e.g.,
node dist/index.js test --progress compact) during rollout to prove parity. - Jest/unit/integration tests remain authoritative; we only add a handful of state-machine-specific cases (event queue ordering, wave retry limits) instead of duplicating every scenario.
- CI should eventually run in a matrix (
ENGINE_MODE=legacyvsstate-machine) so every PR exercises both engines until we flip the default. This is easier than maintaining a completely separate test suite.
One of the driving reasons for this rewrite is to reach NASA-like rigor: strong separation of concerns, declarative control flow, and statically checkable contracts. The state machine gives us the runtime substrate; we’ll complement it with two configuration-level enhancements.
We will keep goto / goto_js fully functional for backwards compatibility—nothing is removed—but we’ll introduce a structured transition DSL that offers better static guarantees. Example:
on_finish:
transitions:
- when: "wave('validate-fact').invalid_count > 0 && event.name == 'issue_opened'"
to: issue-assistant
- when: "wave('validate-fact').invalid_count > 0 && event.name == 'issue_comment'"
to: comment-assistant
- when: "wave('validate-fact').invalid_count == 0"
to: nullPlan:
- Extend the config schema with optional
transitions[]entries (fields:when,to, optional metadata). During theRoutingstate, the engine evaluateswhenexpressions in priority order and enqueues the resulting transition. - Build a static validator that ensures each
torefers to an existing check (ornull), expressions only use approved helpers (wave,event,outputs,memory, etc.), and that transitions either cover all cases or explicitly fall back tonull. - When both
goto/goto_jsandtransitionsare present, the state machine honorstransitionsfirst (still executing the others as a fallback) and logs a warning so we can gradually migrate built-in configs away from dynamicgoto_jswithout breaking existing flows.
To support design-by-contract we’ll let checks declare assumptions about their inputs and guarantees about their outputs:
extract-facts:
guarantee:
- "Array.isArray(output) && output.every(f => f.id && f.claim)"
validate-fact:
assume:
- "typeof extract-facts.item.id === 'string'"
guarantee:
- "typeof output.fact_id === 'string' && output.fact_id === extract-facts.item.id"Execution steps:
- Extend
CheckConfigwith optionalassume[]andguarantee[]arrays. Before executing a provider, the state machine evaluatesassumeexpressions using dependency outputs (and forEach item context); failures short-circuit execution with a structured issue referencing the violated assumption. After execution, it evaluatesguaranteeexpressions against the check’s output and records fatal issues if they fail. - Add compile-time validation: parse each expression and ensure it only references known symbols. For example,
assumecan readdependencyName.output,dependencyName.item, ormemorybut cannot mutate state;guaranteecan read outputs but not future steps. - Emit telemetry (
engine.contract.assume_failed,engine.contract.guarantee_failed) so CI and runtime monitoring can flag contract regressions.
By lifting control flow into transitions and correctness rules into assume/guarantee, we make configurations statically analyzable, reduce reliance on imperative goto_js, and move closer to NASA-inspired static validation goals while still honoring legacy constructs for advanced scenarios.
- on_success/on_fail evaluate
run,run_js, andgoto(with optionalgoto_event). - on_finish for forEach parents is processed after children complete; loop budget enforced.
In addition to goto/goto_js, checks can use declarative transitions on on_success, on_fail, and on_finish:
on_finish:
transitions:
- when: "any(outputs_history['validate-fact'], v => v.is_valid === false) && event.name === 'issue_opened'"
to: issue-assistant
- when: "any(outputs_history['validate-fact'], v => v.is_valid === false) && event.name === 'issue_comment'"
to: comment-assistant
- Rules evaluate in order; first true wins. Use
to: nullto explicitly do nothing. - Backward compatible: if
transitionsis omitted or none match, the engine falls back togoto_js/goto. - Helpers available:
outputs,outputs_history,output,event,memory, plusany/all/none/count.
Per-check contracts:
assume:
- "env.NODE_ENV === 'ci'" # preconditions – if any is false, skip with skipReason=assume
guarantee:
- "Array.isArray(output.items)" # postconditions – violations add error issues (contract/guarantee_failed)
assumeis evaluated pre-execution; skipped checks are recorded and visible in stats/history.guaranteeis evaluated post-execution; violations are non-fatal by default (routing unaffected) but produce issues.