This page answers two questions: what prevents an invalid artifact from ever being created, and what detects one if it slips through anyway. Every mechanism listed is real and installed — nothing aspirational. Each ring below is read from the harness's own rules, not written for this page.
The founding observation: agents obey checkers, not documents. A rule that exists only as prose will eventually be violated by an honest agent under context pressure — proven live when a pull request satisfied exactly the sections the checker verified and dropped the two it didn't. So every contract rule must be a deterministic check, and every check must sit at the earliest chokepoint that can host it.
The model: three rings, by where a violation dies
| Ring | Where | What happens on violation | Who pays |
|---|---|---|---|
| Hooks | The agent's own machine (command, commit, push) | The action itself is refused — the invalid artifact never exists outside the agent's session. The exact errors feed back; the agent fixes and retries, in-session. | Nobody. Self-correcting. |
| Branch Rules | The forge (CI on every pull request) | The identical checks re-run; CI goes red; the merge gate makes red unmergeable by agents. Covers writers the local gates can't reach (web UI, humans, other tools). | Visible red — a human may look. Red on a gated rule now means a gate bug, not an agent failure. |
| Audits | After merge, continuously, across the whole forge | Drift is surfaced as findings, regardless of who or what wrote it — including history that predates the gates. | Scheduled clean-up, never a surprise mid-dispatch. |
The same check implementations run at ring 0 and ring 1 — one codebase, two enforcement points, so the local gates and CI can never disagree.
The fixed-position rule. A gate reads its signal from a fixed position — a named anchor, an HTML-comment marker, a line-anchored field, a bounded read window — never by scanning free text for a phrase that means the right thing. Free text is caller-controlled: whoever writes the artifact can put a signal-shaped sentence anywhere in it, including inside a quotation of somebody else's, and a scanning gate cannot tell a cast verdict from a mention of one. Fixing the position is what closes that: a caller-supplied field rendered outside the window the gate reads can no longer reach a position the gate treats as structural, however the writer formats it.
The two rules a gate author meets before adding a check:
- Decide from parsed structure, never from prose. A gate that must decide whether a task touches a domain, resolves a path, or crosses a boundary decides from a field a writer fills in a fixed grammar — a glob list, a numbered citation, a table row — never from scanning prose for a phrase that would mean the right thing if read charitably. Prose can name a thing to include it or to exclude it, and a scanning gate cannot tell the two apart; a heuristic built to guess which one a sentence meant is wrong in both directions, and a blocking gate that is wrong in either direction is worse than one that runs less often. Where the parsed structure does not yet exist for an artifact (an Issue below the cutover that first mandates a
## Surface), the gate degrades to the old prose scan for that artifact only — it neither invents structure nobody asked for, nor extends the prose scan's blast radius to cover stock that predates it.checkBlastRadiusScopeis the worked example: cutover-gated, it decides from## Surface'sin:glob list once one is mandatory, never again fromBoundary/Project(s) + blast radiusprose, whatever that prose says — naming a shared package to explicitly exclude it no longer trips a gate that used to read prose span-blind to intent. - One fact, one implementation, resolved once and passed down. A fact two gates must agree on — whether a glob resolves to a real tracked file, whether a domain falls under a path — is computed by one function and passed to every caller that needs the answer; it is never re-derived by a second hand-written implementation that merely happens to agree with the first today. Two implementations of the same predicate are a race the moment either one changes: the day they diverge is invisible until an artifact one side accepts and the other refuses reaches the seam between them, and by then neither implementation is provably the bug. Inject the shared function into whichever side cannot compute it itself — a pure, filesystem-free module takes the answer as an injected parameter (a
(glob: string) => boolean, a resolved list); the impure caller that already computes it (the forge-write path, injecting the brief renderer's own glob expansion) is the one and only place that does, so the authoring gate and the brief renderer read the identical resolution and can never disagree about whether a Surface resolves.
The own-PR fixture rule. A pull request that adds a check which reads a PR body ships a fixture test running that check over that PR's own body. A body-reading check is the one class whose real input exists at the moment the PR opens and is never exercised by a synthetic fixture the author also wrote: the author's fixture agrees with the author's mental model by construction, and the first real body it meets is the one it was supposed to grade. Running it over the body in hand costs one test and converts "it should work on a real body" from a belief into a passing assertion.
The Audience column, read once for all three tables below: product — this row's implementation is a named check the product registers, so it ships to every adopter through vinaya check. repo-own — everything else: a mechanism specific to how this repository enforces itself on top of the product — a hand-written CI job, the check runner itself, a forge-write command, or a check this repo runs against its own doctrine tree but has deliberately not registered as a named, adopter-facing check. A row carrying no Audience cell at all (older doctrine, or an un-upgraded adopter copy) reads as repo-own — the safe default, since it makes no shipped claim to verify. G6, below, blocks CI on any product row whose implementation does not actually resolve in that registration.