Skip to content

Warp-drive contract

The warp-drive state machine is a table: PHASES (23 names) and TRANSITIONS (62 (from, event, to) edges) in scripts/warp-drive/state-machine.js, plus a per-phase DEFAULT_REASONING_BUDGET. Everything else in the loop — the hooks that auto-advance it, the graph the monitor draws, the phase-history log, the segmentation checkpoint, the bob warp CLI — is written against that table. This page explains why the table is frozen behind a committed fixture (#2042), what the freeze does and does not cover, and how a change lands while it holds.

The rule lives in one place in code — the fixture, tests/fixtures/phase-contract.json — and in one place in prose, this page. The moratorium itself is decision #2118.

The table grew one edge at a time. The in-session QA loop added qa (#868); the preflight gate added preflight and blocked (#1923); skipping blocked items added work_skipped (#1270). Each was right on its own. What nothing checked was the set: tests/test-phase-meta.js proved every phase had tooltip metadata, but not which phases existed, and by the 2026-09-10 architecture review (#2027) DEFAULT_REASONING_BUDGET had silently drifted to 18 of 23 phases and the state reference’s transition table was missing four phases. The consolidation work that review queued — the glob test runner (#2043), the hook dispatchers (#2082), the flag registry (#2085) — is all written against the table, and it needs the table to hold still while it lands.

So the contract is pinned, and pinned the same way the checkpoint dirt rule was pinned a day earlier (#2037, Checkpoint dirt rules): a hand-authored fixture, a test that drives the real code against it, and a make check gate that keeps the prose honest.

tests/fixtures/phase-contract.json (phase-contract/1) states:

Section Pins Compared against
phases every phase name, in order PHASES
transitions every (from, event, to) edge TRANSITIONS, flattened
reasoning_defaults one level (standard | high) per phase — the key set equals phases DEFAULT_REASONING_BUDGET
moratorium frozen_until (an ISO date) and decision (the linked decision issue) the rules below

Two gates read it:

  • tests/test-phase-contract.js (make test-phase-contract) compares the live engine against the fixture in both directions and fails with the exact diff: a phase in code but not the fixture, a phase in the fixture but not in code, an edge added, removed or retargeted, a phase with no budget entry, a level that disagrees. Negative controls mutate copies of the tables so each class of drift is proven to be named, not merely absent today.
  • scripts/checks/phase-contract.js (make check-phase-contract, in make check) compares the fixture against the two doc tables that state it — the transition table in the state reference and the reasoning defaults in token monitoring — so the prose can never say something the fixture does not. --json carries the contract identity.

The fixture is not a snapshot. A snapshot is regenerated on a whim; this file is edited by hand, in the same commit as the code, and during the moratorium only with a decision linked from it.

The contract is the topology, not the behaviour inside a phase. Nothing here constrains:

  • what a phase’s instruction says, or what data a transition accepts on --data;
  • the display overlays — the bug-work labels (bug_fixing, regression_gate, bug_verified, #518) and the revalidating sub-mode (#1008) relabel existing phases and add no edges;
  • the budget breakers that route into budget_exceeded (they use edges the fixture already lists), or the budget_continue return to the interrupted phase (#1183) — the table edge is the fallback, the recorded phase is a runtime override;
  • PHASE_META, hook wiring, or the _workflow config table — those have their own gates (test-phase-meta, test-declared-behaviours, check-workflow-keys).

No new phase and no new or retargeted transition until 2026-10-11 without a decision issue. Concretely, while the freeze holds:

  1. A phase or transition changes only by editing the fixture in the same commit as the code. Any other route is red — the test names the diff and the fix.
  2. That commit must link a decision issue from moratorium.decision in the fixture, saying why the freeze is being broken. #2118 is the standing record; a new decision supersedes it for that one change.
  3. DEFAULT_REASONING_BUDGET keeps a key set equal to PHASES. The documented default for a phase with no explicit override is standard — the value the lookup falls back to — which is what idle, preflight, blocked, awaiting_continue and completed now carry explicitly.

After 2026-10-11 the fixture stays: it is the contract, and the test still holds code and fixture together. What lapses is the decision-link requirement — a fixture-plus-code change is then an ordinary reviewed commit.

The frozen contract is a declared behaviour (#1834) — contract:phase-contract:all in scripts/warp-drive/declared-behaviours.js, alongside the hook auto-advances and the commit gate — so the engine’s promises and the contract it runs under are listed in one registry. contractVersion() reads the fixture from the source checkout and renders one line:

phase-contract/1 — 23 phases, 64 transitions, frozen until 2026-10-11 (#4206)

The decision named is the latest one the fixture links. #4206 broke the freeze for the two work_skipped exits from chunking and coding (#3476).

It appears on bob warp status (the Contract: line, from state-machine.js status’s contract field), in bob doctor (an INFO contract: line and a contract object in bob-host-drift/1 — named, never graded: the fixture is a source file, not a deployed surface), and in state-machine.js declared. The fixture is read from the BoB source checkout, resolved the way bob doctor resolves it, because the deployed copy under BOB_HOME has no tests/ (#3143). With no checkout the line says not measured, and an unreadable fixture is reported as such, never thrown.