Skip to content

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.

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.

schemas/doc.json
{
"$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:

Terminal window
npx @hawkeyexl/manni meta validate

That 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.

Terminal window
npx @hawkeyexl/manni meta validate --write-baseline
Baseline 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.

Point your config at the file so every run, yours and CI’s, compares against it:

manni.config.yaml
collections:
- name: pages
paths:
- "docs/**/*.md"
meta:
schemas:
- ./schemas/doc.json
baseline: .manni-baseline.json
Terminal window
npx @hawkeyexl/manni meta validate
✓ docs/api/legacy.md (2 baselined)
✓ docs/guides/intro.md
2 files checked, 2 passed, 0 failed, 0 errors
412 baselined findings

Green build, live rule. Ship it.

  1. New violations fail immediately. A doc that lands without owner produces a fingerprint that is not in the baseline, so the run exits 1 and names only that file. The rule you shipped in step 1 is doing its job from day one.

  2. 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
  3. Prune when the number gets noisy. Re-record to drop the entries that no longer apply:

    Terminal window
    npx @hawkeyexl/manni meta validate --write-baseline
    Baseline written to .manni-baseline.json
    375 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.

  • 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.json to 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:

Terminal window
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.

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.