guard run is fully deterministic: it builds the repo via the recipe, executes every committed scenario (including the ones that were already failing at birth), and writes the run to .truecourse/guard/. A test that was red at birth simply comes back green once the code catches up. It exits non-zero on any drift, so it drops straight into CI. No LLM, no API key, no claude binary.
api.services.up and the seed once per run, and points provided external services through their fault-scriptable proxies.
Not every red test is drift
A scenario walks a flow: some steps assert a spec claim (they carry a milestone), others only prepare the world (the seeding request at the head of a flow, a login). When the step that fails is one of the preparation steps, the run annotates the result blocked precondition: the scenario still fails, but the documented behavior was never actually exercised, so the fix is the setup (seed the row, declare the fixture, supply the credential), not the code. The CLI prints it on its own line under the failure and the dashboard marks the test “setup failed”, distinctly from a real expectation mismatch. It’s an annotation only; it never changes an outcome and never softens a CI gate.Reading the results
.truecourse/guard/evidence/<runId>/ showing exactly what the scenario ran and what came back (credential and external-service values are masked). The dashboard’s Guard → Runs view shows each run’s drifts with the per-failure evidence inline.
The run store
.truecourse/guard/ mirrors the analyze store:
In CI
0 when every scenario passes, non-zero on any drift. Scenarios, recipe, and manifest are committed, so CI needs no LLM configuration at all.
TRUECOURSE_MAX_CONCURRENCY caps the runner’s parallel scenario sandboxes, and TRUECOURSE_MAX_API_CONCURRENCY separately bounds how many api-driver servers are resident at once; see Models & environment.Next steps
Dashboard
Review runs, drifts, and per-failure evidence visually.
Storage
What the guard store holds, and which files to commit.