Documentation-Sync Gates (tiered)
In one line: Documentation-sync is enforced the way code is — as fitness functions, tiered by what a machine can judge: objective checks block (fail-closed), judgment checks advise (fail-open).
Living documentation (§11) is a discipline, not a stage; v3.2 gives it teeth without the blanket-age-gate friction the earlier advisory hook deliberately avoided.
| Tier | Check | Mechanism |
|---|---|---|
| Blocking (fail-closed) | Link integrity | the rendered build (onBrokenLinks: throw) + a repo-level markdown link pass |
| Blocking (fail-closed) | ADR-register integrity | every ADR file has a register row (check-adr-register.sh) |
| Blocking (fail-closed) | Scoped code↔doc drift | a code path in the doc-pointers manifest (§11.4) changes without its mapped doc → block; clear with [skip-docs: <reason>], logged for the §2.8 census |
| Blocking (fail-closed) | Generated-artifact freshness | a committed generated tree (e.g. a Docusaurus site built from canon *.md) must equal a fresh run of its generator — check-generated-fresh.sh seeds a throwaway dir from the committed tree, regenerates into it, and diffs; any difference blocks. Closes the "generated copy lags source" class (edit source, forget to regenerate) |
| Advisory (fail-open) | Blanket 30-day staleness | check-doc-staleness.sh warns; a blanket age-gate that blocks commits surprises contributors, so it advises (never shipped armed) |
| Advisory (fail-open) | Prose quality, Diátaxis classification | human judgment; tooling (vale, etc.) is Recommended, not Core |
The split is scope, not predicate: a scoped, code-change-triggered check blocks; a blanket, calendar-age check advises. Core blocking checks use plain scripts plus the existing build — zero new Core dependencies. The scoped code↔doc gate ships behind DOC_SYNC_BLOCKING until a dry-run over recent PRs shows a sub-10% false-positive rate (the §7 gate-admission cost/mechanism/retirement rule).
The generated-artifact freshness gate, by contrast, blocks immediately with no warm-up flag — regenerate-and-diff is deterministic and ungameable (it runs the real generator, not a hand-maintained source→output map), so its false-positive rate is structurally near-zero whenever the generator itself is deterministic (no timestamps, no randomness). The one precondition is that determinism; a generator that stamps the current date will diff against itself and must be made reproducible before the gate is armed. The check is generic — point --generator at any script that takes its output dir as $1 and --out at the committed tree — so the same gate pins a Docusaurus site, generated protobufs, or an OpenAPI client.
Why: enforcing every documentation concern as advisory is the weak form — subjectively-evaluated concerns end up sparsely evaluated. Tiering keeps the objective concerns honest (they block) while refusing to arm the one gate — blanket age — that trains bypass behavior.