State contract
Field-by-field reference for .greenroom/state-contract.json.
The state contract declares your app's screens and how your router maps source files to them. It is what gives a run coverage authority: with it, Greenroom scopes your diff to exact screens and a clean run is a real pass; without it, clean runs cap at inconclusive. It lives at .greenroom/state-contract.json and, like the manifest, is always read from the PR's base revision.
Top-level fields
| Field | Type | Required | Meaning |
|---|---|---|---|
schemaVersion | string | yes | Contract schema version, e.g. "1.0". |
routerKind | "hash-spa" | "expo-router" | "react-navigation" | "swiftui" | with graphPaths | Which deterministic extractor rebuilds your screen graph from source. |
graphPaths | string[], 1 to 20 | with routerKind | Where the extractor reads. See the pairing rules below. |
entryState | string | no | The state a fresh launch lands on. |
states | array, 1 to 20 | yes | The screens you declare. |
transitions | array, up to 400 | no | Edges between states. |
Router kinds and graphPaths
routerKind and graphPaths must be declared together; one without the other is a malformed contract and fails validation loudly, rather than silently falling back to model-based mapping.
hash-spa: exactly one path, the single source file defining your views object. Diff scoping is line-level.react-navigation: exactly one path, same single-file shape. Scoping for this kind is file-level, not line-level.expo-router: one or more directories containing the routed screens (typically yourapp/directory).swiftui: one or more directories, typically the target's source root.
States
Each state entry:
| Field | Type | Required | Meaning |
|---|---|---|---|
id | string | yes | Stable identifier, referenced by transitions and reports. |
route | string | no | The route or URL fragment that reaches it. |
screen | string | no | The component or view name (TodayView, CheckoutPage). |
variant | string | no | Distinguishes variants of one screen (empty state, error state). |
goal | string | no | What a user accomplishes here, phrased as a goal. Drivers pursue this. |
identity | object | no | Key facts that identify this state in an observation. |
oracle | string | no | A declared expectation checked against evidence. This is what powers tier 3 findings. |
setupTransitions | string[] | no | Transition ids that must run before this state is reachable. |
Transitions
Each transition names from and to state ids, optionally with the action (or actions, up to 10) that performs it and preconditions that must hold. Transitions let the mapper reason about what else a changed screen can affect downstream.
Start small
A contract with five states and no oracles already upgrades coverage authority and makes scoping exact. Add oracles and variants where correctness matters most (checkout, data entry, anything with arithmetic), and grow from there. The schema caps states at 20 per contract deliberately: the contract describes your app's load-bearing surfaces, not every screen you have.