Put the plugin API post-v1, and let the matrix count it that way
The register said two things about plugins. §7 had listed "Plugin API" as deferred since the first draft, in a bare row; §3.10 then specified it in 23 clauses that counted against coverage. Twenty-one of them had no implementation of any kind, and could not have: no crate loads anything at runtime. The coverage figure was measuring the contradiction. Decided 2026-09-19: §7 is right. §3.10 stays as the design of record, each of its clauses is marked "(post-v1)" on its defining line, and NFR-SEC-6 — which exists only for plugins — goes with them, as does D16. The traceability tool learns the marker. A deferred requirement is still defined, so a tag naming it is not an orphan, but it leaves the denominator and is listed in its own table rather than under "not yet tagged". The marker must sit on the definition line; a mention of "post-v1" in prose changes nothing, and where an ID is defined twice the deferral on either line wins. Both are tested. Coverage moves from 72.2% of 194 to 80.6% of 170 without a line of application code changing, which is the honest figure: it now measures what v1 owes.
This commit is contained in:
@@ -238,6 +238,9 @@ fn print_summary(
|
||||
println!("files scanned {}", files.len());
|
||||
println!("tags found {}", entries.len());
|
||||
println!("requirements {}", defined.total());
|
||||
if !defined.deferred.is_empty() {
|
||||
println!("deferred {} (post-v1)", defined.deferred.len());
|
||||
}
|
||||
println!(
|
||||
"coverage {:.1}% ({}/{})",
|
||||
cov.percent, cov.covered, cov.total
|
||||
@@ -260,13 +263,19 @@ fn render(
|
||||
m.push_str(
|
||||
"Denominators are parsed from [`requirements.md`](requirements.md) at run time, \
|
||||
never hardcoded. Coverage is the intersection of tagged and defined IDs over \
|
||||
defined IDs, so it cannot exceed 100%.\n\n",
|
||||
defined IDs, so it cannot exceed 100%. A requirement whose definition line carries \
|
||||
`(post-v1)` is defined but deferred: outside the denominator, listed below, and \
|
||||
never an orphan.\n\n",
|
||||
);
|
||||
|
||||
m.push_str("## Summary\n\n| Metric | Value |\n|---|---|\n");
|
||||
m.push_str(&format!("| Source files scanned | {} |\n", files.len()));
|
||||
m.push_str(&format!("| TRACES tags found | {} |\n", entries.len()));
|
||||
m.push_str(&format!("| Requirements defined | {} |\n", defined.total()));
|
||||
m.push_str(&format!(
|
||||
"| Requirements deferred (post-v1) | {} |\n",
|
||||
defined.deferred.len()
|
||||
));
|
||||
m.push_str(&format!("| Requirements covered | {} |\n", cov.covered));
|
||||
m.push_str(&format!(
|
||||
"| **Coverage** | **{:.1}%** ({}/{}) |\n\n",
|
||||
@@ -324,6 +333,31 @@ fn render(
|
||||
}
|
||||
m.push('\n');
|
||||
|
||||
m.push_str("## Deferred (post-v1)\n\n");
|
||||
m.push_str(
|
||||
"Defined in `requirements.md` and marked `(post-v1)` on the defining line. Not in \
|
||||
the denominator; a tag naming one is recorded here rather than counted.\n\n",
|
||||
);
|
||||
if defined.deferred.is_empty() {
|
||||
m.push_str("_None._\n\n");
|
||||
} else {
|
||||
for id in &defined.deferred {
|
||||
if cov.deferred_tagged.contains(id) {
|
||||
let mut locs: Vec<String> = entries
|
||||
.iter()
|
||||
.filter(|e| e.requirements.contains(id))
|
||||
.map(|e| format!("[`{}:{}`](../{}#L{})", e.file, e.line, e.file, e.line))
|
||||
.collect();
|
||||
locs.sort();
|
||||
locs.dedup();
|
||||
m.push_str(&format!("- {id} — tagged in {}\n", locs.join(", ")));
|
||||
} else {
|
||||
m.push_str(&format!("- {id}\n"));
|
||||
}
|
||||
}
|
||||
m.push('\n');
|
||||
}
|
||||
|
||||
m.push_str("## Not yet tagged\n\n");
|
||||
m.push_str(&format!(
|
||||
"{} of {} requirements have no implementation tag. Expected while the \
|
||||
|
||||
Reference in New Issue
Block a user