Orchestrata Ultimate › Gates › requirement-corpus
Gate: requirement-corpus
At a glance
| Purpose | Refuse a @traces citation in src/ naming a requirement id the RTM loader cannot grade (DANGLING). |
| Where it runs | The scripts/gates.py battery (registered as requirement-corpus); scripts/health_check.py. |
| Trigger | A battery run whose specs/, src/ or scripts/generate_rtm_evidence.py fingerprint changed; a health check. |
| Exact command | python scripts/check_requirement_corpus.py |
| Blocks | Exit 1 on a DANGLING citation; exit 2 INVALID when zero ids or zero citations were examined; 0 pass. Spec/checklist drift is advisory. |
| Ruling / ADR | RUL-013 — specs are authoritative, RUL-031 — requirement ids have one spelling, RUL-028 — traceability is a query; RTM regeneration is part of the Definition of Done in ADR-0069 / RUL-034. The module docstring names no ruling. |
| Last recorded verdict | ok: true in the primary checkout's .cache/gates.json (file last written 2026-09-22 10:01 local). wt-MAP-PRECOMMIT has no .cache/gates.json. |
What it refuses
A @traces citation in src/ naming a requirement id the RTM's loader cannot grade
— DANGLING: "either the decorator is wrong, or the loader has been narrowed again.
Both are defects, and this gate fails on either without needing to know which."
Why it exists
Module docstring, the drift it closes:
"This repo had two things deciding 'what requirements exist':
serialize_codebase_ast._requirement_source_files— the RATIFIED corpus … andgenerate_rtm_evidence.load_requirements— what the RTM actually grades against.They drifted, and the drift was invisible for as long as nobody compared them. The toolkit widened its scan after finding that a checklist-only glob made 43 real
@traceslook dangling; the RTM generator never received that fix. Sotrace-lintcalled those citations clean while the RTM graded them GHOST, and 68 requirement ids across 6 files — all of SPEC-033 and SPEC-034, 22 of SPEC-110, the001-kerneland097addenda — sat outside the matrix's universe. Not UNLINKED. Not GHOST. Absent from the denominator, so every coverage percentage was computed against the wrong total.The defect was never a missing checklist. It was two readers of one corpus with nobody asserting they agree."
Why it is shaped the way it is:
"The obvious gate — compare
load_requirements()against the corpus function it calls — is a TAUTOLOGY. It can never fail, which is the same false green as a checker that never looked. So the load-bearing comparison is against@tracesdecorators harvested from the AST ofsrc/, which know nothing about how requirements are loaded and can therefore genuinely disagree."
Documentation drift (spec.md vs its checklist) is an ADVISORY, not a failure — "a gate that fails on documentation cadence gets switched off, which costs more than the drift it catches" (the docstring's example: "SPEC-110's checklist asserts '29/29 requirements implemented' against a spec.md defining 51").
How it runs
- Command:
python scripts/check_requirement_corpus.py - When it fires: the
scripts/gates.pybattery (registered asrequirement-corpus);scripts/health_check.py. - Inputs: fingerprint roots
("specs", "src", "scripts/generate_rtm_evidence.py")— "BOTH sides of the comparison it makes: the spec corpus that defines requirement ids, and the src/ @traces that cite them. Hashing only one side would let a narrowed loader or a new bad decorator land while the battery served a cached pass — and a stale pass here is exactly the drift that hid 21 dangling traces for months" (registry comment). - Caching: the verdict is cached in
.cache/gates.jsonagainst_fingerprint(roots): a sha256 over the relative path and bytes of every file underspecs/andsrc/whose suffix is.py,.toml,.pl,.json,.mdor.sh(_interesting()inscripts/gates.py), plusscripts/generate_rtm_evidence.pyas a file root. That covers both readers:harvest_src_declarations(.pyunder src/) and the requirement loaders (.mdunder specs/:checklists/requirements.md,spec.md,*-addendum.md). There is no per-gate suffix mask: this page previously quoted one, andgit log --all -S GATE_MASKS -- scripts/gates.pyfinds no commit that ever contained aGATE_MASKStable (checked 2026-10-23). - It deliberately imports
generate_rtm_evidence's own readers (harvest_src_declarations,load_requirements,requirement_ids_in, …) rather than reimplementing "what counts as a citation" a third time.
Typical failures and how to fix them
- Exit codes: "0 pass · 1 violations · 2 INVALID. A run that examined zero requirement ids or zero citations reports 2, never 0."
- An INVALID after a corpus move. If
specs/was renamed or the loader's globs stopped matching, the gate examines zero ids and exits 2 rather than passing. Fix the path or glob; do not treat the 2 as noise. - A DANGLING id with a variant spelling. RUL-031 gives each requirement id one spelling; a decorator citing a different form of the same id reads as DANGLING. Cite the id exactly as the spec defines it.
- What a failure looks like (representative): each DANGLING citation named with its source symbol and the ungradeable id; advisories (spec/checklist drift) are printed but never fail.
- Un-blocking: fix the decorator (cite a real id), or fix the loader if it was narrowed — the gate cannot tell which, and that is deliberate.
Wiring
- Invokers:
scripts/gates.py(battery),scripts/health_check.py. - Complementary gates, per this gate's and feature-loss's own scope demarcation:
feature-loss supplies the
"why" for the subset of dangling citations caused by a deleted spec/ADR (naming
the deleting commit);
check_spec_authority.pyowns fabricated ids intests/;trace_intel.py untraced-reqsis the advisory current-tree sweep. - Tests:
tests/scripts/test_check_requirement_corpus.py,tests/scripts/test_check_requirement_corpus_main.py.