11 Governance and quality
11.1 Profiles
Quarto-Needs separates structural findings from semantic governance. Profiles control how quality-gate failures affect automation, while structural errors remain blocking.
profile = "strict"The available profiles are default (the default), advisory, and strict. For quality and the JSON/CSV/SARIF/JUnit/Markdown exports, strict makes failed gates return an error; default and advisory keep the gate results visible without a failing gate verdict. Structural failures remain blocking in every profile.
scan and check fail on error-severity findings, but do not enforce numeric gates. A CI workflow that requires coverage thresholds should run quarto-needs quality as well. The command-line reference explains the separate exit behavior of change reports.
11.2 Rules
Rules are registered by code and can be enabled, disabled when allowed, and assigned a severity.
[rules.REQ011]
enabled = true
[rules.DEC001]
enabled = true
severity = "error"11.2.1 How a rule becomes active
Not every registered rule runs in every project. A rule reaches the findings list in one of three ways:
| Activation | Meaning |
|---|---|
| Always | Runs in every project, with or without .quarto-needs.toml. |
| Configured | Runs as soon as the configuration it depends on exists — declaring required-attributes, id-prefix, allowed-statuses, an attribute-schema, or a [relations.*] policy activates the matching rule automatically. |
| Opt-in | Runs only after an explicit [rules.<code>] enabled = true. |
The opt-in group matters in practice: decision governance and the implementation, evidence, risk, orphan, and expiry rules stay silent until the project asks for them. A project that expects DEC001 to complain about an undriven decision must enable it.
Any active rule can be turned off with enabled = false, or re-graded with severity, except where the catalog below says otherwise.
11.2.2 Rule catalog
| Code | Checks | Default severity | Activation |
|---|---|---|---|
REQ002 |
Requirement declarations should explain why they exist. | warning |
Always |
REQ004 |
Every engineering object needs a unique ID. | error |
Always (structural) |
REQ005 |
Relations must point at declared objects. | error |
Always (structural) |
REQ006 |
Approved requirements need a verification relation. | warning |
Always |
REQ008 |
Configured types may require attributes. | warning |
Configured |
REQ009 |
Configured relations restrict endpoint types. | warning |
Configured |
REQ010 |
Configured relations may constrain cardinality. | warning |
Configured |
REQ011 |
Approved requirements need an implementation-family relation. | warning |
Opt-in |
REQ012 |
Tests with status approved or passed need evidence-family relations. |
warning |
Opt-in |
REQ013 |
High/critical risks need incoming mitigation relations. | warning |
Opt-in |
REQ014 |
Objects with no relations in either direction. | info |
Opt-in |
REQ015 |
Evidence carrying an expiry attribute must still be valid. | warning |
Opt-in |
ID001 |
Object IDs may be governed by a prefix per engineering type. | warning |
Configured |
OBJ001 |
Object status must belong to the configured lifecycle for its type. | warning |
Configured |
OBJ002 |
Object attributes may be constrained by a per-type JSON Schema. | error |
Configured |
DEC001 |
Accepted decisions should address at least one engineering driver. | warning |
Opt-in |
DEC002 |
Accepted decisions should identify affected architecture elements. | warning |
Opt-in |
DEC003 |
Accepted decisions should define how conformance is confirmed. | warning |
Opt-in |
DEC004 |
Superseded decisions need explicit lineage to another decision. | warning |
Opt-in |
DEC005 |
Supersession lineage must remain acyclic. | error |
Opt-in |
DEC006 |
Accepted decisions with revisit-after dates should be reviewed when due. |
warning |
Opt-in |
ARC001 |
part-of must connect adjacent C4 layers (system → container → component → source-module). |
error |
Always |
Severity may be error, warning, or info, except where the rule restricts it: REQ004 and REQ005 are structural, so they can be neither disabled nor re-graded; REQ006 accepts only error or warning; DEC005 and ARC001 accept only error. An unsupported value is a configuration error rather than a silently ignored setting.
Decision rules use the DECxxx namespace, identification and type policies use IDxxx/OBJxxx, and architecture layering uses ARCxxx. Evidence validation uses the separate EVDxxx namespace described in Executable evidence and Machine evidence providers; those codes are reported by the evidence commands, not by [rules.*].
11.3 Relations and cardinality
Policy can restrict endpoints and cardinality:
[relations."addresses"]
allowed-source-types = ["architecture-decision"]
allowed-target-types = [
"stakeholder-need",
"system-requirement",
"non-functional-requirement",
"risk",
]
minimum-per-source = 1| Setting | Meaning |
|---|---|
allowed-source-types |
Types permitted at the declaring end. Activates REQ009. |
allowed-target-types |
Types permitted at the referenced end. Activates REQ009. |
minimum-per-source |
Fewest edges of this relation each eligible source must declare. Activates REQ010. |
maximum-per-source |
Most edges of this relation one source may declare. Activates REQ010. |
Use maximum-per-source = 1 for relations that must stay single-valued — a component belonging to exactly one container, or a decision superseding exactly one predecessor:
[relations."part-of"]
allowed-source-types = ["container", "component", "source-module"]
allowed-target-types = ["system", "container", "component"]
minimum-per-source = 1
maximum-per-source = 1Omitting a bound leaves that side unconstrained. Declaring any of these four keys activates the corresponding rule automatically; no [rules.*] entry is needed.
This turns modeling conventions into verifiable invariants.
11.4 Quality gates
[gates]
scope = "approved-requirements"
max-errors = 0
min-implementation-trace = 100.0
min-implementation-effective = 100.0
min-verification-trace = 100.0
min-verification-successful = 100.0
min-evidence = 100.0
require-risk-mitigation = trueGates are appropriate for measurable properties of a set, while rules describe localized violations on objects or relations.
| Setting | Type | Default | Meaning |
|---|---|---|---|
scope |
Named query | approved-requirements |
The population every percentage gate measures. |
max-errors |
Integer | 0 |
Maximum error-severity findings allowed in the whole project. |
min-implementation-trace |
Percent | unset | Share of the scope with an implementation-family relation. |
min-implementation-effective |
Percent | unset | Share of the scope whose implementation endpoint is not in an ineffective status. |
min-verification-trace |
Percent | unset | Share of the scope with a verification-family relation. |
min-verification-successful |
Percent | unset | Share of the scope verified by a test in a successful status. |
min-evidence |
Percent | unset | Share of the scope reaching unexpired evidence. |
require-risk-mitigation |
Boolean | false |
Requires zero REQ013 findings. |
The paired -trace and -effective/-successful gates are the difference between a link existing and that link meaning something. min-verification-trace accepts a requirement that names a test case; min-verification-successful additionally demands that the test carries a successful status. Which statuses count is [governance] policy — see Practical reference.
An unset percentage gate is not evaluated. The gate scope must be catalog, approved-requirements, or a declared named query; a misspelling is a configuration error listing valid scopes. Queries are declared at load time, even when their results depend on the graph.
A percentage gate with zero eligible requirements is unmeasured and fails by default, including a zero-percent threshold. A risk gate with no high/critical risks is likewise unmeasured. Projects intentionally starting empty may set [gates] allow-empty-scopes = true: the verdict is allowed, but the CLI says [WAIVED], JUnit records a skipped check, and Markdown identifies the waiver. Without that opt-in the CLI says [UNMEASURED]. Both explain that the scope matched no eligible objects. Missing measurements and disabled dependencies cannot be waived.
11.4.1 When allow-empty-scopes is legitimate
It is legitimate when the emptiness is a true statement about a project that has not filled in yet: a repository adopting Quarto-Needs before authoring requirements, or a new component whose gates are declared up front so that they are already in place when content arrives. The claim being waived is “we know this measures nothing right now”, and it is meant to stop being true.
It is a patch when it hides a scope that should be matching and is not. The symptom looks identical — zero eligible objects — but the cause is drift: a named query whose predicate no longer matches how statuses are written, a scope pointing at the wrong query, or a risk model that was never authored while require-risk-mitigation claims risk is managed. Waiving that converts a broken configuration into a permanent green check.
Two habits keep the two apart:
- Before waiving, confirm the emptiness by hand.
quarto-needs query <scope>returns the population; if you expected objects and got none, the scope is broken, not empty. - Treat the waiver as temporary and review it. A waiver that has outlived several releases is describing drift, not a beginning.
Note that the waiver is project-wide rather than per gate. Enabling it to accept one empty population also accepts every other empty population in the same project, so a scope that breaks later is waived without anyone deciding to waive it. This is the main reason to remove the waiver once the project has content, rather than leaving it in place because it is harmless today.
Quality JSON v1 exposes optional measurementStatus: measured, empty, or unavailable. Unmeasured gates have actual: null; the coverage metric itself still reports 100.0 for 0/0 for compatibility. max-errors measures findings from a completed analysis: zero findings is a valid zero count. Structural analysis failure blocks every profile before population gates are evaluated.
require-risk-mitigation counts findings from rule REQ013, which is opt-in. Enabling the gate alone could only ever pass, so that pair is rejected as a configuration error rather than reporting a vacuous success. Enable both:
[rules.REQ013]
enabled = true
[gates]
require-risk-mitigation = true11.5 Baseline, diff, and impact
A baseline captures the analyzed state and configuration fingerprint. Diff distinguishes added/removed objects, modified content, relocation, and relation changes. When policy changes, incompatible derived results are explicitly suppressed instead of producing misleading comparisons.
Impact analysis uses semantic direction declared in the catalog. ADR relations participate in this mechanism exactly like implementation, verification, and evidence relations.
See the command-line reference for baseline creation/inspection, comparisons with current sources, --recompute-with current, committed Git ranges, suspect claims, and PR/GitHub report generation.
11.6 Verified determinism
Determinism is a checked property, not a claim. tools/reproducibility.py runs the same analysis over the same sources several times and compares the resulting artifacts as raw bytes: needs.json, the generated Lua index, the baseline payload, every deterministic export (json, csv, markdown, reqif, jsonld), a materialized source index, and the three fingerprints compared as values in addition to bytes.
Each run is a fresh interpreter. This is not a stylistic choice: PYTHONHASHSEED is read once at interpreter start-up, so a test that sets it through os.environ in the current process measures nothing. Every variation therefore executes as a subprocess with an explicitly constructed environment rather than an inherited one.
The matrix varies the hash seed, the time zone, the collation locale, the working directory, the form of the --root argument (absolute, relative from inside the tree, relative from outside it), and the order in which sources are discovered. Each worker writes a probe recording what it actually observed, and the harness asserts those probes differ where the perturbation was supposed to bite — a locale that silently fell back to C fails the run instead of passing a test that never exercised collation. SOURCE_DATE_EPOCH freezes the reference date, so no field needs to be excluded from comparison; the allowlist of volatile fields lives in one module, is currently empty, and is locked by a reviewed digest so it cannot grow without a deliberate change.
The reproducibility workflow repeats the whole matrix on Ubuntu, macOS, and Windows for Python 3.10 through 3.14, then compares the artifacts of all fifteen runtimes against each other. Its aggregate job is required for merge.