Roll out a new required field without breaking the build
Your metadata standard needs to grow. Maybe you want every doc to carry an owner, or you decided description should be mandatory rather than recommended. The risk is obvious. Flip a field to required in one commit, and the next CI run fails on every existing doc that predates the rule. On a large repo that is hundreds of red checks at once, and no contributor can merge until the backlog is cleared.
A baseline removes that trade-off. You record today’s violations once, and from then on manni meta fails only on findings that are new. The rule is live immediately for everything anyone writes from now on, and the backlog stays visible instead of blocking the build.
This guide assumes you already have a working manni.config.yaml and a schema you control. If you are still using the built-in OKF schema, author your own schema first so you have a file to edit. You cannot add fields to a built-in.
Step 1: Add the field as required
Section titled “Step 1: Add the field as required”Add the new field to your schema’s properties and to required. No staging, no second lenient copy of the schema to keep in sync.
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "required": ["type", "owner"], "properties": { "type": { "type": "string", "minLength": 1 }, "owner": { "type": "string", "minLength": 1 } }}Run a pass to see the size of the backlog you are about to record:
npx @hawkeyexl/manni meta validateThat run exits 1 and lists every doc missing owner. This is the wall of red the baseline exists to absorb. Read it once, so you know what you are agreeing to forgive.
Step 2: Record the backlog
Section titled “Step 2: Record the backlog”npx @hawkeyexl/manni meta validate --write-baselineBaseline written to .manni-baseline.json 412 findings recorded (+412 new, -0 no longer occur)Commit .manni-baseline.json alongside the schema change. It is a source file, and reviewers should see it grow or shrink in the diff. A pull request that adds entries is a pull request that added violations.
Step 3: Turn the baseline on
Section titled “Step 3: Turn the baseline on”Point your config at the file so every run, yours and CI’s, compares against it:
collections: - name: pages paths: - "docs/**/*.md"
meta: schemas: - ./schemas/doc.json
baseline: .manni-baseline.jsonnpx @hawkeyexl/manni meta validate✓ docs/api/legacy.md (2 baselined)✓ docs/guides/intro.md
2 files checked, 2 passed, 0 failed, 0 errors412 baselined findingsGreen build, live rule. Ship it.
Step 4: Watch the debt shrink
Section titled “Step 4: Watch the debt shrink”-
New violations fail immediately. A doc that lands without
ownerproduces a fingerprint that is not in the baseline, so the run exits1and names only that file. The rule you shipped in step 1 is doing its job from day one. -
Fixed violations are reported, not forgotten. When someone adds the missing field, its fingerprint stops occurring and the summary says so:
412 baselined findings, 37 no longer occur — run --write-baseline to prune -
Prune when the number gets noisy. Re-record to drop the entries that no longer apply:
Terminal window npx @hawkeyexl/manni meta validate --write-baselineBaseline written to .manni-baseline.json375 findings recorded (+0 new, -37 no longer occur)The count in the summary is the debt, on every run. When it reaches zero, delete the file and drop the
baseline:key. The ratchet is finished.
Two things a baseline does not forgive
Section titled “Two things a baseline does not forgive”- A renamed file. Entries are keyed by path. Moving a document takes its findings to a key with no entry, and every one of them reads as new. The fix is one
--write-baseline; the stale-entry line in the summary is the hint that a rename, not a regression, is what happened. - A re-pointed schema. The schema reference is part of a violation’s identity, so switching
./schemas/doc.jsonto a URL serving identical bytes changes every fingerprint. Re-record when you move a schema.
The one-statement ratchet, for when a backfill is honest
Section titled “The one-statement ratchet, for when a backfill is honest”A baseline exists because most required fields have per-document values nobody can invent in bulk. Every page’s owner is a fact you have to go find. But some rollouts start from a uniform value that is simply true: reviewed: pending, visibility: internal, language: en. For those, carrying a backlog is the long way around. One ALTER statement adds the field to your schema as required and backfills every file, in the same write:
npx @hawkeyexl/manni meta query "ALTER TABLE docs ADD COLUMN reviewed TEXT NOT NULL DEFAULT 'pending'" docs/The preview (run it with --dry-run first) names every file it will touch and the schema file it will edit. Afterwards validate is green with no baseline entry, because no document is missing the field. As a rule of thumb, ratchet when a starting value is honest, and baseline when the value is a fact per document. The two compose, so you ratchet the uniform fields and baseline the rest.
ALTER edits the schema the corpus resolves, either your file in place or a local fork if the corpus runs on a built-in. Gate on rules that span files introduces the query surface this rides on, and the query reference has the full DDL rules.
What you have now
Section titled “What you have now”You now have three things:
- A required field that went live the day you wrote it.
- A build that stayed green.
- A single file that says exactly how much legacy debt is left, visible on every run and shrinking as contributors touch their pages.
No parallel lenient schema, no hand-maintained list of which folders are clean, nothing to keep in sync.