Tier-L survey plus the full review trail. v1 was REJECTED by adversarial
review with four blocking findings; this is v2, and the finding below is
the reviewer's as much as mine.
The root cause is neither v1's story ("coverage scope is too narrow" —
true but shallow) nor the reviewer's cheaper one ("the link check points
at one file" — true but catches 3 of 7). It is:
Every coverage instrument in this project counts NAMES. None counts
ASSERTIONS. M-D1-COV counts covers: tags; M-D1-LNK counts rule-ID
strings. Both answer "is this rule mentioned?" and neither answers
"does anything fail if this rule is violated?"
Four of seven defects found this pass are named in the source and inert,
so no name-based check finds them. This subsumes the oldest error class:
all five prior harness-does-nothing instances have the same shape.
Two are mutation-proven, because the reviewer set that bar for findings
they had only read:
AM-7 — folding a 100k-event log from fresh(999) instead of fresh(42),
an unrelated genesis state, leaves the test green. The hash reaches
only a println!; the sole assertion is on elapsed time. And the log is
built across games seeded 42,43,44... so it could not be asserted as
written. evidence/CB-EV-0001 reports "AM-7 replay met, 2,290x" —
unearned on the hash-identical clause. HDN instance #6.
K9 — making Snapshot::take discard its EventSeq and store 0, which
destroys the half of K9 that says "+ the EventId it includes", leaves
the test green. On the acceptance criterion GameKernel §2.4 is zero of
three, not two of three.
Also found: K10, K14, K18 named nowhere in source; AM-10 vacuous (a K6
determinism lint reported under a D4 leak row, over a cb-*-api crate that
does not exist); AM-11's conformance suite does not exist; AM-1b measured
and omitted from the evidence scoreboard.
All twelve challenge findings conceded, none defended on its facts. v1's
prediction was rejected as unfalsifiable AND already determined; the
replacement names its refuting outcome and is beatable in both
directions.
Chaos d4=1, no override. Declaration 1 of the 12-declaration window.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
14 KiB
CB-RES-0004: the instruments count names, not assertions
capability: meta.coverage.assertions + kernel.replay
status: approved (v2, after adversarial review — v1 was rejected with four
blocking findings)
tier: L (structural L — creates a new capability port (durable log
storage); changes what several headline acceptance numbers mean; chaos
d4=1 → no override)
instrument: extended make coverage (spec→code over every numbered spec)
plus a new make mutation-check and make replay-test
baseline: cited — boardgame.io, Rune, rr/Pernosco, Playwright trace,
event-sourcing log framing
Survey opened 2026-07-31. v1 was rejected; the review trail is
history/260731-replay-kernel-coverage-{research,challenge,response}.md.
The finding below is the reviewer's as much as mine.
1. The finding
Every coverage instrument in this project counts names. None counts assertions. M-D1-COV counts
covers:tags in scenario YAML. M-D1-LNK counts rule-ID strings in one source file. Both answer "is this rule mentioned?"; neither answers "does anything fail if this rule is violated?"
Seven defects were found this pass. Four of them are named in the source and unasserted — invisible to any name-based check, including the cheaper one the reviewer proposed:
| defect | named in code? | a name-based check finds it? |
|---|---|---|
K10 replay bundle — no writer, reader, or --replay |
no | yes |
K14 commit window — CommitWindow has zero non-test users |
no | yes |
| K18 benches from scenarios — bench hardcodes commands | no | yes |
K9 snapshot + the EventId it includes |
yes | no |
| K11 durable format, truncated-tail detection | yes | no |
AM-7 hash-identical |
yes | no |
AM-11 passing the same conformance suite |
yes | no |
This subsumes the project's oldest error class. All five prior harness-does-nothing instances have the same shape as AM-7 and K9 today: the name is there, the assertion is not.
1.1 The two that are mutation-proven
AM-7's hash-identical clause detects nothing.
games/ground/src/lib.rs computes state_hash_hex(&state) and passes it
to a println!. The only assertion is on elapsed time. Mutating the
fold's genesis from fresh(42) to fresh(999) — replaying a 100k-event
log into an unrelated starting state — leaves the test green:
test replay_probe::replay_100k_events_is_linear_and_fast ... ok
The hash could not be asserted as written anyway: the log is built across
games seeded 42, 43, 44… (source = fresh(42 + games)) and folded into a
state from fresh(42). It is a fold-rate microbenchmark over a
semantically invalid stream, reported in evidence/CB-EV-0001 as
AM-7 replay | met, 2,290×. That verdict is unearned on the
hash clause and needs a correction note. HDN instance #6.
K9's through field is unverified. The whole evidence for K9 is one
test round-tripping a BTreeMap<String, u8> with EventSeq(17) as a
literal — no game aggregate, no events applied, no from-genesis
comparison. Snapshot has no callers outside that test. Mutating
Snapshot::take to discard its EventSeq and store EventSeq(0) leaves
the test green.
On the acceptance criterion, GameKernel §2.4 is zero of three, not two of three.
1.2 The three that are named nowhere
spec: K1..K18 code: K1..K9 K11 K12 K13 K15 K16 K17
diff: K10, K14, K18
- K10 — no
.cbreplaywriter, reader, orreplays/directory; the onlyfs::call in the workspace isread_to_stringfor scenario YAML.cb-simhas no flag parsing at all, so--replayhas no place to go.MetricsAndScenarios§2 promises a failing run writes a bundle. It does not. INTENT names this design decision 8 of 10. - K14 — "one commit window per Select".
CommitWindowhas zero non-test users andgames/grounddoes not import it; GROUND collects selections in its own aggregate. A dead abstraction certified by a test that exercises only itself. - K18 — "Criterion benches driving the same scenario format". The
bench hardcodes commands in Rust and never touches
ScenarioFile.benchmarks/contains onlybaselines/model-prices.toml.
1.3 The scoped-denominator sweep
v1 claimed this problem was general and swept in one place. Swept properly, in descending order of severity:
AM-10 is vacuous — the population is empty. The row reads
M-D4-LEAK: foreign types in cb-*-api-visible signatures | 0. There is
no cb-*-api crate. What is actually measured is a clippy.toml deny of
HashMap/HashSet whose stated reason cites K6 (determinism),
reported under a D4 leak row. A determinism lint standing in for a
leak metric, over a crate that does not exist.
AM-1's denominator is one spec, and the metric says otherwise.
M-D1-COV is defined as "numbered spec rules covered by ≥1 passing
scenario", and covers is documented as "numbered rules from the
capability spec" with a non-GR example. RULE_RE matches GR- only,
against GroundRules.md only. So 58/58 (100%) is not merely narrowly
scoped — the instrument is non-conformant with its own metric
definition, and 100% is a wrong answer to the question M-D1-COV asks.
AM-1b is measured and dropped from the scoreboard. make coverage
prints 49/58 and MetricsAndScenarios records it Unmet;
grep -c 'AM-1b' evidence/CB-EV-0001 returns 0. The artifact carrying
the headline reports the flattering half of the gate and omits the
unflattering half the same command prints two lines below it.
AM-11's suite does not exist. M-D4-SWAP requires impls "passing the
same conformance suite". grep -rn conformance over all .rs returns
one doc comment describing future work. The RNG pair is exercised by two
separate, non-shared tests. met, narrow is unearned on the suite clause.
AM-2, AM-3, AM-5, AM-9 are unreported and were unmentioned by v1.
The history file's stated weakness — "the evidence for this is n=1" — is retired. It is n≥5 in this repo, found without leaving the tree.
2. Baseline — what the state of the art does
Cited, not locally measured; per MetricsAndScenarios §3 this caps
comparison verdicts at parity.
| system | durable log | exportable failure bundle | machine re-executable |
|---|---|---|---|
| boardgame.io | in-memory, optional server persistence | debug panel, in-session time travel | no |
| Rune | rollback ring buffer, transient | no | no |
| rr / Pernosco | full process recording | yes — the gold standard | yes, bit-exact |
| Playwright trace | per-run trace zip | yes, one file | partly (UI, not logic) |
| Kafka / EventStore | append-only, length-prefixed, versioned, checksummed | n/a | yes, by re-consumption |
Two lessons:
- The valuable property is machine re-execution, not inspection.
boardgame.io's debug panel is more visible than a
.cbreplayfile and far less useful to an agent, because a human must interpret it. rr and Playwright are the precedents worth taking. - The log format is solved engineering. Length prefix, version byte, per-record framing, tail detection: Kafka and EventStore converged on the same shape decades apart. It is ~80–120 lines.
Provenance. Log framing: adapted:event-sourcing-log-framing —
length-prefixed versioned records with tail detection, dropping the
distributed concerns (partitions, offsets, compaction) a single-game log
does not have. Bundle: adapted:playwright-trace — one artifact per
failed run containing everything needed to reproduce, adapted so the
consumer is an agent, not a human viewer. Assertion coverage:
adapted:mutation-testing — mutate the property, expect a red test;
applied only to acceptance rows rather than exhaustively.
3. The four dimensions
D1 — ease of specification. Strongly positive. A .cbreplay bundle
turns "the scenario failed" into an artifact with a stated contract. More
importantly, mutation coverage makes "this rule is enforced" a claim with
a test rather than a tag.
D2 — efficiency of implementation. The log format adds no dependency
(serde is already shipped). The bundle writer is not "a directory of
four files" — v1 said that and it was wrong. scenario.rs creates an
EventLog, appends to it, and never reads it; it is dropped at the end of
execute. Pass carries the end state, not an initial snapshot, and
failures are a formatted String, not structured expected-vs-actual. The
bundle requires changing the runner's data flow: plumb the log out, capture
an initial snapshot, restructure RunOutcome::Failed.
D3 — speed of execution. Bundle writes happen only on failure, so the hot path is untouched. Log append gains a length-prefix write. Mutation checks run in CI, not in the loop.
D4 — optionality. Where the pass earns its L, on a corrected premise.
v1 argued AM-11 "claims" a storage port; it does not — that is the target
column, and the evidence honestly reports 1 of 1, met, narrow. The real
argument is stronger: M-D4-SWAP is a bool over a conformance suite that
does not exist. Building a durable-log port with a genuine shared suite
discharges AM-11 properly and creates the seam stage 3 (networked
sessions) and stage 4 (game-creation framework) both need.
4. Options
A — implement K10/K11, leave the instruments alone. Rejected. Fixes what was noticed; leaves the mechanism that hid it.
B — extend coverage to K-rules, defer the rest. Rejected as a final state. Adopted as ordering.
C — adopt an embedded store (sled, redb). Rejected — and v1 rejected
it against the wrong budget. The durable log (K11) is shipped-runtime,
so AM-4a applies with 1.5% headroom; the bundle writer (K10) is dev-only
behind the scenarios feature, so AM-4b applies with 9.4%. v1 wanted it
both ways without saying so. With the split stated, C is rejected on
simplicity for the log — a length prefix is not a hard implementation, and
"assimilate the implementation" is for hard ones — and the headroom
argument is dropped for the bundle, where it never applied.
E — extend AM-1b's spec→code link to every numbered spec and every
crate. ~10 lines against an instrument that already exists. Catches
K10, K14, K18 immediately. Adopted as step one — and demonstrably
insufficient, since it catches 3 of the 7 defects and none of the four
mutation-class ones. AGGREGATE must become a list.
F — E, plus mutation checks on acceptance rows, plus K10/K11. Recommended. The name-based check is cheap and catches the absent; the mutation check is the only thing that catches the present-but-inert; the capability work discharges the rules both instruments expose.
5. What this pass must not do
- Do not tag K-rules as covered to make a number go up. The overclaim CB-WP-0001 committed once and M-D1-LNK exists to catch. A K-rule is covered when breaking it breaks a test.
- Do not let the widened coverage gate fail the build on day one.
rule-coverage.pyreturns2whenmissingis non-empty andcoverageis inall, so 18 uncovered K-rules failmake allimmediately. The kernel denominator must report outside the exit code until the pass closes the gap, with a stated date for when it binds. - Replicate the zero-rules positive control on the new denominator.
The existing arm refuses to report over zero rules — a defect it was
fixed for. A kernel regex matching nothing must abort, not print
0/0 (100%). make replay-testmust not be able to pass vacuously. It reportsokunder at least four silent failures: no scenario fails so zero bundles are round-tripped; the writer emits nothing and identical error strings satisfy "same failure"; the replayed hash is compared to one recomputed in the same process (assert_eq!(h, h)— the AM-7 defect exactly); the truncation path is never exercised. Required controls: a committed deliberately-failing fixture plusbundles > 0; the comparison hash read out of the bundle, written by the first process; truncate-by-one-byte and corrupt-length-prefix negative controls; and a mutated-seed negative control so the round-trip is capable of failing.
6. Prediction
Stated as a mechanism per CB-WP-0004 T06, and with the refuting outcome named — v1's prediction was rejected for being unfalsifiable and already determined (any tag-based kernel count is 0 of 18 today, computable before writing a line).
| claim | mechanism after the fix | refuted if |
|---|---|---|
| absent rules become visible | spec→code runs over every numbered spec × every crate; no manual path | any numbered rule in any spec is unnamed in source after the pass |
| present-but-inert rules become visible | mutation: each acceptance row's property is inverted and the suite must go red | fewer than 9 of the 12 acceptance rows have a mutation that turns a test red |
| replay is real | acceptance test re-executes a bundle in a fresh process against a hash read from the bundle | the replayed hash is recomputed in-process, or the round-trip cannot be made to fail |
| K9/K11 are enforced | snapshot-at-N + events N+1..M ≡ genesis fold on GroundState; truncation rejected |
either passes when mutated |
The middle row is the one that carries information. 9 of 12 is a guess, and it is meant to be beatable in both directions — if 12 of 12 mutate red, the instruments were better than this survey claims and the finding shrinks to the three absent rules; if 3 of 12 do, the problem is larger than the pass is scoped for and the pass should stop and re-plan.