T09: close the spec->code link mechanically (M-D1-LNK)
make coverage now reports a second number: how many claimed rules are also NAMED in the aggregate source. 58/58 tag coverage was weaker evidence than it read as, and this says how much weaker. Measured: 49 of 58. Nine rules are claimed by a scenario and appear nowhere in games/ground/src/lib.rs -- GR-D07 GR-F02 GR-L03 GR-O03 GR-P01 GR-P02 GR-P03 GR-P04 GR-T01. The gate REPORTS rather than fails, on purpose. Closing the gap by adding those IDs to comments would satisfy the check without establishing that any of the nine is implemented -- the overclaim InnerLoop implementation rule 2 exists to prevent, and one CB-WP-0001 already committed once. Each needs its implementation confirmed before it is tagged; promoting M-D1-LNK to a failing gate is correct after that, not before. Also added: a phantom check that fails when a rule id appears in code that the spec does not define (currently zero). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
4fd6322e17
commit
9e30bbb2b3
3 changed files with 58 additions and 1 deletions
|
|
@ -31,6 +31,7 @@ and add capability-specific rows only when these don't cover the claim.
|
|||
| ID | Dimension | Metric | Unit | Provenance |
|
||||
|---|---|---|---|---|
|
||||
| M-D1-COV | D1 | numbered spec rules covered by ≥1 passing scenario | % | adapted:requirements-traceability (per-rule, not per-feature) |
|
||||
| M-D1-LNK | D1 | claimed rules also **named in the aggregate source** | count | novel — closes the spec→code→scenario chain; instrument: `make coverage` (AM-1b) |
|
||||
| M-D1-SPL | D1 | spec lines per numbered rule | lines | novel — proxies statement simplicity; gameable, so paired with M-D1-COV |
|
||||
| M-D2-LOC | D2 | source LOC excluding tests (tokei) | lines | adopted:tokei |
|
||||
| M-D2-DEP | D2 | transitive dependency count (cargo tree) | crates | adopted:cargo-deny practice |
|
||||
|
|
@ -44,6 +45,32 @@ and add capability-specific rows only when these don't cover the claim.
|
|||
| M-D4-LEAK | D4 | foreign types in canonical interfaces | count | novel — must be 0; enforced by grep/deny rule, the Clay-Borg hard rule |
|
||||
| M-D4-SWAP | D4 | capability has null + reference impls passing the same conformance suite | bool | adapted:hexagonal-architecture port testing |
|
||||
|
||||
### 1b. The coverage gate's two numbers (M-D1-COV, M-D1-LNK)
|
||||
|
||||
M-D1-COV counts tags. It proves no rule is unclaimed and no claimed rule
|
||||
is invented; it does **not** prove a scenario exercises what it names, and
|
||||
that limit is printed with the number every time (InnerLoop implementation
|
||||
rule 4).
|
||||
|
||||
**M-D1-LNK** (CB-WP-0003 T09) closes one link of that chain mechanically:
|
||||
a rule a scenario claims should also appear in the aggregate source, or
|
||||
the claim rests on a tag and nothing else.
|
||||
|
||||
Measured 2026-07-31: **49 of 58** claimed rules are named in
|
||||
`games/ground/src/lib.rs`. **Unmet**, target 58. The nine unlinked:
|
||||
|
||||
```text
|
||||
GR-D07 GR-F02 GR-L03 GR-O03 GR-P01 GR-P02 GR-P03 GR-P04 GR-T01
|
||||
```
|
||||
|
||||
The gate **reports** this rather than failing, deliberately. Closing the
|
||||
gap by adding rule IDs to comments would satisfy the check without
|
||||
establishing that each rule is implemented — which is the overclaim
|
||||
InnerLoop implementation rule 2 exists to prevent, and which CB-WP-0001
|
||||
committed once already. Each of the nine needs its implementation
|
||||
confirmed before it is tagged. Promoting M-D1-LNK to a failing gate is
|
||||
correct **after** that, not before.
|
||||
|
||||
### 1a. Token cost accounting (M-D2-CST)
|
||||
|
||||
> **Superseded 2026-07-31 by [CostAccounting.md](CostAccounting.md)**, which
|
||||
|
|
|
|||
|
|
@ -26,12 +26,19 @@ import sys
|
|||
|
||||
RULE_RE = r"\*\*(GR-[A-Z]+\d+)"
|
||||
COVERS_RE = r"covers: \[(.*?)\]"
|
||||
AGGREGATE = "games/ground/src/lib.rs"
|
||||
ID_RE = r"GR-[A-Z]+\d+"
|
||||
|
||||
|
||||
def parse_rules(spec_text):
|
||||
return sorted(set(re.findall(RULE_RE, spec_text)))
|
||||
|
||||
|
||||
def parse_code_ids(text):
|
||||
"""Rule IDs named anywhere in the aggregate source (T09)."""
|
||||
return set(re.findall(ID_RE, text))
|
||||
|
||||
|
||||
def parse_covers(text):
|
||||
match = re.search(COVERS_RE, text, re.S)
|
||||
if not match:
|
||||
|
|
@ -58,6 +65,12 @@ def self_test():
|
|||
check("covers matcher works", parse_covers("covers: [GR-R06, GR-A12]")
|
||||
== {"GR-R06", "GR-A12"})
|
||||
check("missing covers yields empty set", parse_covers("no covers key") == set())
|
||||
# T09: the spec -> code link must be detectable.
|
||||
check("code-id matcher finds ids in source",
|
||||
parse_code_ids("// GR-R06: lead first\nfn f(){} // GR-A12")
|
||||
== {"GR-R06", "GR-A12"})
|
||||
check("code-id matcher finds none in unmarked source",
|
||||
parse_code_ids("fn f() { let x = 1; }") == set())
|
||||
|
||||
print("rule-coverage self-test (positive control)")
|
||||
ok = True
|
||||
|
|
@ -94,10 +107,27 @@ def main():
|
|||
missing = [r for r in rules if r not in covered]
|
||||
invented = sorted(covered - known)
|
||||
|
||||
# T09: the spec -> code -> scenario chain, made mechanical. A rule a
|
||||
# scenario claims should also be named in the aggregate, or the claim
|
||||
# rests on nothing but a tag.
|
||||
code_ids = parse_code_ids(open(AGGREGATE).read())
|
||||
unlinked = sorted((known & covered) - code_ids)
|
||||
phantom = sorted(code_ids - known)
|
||||
|
||||
pct = 100 * len(hit) // len(rules)
|
||||
linked = len((known & covered) & code_ids)
|
||||
print(f"AM-1 rule coverage: {len(hit)}/{len(rules)} ({pct}%) "
|
||||
f"over {len(paths)} scenarios")
|
||||
print(f"AM-1b spec->code link: {linked}/{len(hit)} claimed rules also "
|
||||
f"named in {AGGREGATE}")
|
||||
print(" NOTE: counts tags; does not prove a scenario exercises what it names")
|
||||
if unlinked:
|
||||
print(" unlinked (claimed by a scenario, absent from the aggregate):")
|
||||
print(" ", " ".join(unlinked))
|
||||
if phantom:
|
||||
print(" ERROR — rule id in code that the spec does not define:",
|
||||
" ".join(phantom), file=sys.stderr)
|
||||
return 1
|
||||
if missing:
|
||||
print(" uncovered:", " ".join(missing))
|
||||
if invented:
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ file must list them, not that the build breaks.
|
|||
|
||||
```task
|
||||
id: CB-WP-0003-T09
|
||||
status: todo
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "69ccc9ec-dc35-481c-8065-cef040f07f50"
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue