Invariants
Six rules hold across all three stages. Each one names the file that enforces it, because a rule with no named enforcer is a rule someone will break without anyone noticing.
Where a rule is enforced by an agent instruction rather than by a script, this page says so. That distinction is the useful one: an instruction is a rule the model follows, and a script is a rule the pipeline cannot proceed past.
One artifact per file
Every requirement, component, interface and ADR is one Markdown file with one
YAML frontmatter block, named <ID>-<kebab-title>.md and placed in the
subdirectory its type owns.
Atomicity is what makes the rest of the pipeline work. A traceability edge addresses a file; a critic can reject one requirement without re-opening a document; a diff shows which artifact changed rather than which paragraph.
Enforced by: plugin/agents/requirements-formatter.md,
plugin/agents/design-formatter.md and plugin/agents/qa-formatter.md — the
three agents that decide the path and the filename, per type. The structural
validators enforce the contents of that
arrangement: plugin/lib/artifact_core.py’s parse_frontmatter reads exactly one
frontmatter block per file, discover_files treats every .md that is not a
named project-level companion as an atomic artifact, and
cross_file_checks in each validator rejects a duplicate ID across the set.
Note the seam honestly: neither validator compares the filename to the id
inside it, so a correctly-formed artifact under a wrong filename passes the
gate. The naming convention is a formatter contract, not a gated one.
IDs are categorical, zero-padded, and allocated only by an orchestrator
The prefix encodes the type — FR, NFR, CON, BR, UC in requirements;
CMP, IF, ADR in design; TS in QA — followed by a three-digit
zero-padded sequence. No specialist invents an ID.
Single allocation is what keeps the two ends of every traceability edge agreeing. If two specialists could both mint IDs they would eventually mint the same one, and neither would be wrong.
Enforced by: the schemas, structurally —
plugin/skills/requirements/schema/requirement.schema.json pins
^(FR|NFR|CON|BR|UC)(-[A-Z0-9]+)*-[0-9]{3,}$ and
plugin/skills/qa/schema/qa.schema.json pins ^TS(-[A-Z0-9]+)*-[0-9]{3,}$,
and each validator’s cross_file_checks rejects an ID whose prefix disagrees
with its own type field. Sole allocation is an orchestrator instruction:
plugin/agents/requirements-orchestrator.md Stage 3,
plugin/agents/design-orchestrator.md Stage 4, and
plugin/agents/qa-orchestrator.md Stage 3, each of which states it as “the
only ID authority” (or “the single authority for ID allocation”) and makes an
IF- ID appearing in a component draft — or a specialist minting its own
TS- ID in QA — a re-dispatch rather than a repair.
IDs are never reused
A deleted artifact’s ID stays retired. The artifact is marked
status: obsolete rather than removed, and the next allocation continues past
it.
An ID that means one thing in a commit and another thing later makes every reference to it ambiguous — including references in code, tests and conversation, which no validator can see.
Enforced by: plugin/agents/requirements-orchestrator.md:103,
plugin/agents/design-orchestrator.md:177 and
plugin/agents/qa-orchestrator.md:232, as an instruction. It is not gated,
because a validator that could enforce it would need the history rather than
the set. The design orchestrator’s retired Stages 11 and
12 are the same principle applied to stage numbers, and are
what the rule looks like when it is honoured.
The critic gates judgment; the formatter gates structure
The critic runs before anything is written and judges quality. The structural validator runs inside the formatter, against the files it has just written.
This is not a preference about where to put a check. The critic cannot run
the structural validator: at gate time there are no files, and in the
requirements stage assumptions.md and glossary.md — which that validator
hard-gates — are not even assembled until the stage after the gate passes.
Folding the validator into the formatter rather than placing it after also
means one agent both writes the set and answers for its structure.
Enforced by: plugin/agents/requirements-formatter.md’s “Verify, then report”,
plugin/agents/design-formatter.md’s “Validator re-run” and
plugin/agents/qa-formatter.md’s “Verify, then report” sections, which run
validate_requirements.py, validate_design.py and validate_qa.py
respectively (plus validate_traceability.py in design and QA) as part of the
write contract.
All three stages’ SKILL.md files state the invariant explicitly, and they say
it in that form because stating it wrongly has cost twice: STO-215 and STO-207
were both agent files describing a structural check at a point where the files
it checks did not yet exist.
Low-confidence artifacts are triaged, never silently accepted
An artifact the pipeline is unsure of is written with confidence: low and
listed in index.yaml’s review_queue with a one-line reason, so a human can
triage the uncertain items without opening every file.
Uncertainty is a normal output of all three stages, and each downstream stage
inherits the one before it’s queue rather than clearing it — the design stage
inherits the requirements stage’s, and the QA stage inherits the design
stage’s inherited_review_queue in turn. Several of those items are decisions
that correctly landed in an earlier stage’s out-tray and are still unresolved.
Enforced by: plugin/agents/design-formatter.md and
plugin/agents/qa-formatter.md, which require review_queue to agree exactly
with the confidence: low set and report a review_queue_count each must
match; and each stage’s SKILL.md, which surfaces the same list in the
sign-off summary.
A contradiction worth knowing about. plugin/agents/requirements-formatter.md:130
says the formatter MAY emit index.yaml, and that the index MAY carry a
review_queue. plugin/skills/requirements/SKILL.md:182 describes the formatter
writing both as a matter of course, every generated example set has them, and
plugin/skills/design/SKILL.md reads that review_queue as an input to the design
stage. Neither structural validator gates the file — both skip index.yaml as
a project-level companion — so nothing catches the disagreement. Filed as
STO-269 #1; it is recorded here rather than resolved, because which way it
resolves is a decision, not a typo.
Generated reference data is committed, and CI fails on drift
Everything under site/content/_generated/ is derived from the repository’s
own sources, committed alongside them, and re-derived by CI on every pull
request. A change to a source that is not accompanied by a regenerated file
reddens CI.
This is the tax that makes the generated half of this site trustworthy. The site build itself runs no Python — it reads the committed JSON — so the drift gate is the only thing standing between a source edit and a published page that contradicts it.
Enforced by: .github/workflows/ci.yml, in the Generated reference is not stale step, which runs the exporter’s --check mode and exits 1 naming each
stale file.
You will trip it by editing any of these:
- a rule in
lint_design_content.py,lint_requirements_content.pyorvalidate_traceability.py— theirRULESregistries arerules.json; - a field in any of the three JSON Schemas — they are
fields.json; - an agent’s frontmatter
description:— the roster isagents.json; - any
SKILL.md’s coverage-area list — those arestages.json; - a stage heading or its hand-off YAML in any orchestrator — those are
pipeline.json, and the pages in this section.
The fix is one command:
python3 site/scripts/export_reference.pyThe worked example sets have their own gate, export_examples.py --check, and
their own command, python3 site/scripts/export_examples.py.
The exporters fail loudly rather than degrading. A parser that returned an empty result when its source moved would produce committed JSON that still matched what the parser currently emits, and the gate would stay green over a page that had quietly stopped saying anything.