Where it lives

One file at the project root: .deviations.yaml. A YAML array of entries. just check reads it (inside _fail(), so both the local and CI paths get it for free) and reports a matching FAIL as WAIVED instead: a distinct, separately counted outcome, never a silent PASS. Only for entries whose reason is non-empty; anything else falls straight through to FAIL unchanged.

A project with no deviations has no .deviations.yaml at all. Absence is the conformant default.

The format

# .deviations.yaml — at project root
- standard: design-system
  assertion: grep:global.css
  reason: |
    Migrating off a legacy CSS framework. global.css still carries
    the old palette in parallel until the new build lands; the
    @dunn.dev/reference import ships as part of that change.
  owner: andrew@dunn.dev

- standard: design-system
  assertion: grep:package.json
  reason: |
    Same migration. package.json declares @dunn.dev/reference once
    the build swap lands.
  owner: andrew@dunn.dev

- standard: design-system
  assertion: grep:package.json
  reason: |
    Transitional. Pinned to a local file: spec while validating a
    breaking-change candidate against @dunn.dev/reference. Flip
    back to the registry version once the change ships.
  owner: andrew@dunn.dev

Field semantics

  • standard: the contract directory name (repository, pipeline, design-system).
  • assertion: the assertion id as it appears after the contract name in FAIL output (e.g. grep:global.css, file-contains:README.md). Per-assertion entries are canonical: write one entry per failing assertion, named explicitly. The wildcard * is deprecated: it reads as "we didn't enumerate" rather than "we considered each assertion and concluded it doesn't apply". just check still honors it: it matches every assertion under that standard, so existing wildcard entries keep working. Write new entries per-assertion going forward; older repos may carry wildcards, and flip them on next touch.
  • reason: one paragraph naming what's true about this project that makes the standard not apply OR what's in flight that will close the gap. Future-you reads this; write to that audience.
  • owner: email of the person accountable for the entry. Audit point if the deviation outlives the reason.

What the deviation register is NOT

It is not a place to silence a check that you don't feel like satisfying. It is not retroactive cover for project-local hacks that should have been routed to the right repo. It is not a permanent license; an entry stands only while its reason stays true. A reason that stops being true is deleted at the next audit, or when the project re-conforms, never renewed in place.

The vacuity-detection principle applies here: an entry without a reason that names either a permanent project shape or an in-flight unblock is a vacuous register entry. Treat it as you would a vacuous check: broken even if it passes.

When in doubt, route first

Before adding a deviation, ask where the fix belongs. Most "I need a deviation here" instincts are actually "this fix belongs in the catalog / design package / arcana instead of this project." The deviation is the last resort, not the first move.