CB-WP-0006 T07: implement K18, amend K14

Two rules, two different answers, which is the point of a task phrased
"implement, or amend and say why".

K18 is implemented. "Criterion benches driving the same scenario format at
scale" was false — the bench hardcoded its commands and never touched
ScenarioFile, while MetricsAndScenarios §3 pointed at a benchmarks/
directory containing only baselines/. benchmarks/synthetic-3p.yaml now
holds the workload and both the bench and bench_shape read it: the
workload is data, not code.

A second defect surfaced while fixing the first. After the bench switched
to the file, bench_shape still hardcoded the same sequence, so the
workload existed twice — deleting end_round from the YAML broke bench-test
while bench_shape kept passing. Duplicated-fact drift in executable form.
Both now read the same include_str! and deleting a command breaks both.

Explicitly not claimed: this does not unblock AM-3. AM-3's baseline is a
declarative game object — moves, turn order, rules. synthetic-3p.yaml is a
command list; the rules live in games/ground. Marking it as AM-3's subject
would compare a script to a game definition, which is the category error
AM-3 is blocked on. The file says so in its own header, where the next
person will be tempted.

K14 is amended. CommitWindow had zero non-test users and GROUND enforces
the same contract inline. Wiring GROUND through it was rejected: it would
change the serialized shape of `selections`, which four scenario files
assert by dot-path and every state hash depends on, for the sole benefit
of making a sentence literally true.

The deciding argument is INTENT's, not convenience: abstractions are
extracted from working games rather than invented in isolation, and no
concept becomes canonical until it survives a second concrete use.
CommitWindow was invented before any game needed it and has survived none.
Imposing it on GROUND would manufacture the first use rather than discover
it. So K14 states what is actually guaranteed, CommitWindow is marked
provisional in the source, and it carries a delete-by date of 2026-12-31.

Kernel spec->code link 16/18 -> 18/18, stated with the caveat the gate
prints every run: that is about names, not assertions.

Two self-tests broke and both broke correctly. rule-coverage's gate test
hardcoded "unlinked rules exist today" and failed when the last one was
linked; it now computes that and asserts the gate fails iff rules are
unlinked. facts' text check rejected k_unlinked once it became
legitimately empty; empty now renders as "(none)" and the check
distinguishes absent from empty.

M-D1-MUT: 8 of 14, unchanged — K14 and K18 are kernel rules, not
acceptance rows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-01 12:47:16 +02:00
parent 4e82a520b3
commit 327bda64ab
11 changed files with 256 additions and 105 deletions

View file

@ -334,3 +334,80 @@ immediately caught the spec's copy of that number going stale — the DFD
gate earning its place on a number that moved the same hour.
**M-D1-MUT: 8 of 14** (unchanged; AM-7 remains partial).
## CB-WP-0006-T07
**Two rules, two different answers — which is the point of a task phrased
"implement, or amend and say why".**
### K18: implemented
"Criterion benches driving the same scenario format at scale" was false —
the bench hardcoded its command sequence in Rust and never touched
`ScenarioFile`, while MetricsAndScenarios §3 pointed at a `benchmarks/`
directory containing only `baselines/`.
`benchmarks/synthetic-3p.yaml` now holds the workload, and both the bench
and `bench_shape` read it. **The workload is data, not code.**
**A second defect surfaced while fixing the first.** After the bench
switched to the file, `bench_shape` still hardcoded the same sequence — so
the workload existed **twice**, and deleting `end_round` from the YAML
broke `bench-test` while `bench_shape` kept passing. That is
duplicated-fact drift in executable form. Both now read the same
`include_str!`, and deleting a command breaks both:
```text
assertion `left == right` failed: commands per synthetic round
```
**Explicitly not claimed:** this does *not* unblock AM-3. AM-3's baseline
is a declarative **game object** (~36 LOC, boardgame.io) — moves, turn
order, rules. `synthetic-3p.yaml` is a command list; the rules live in
`games/ground` (1,575 lines). Marking it as AM-3's subject would compare a
script to a game definition, which is the category error AM-3 is blocked
on. The file says so in its own header, where the next person will be
tempted.
### K14: amended, and the reasoning is recorded because it could have gone
the other way
`CommitWindow` had **zero non-test users**; GROUND enforces the same
contract inline. Wiring GROUND through it was rejected: it would change
the serialized shape of `selections`, which four scenario files assert by
dot-path and every state hash depends on — a large, risky refactor whose
only benefit is making a sentence literally true.
The deciding argument is INTENT's, not convenience: *"abstractions are
extracted from working games... rather than invented in isolation"*, and
*"No concept becomes canonical merely because it looks general. It becomes
canonical after surviving a second concrete use."* `CommitWindow` was
invented before any game needed it and has survived **zero** uses.
Imposing it on GROUND would **manufacture** the first use rather than
discover it.
So K14 now states what is actually guaranteed, `CommitWindow` is marked
**provisional in the source**, and it carries a **delete-by date of
2026-12-31** if no second game uses it. A primitive with one hypothetical
user and a test that exercises only itself is the AM-11 shape, and this
project has now paid for that shape twice.
### Consequences
**Kernel spec→code link: 16/18 → 18/18 (100%).** Stated with the caveat
the gate prints on every run: that is a statement about **names**, not
assertions.
Two self-tests broke and both broke *correctly*:
* `rule-coverage`'s gate test hardcoded "unlinked rules exist today" and
failed the moment the last one was linked. It now computes whether
anything is unlinked and asserts the gate fails **iff** so — the
assertion was right, its premise was transient.
* `facts`' "every fact records a value and its rendered text" rejected
`k_unlinked` once it became legitimately empty. Empty now renders as
`(none)`, and the check distinguishes *absent* from *empty*.
**M-D1-MUT: 8 of 14, unchanged.** K14 and K18 are kernel rules, not
acceptance rows, so neither moves that number — worth stating, because the
temptation is to imply progress on a metric this task never touched.