greenroom
Reference

State contract

Field-by-field reference for .greenroom/state-contract.json.

The state contract declares the screens and goals Greenroom will test, and maps source files to them. It gives a run coverage authority: a pass can be clear within its declared scope when its goals are verified; without a contract, clean runs cap at inconclusive. Visiting every screen does not prove that a user journey succeeded. The contract lives at .greenroom/state-contract.json and, like the manifest, is always read from the PR's base revision.

Top-level fields

FieldTypeRequiredMeaning
schemaVersionstringyesContract schema version, e.g. "1.0".
routerKind"hash-spa" | "expo-router" | "react-navigation" | "swiftui"with graphPathsWhich deterministic extractor rebuilds your screen graph from source.
graphPathsstring[], 1 to 20with routerKindWhere the extractor reads. See the pairing rules below.
entryStatestringnoThe state a fresh launch lands on.
sharedSourcesstring[]noRepository-relative globs whose changes require all states to be checked.
statesarray, 1 to 20yesThe screens you declare.
transitionsarray, up to 400noEdges 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 your app/ directory).
  • swiftui: one or more directories, typically the target's source root.

States

Each state entry:

FieldTypeRequiredMeaning
idstringyesStable identifier, referenced by transitions and reports.
routestringnoThe route or URL fragment that reaches it.
screenstringnoThe component or view name (TodayView, CheckoutPage).
variantstringnoDistinguishes variants of one screen (empty state, error state).
goalstringnoActions and observable outcome the driver must establish. Without one, the driver is asked only to reach and identify the screen.
identityobjectnoKey facts that identify this state in an observation.
sourcesstring[]noRepository-relative source globs used to attribute changes to this state.
oraclestringnoContract metadata used by research fixtures. Production does not execute this string as an assertion; put observable expectations in goal.
setupTransitionsstring[]noSetup metadata used by research fixtures. Production does not replay these transitions automatically; describe necessary setup in goal.

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.

Transitions describe the graph; they are not an execution script. In production, the driver chooses actions from current observations to pursue each selected state's goal.

Test a completed journey

The workflow's goal describes the overall journey. When a state contract exists, the driver receives each selected state's goal. A full pass's verification judge checks the overall journey against the evidence; a scoped pass checks only its selected goals. The overall workflow goal is not automatically added as another driver task.

For example, “Reach Progress” can succeed by opening a tab with old history. To test saving a session, make the actions and new result explicit:

{
  "schemaVersion": "1.0",
  "entryState": "session-saved",
  "sharedSources": [".github/**", ".greenroom/**"],
  "states": [{
    "id": "session-saved",
    "screen": "Saved session journey",
    "sources": ["src/**"],
    "goal": "Inspect Progress and record the existing session count. Return to Today, start a session, complete every step, choose a 4-star rating, and press Finish session. Verify Progress now has one additional session and its newest row matches the exercise and selected rating. Navigate away and back and confirm that row remains. Existing history alone does not satisfy this goal."
  }]
}

Replace the source mapping and app labels with your own. This contract selects one journey checkpoint, which may visit several screens; it does not claim full app coverage. Use a fresh isolated test environment. If persistence across a reload or relaunch matters, ask for that explicitly and ensure your test app supports it. Merge contract changes into the base branch before opening the test PR.

System alerts in goals

If the app requests a permission at launch (notifications, tracking, location), the iOS system alert is the first screen the virtual user sees on every fresh install, not your entry state. The driver never grants a permission (tapping Allow is a policy denial), may decline (Don't Allow, Not Now), and continues on what the app shows next. Write that into the entry state's goal ("decline the notifications alert, then confirm Today lists exactly one session"), make sure the app proceeds after a decline, and keep screens that need the permission granted out of the contract; the simulator cannot pre-grant notifications and Greenroom does not edit its privacy database.

Start small

Start with a few observable goals and source mappings, including the primary user outcome. Add variants where correctness matters most, and inspect the first report's evidence before expanding. The schema caps states at 20 per contract deliberately: it describes your app's core surfaces and outcomes, not every possible screen.

On this page