K11 is implemented: crates/cb-events/src/store.rs, magic + version header, 4-byte little-endian length prefix, append-only. Reimplemented not assimilated per ADR-0005 §2 — no new dependency, and AM-4a/AM-4b are unchanged at 246,250 / 317,021 because nothing entered the graph. The operative clause is "detected", so corruption is tested rather than assumed: a tail short by one byte, a half-written length prefix, a length prefix corrupted to claim more than the file holds, foreign magic, and a future format version are each rejected with a distinct error. A reader that accepts a truncated tail is worse than no format, because it silently returns a short history that looks complete. AM-11 is earned. LogStore has two impls — MemLogStore and FileLogStore — driven through ONE conformance(). The trait carries raw/set_raw precisely so the corruption controls live in the shared suite: a format contract that only one impl enforces is not a contract. The same shape is retro-fitted to KernelRng, which is what AM-11 actually names: ChaChaRng and NullRng now pass one suite asserting bounds, draw(1) == 0, determinism across fresh instances, and shuffle preserving the multiset. They were previously exercised by two separate tests, which is why "met, narrow" was never earned and ADR-0005 §4 downgraded it. K9 gets the assertion it did not have: snapshot at seq N + events N+1..M must equal the from-genesis fold, hash-compared, on GroundState, single-seed on purpose — AM-7's probe folds a multi-seed log, which is not a replay of anything, and that defect is not repeated. Two positive controls: the log must exceed 50 events, and the mid-log snapshot must differ from the end state or "apply the remainder" is vacuous. Proof it works: the exact mutation that SURVIVED in CB-WP-0005 — making Snapshot::take discard its EventSeq — now fails on the K9 assertion. AM-11's mutation breaks NullRng::draw to return its bound and the shared suite fails. That is what M-D4-SWAP claims — either impl substitutable — and exactly what two separate per-impl tests could never demonstrate. M-D1-MUT: 7 -> 8 of 14. CB-EV-0001's scoreboard is refreshed: AM-2, AM-5 and AM-9 added, AM-6 moved to enforced, and the headline total corrected from 4 to 8 — it had gone stale inside the same workplan that produced it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
270 lines
13 KiB
Markdown
270 lines
13 KiB
Markdown
# CB-WP-0006 — delivery log
|
||
|
||
Per-task delivery notes, moved out of
|
||
[the workplan](../workplans/CB-WP-0006-instrument-the-table.md) when it
|
||
crossed the ~400-line loadability limit for the third time in this
|
||
project. The workplan holds the plan; this holds what happened. T08
|
||
draws on both.
|
||
|
||
## CB-WP-0006-T01
|
||
|
||
**Delivered.** `am6_throughput_clears_the_spec_target` in
|
||
`games/ground/src/lib.rs` — a test, not a bench. Best of 3 samples of
|
||
50,000 applied events each.
|
||
|
||
Measured on bnt-lap001 2026-07-31: **341,280 ev/s in debug (3.4× the
|
||
target)**, ~2.4–3.1M in release (~24–30×). **The spec target holds even in
|
||
an unoptimized build**, so the gate needed no `cfg` split and runs in the
|
||
ordinary `make test`.
|
||
|
||
**The trap was avoided by construction, not by intention.** The threshold
|
||
is the spec value `100_000`, untouched; the constant carries a comment
|
||
saying lowering it requires an ADR; and the failure message repeats that,
|
||
states the measured headroom, and names the reference figures — so a
|
||
future agent hitting a red AM-6 is told not to tune it, in the place they
|
||
will actually be reading. Robustness comes from **best-of-N**, not from a
|
||
lower bar: a throughput *floor* asks "is this machine capable", so
|
||
transient load should not fail the build.
|
||
|
||
Two positive controls in the test itself: a run that applied fewer than
|
||
50,000 events, or measured zero elapsed time, fails rather than scoring as
|
||
infinite throughput.
|
||
|
||
**Verified:** `make mutation-check --row AM-6` → **red**, via a *property*
|
||
mutation (4,000 `black_box` iterations injected into `GroundState::fold`,
|
||
the hot path) rather than a threshold tweak — raising the target would only
|
||
prove the comparison runs.
|
||
|
||
**And the FA class is now gated.** `mutation-check` rows gained an
|
||
`expect` field: the mutant's output must contain the row's stated failure
|
||
string, or the verdict is **`WRONG-REASON`**, not `red`. Without it, a
|
||
mutation that merely failed to compile would credit its row with an
|
||
assertion it does not have. Verified by pointing `expect` at a string the
|
||
verifier never prints and confirming the verdict flips. This is
|
||
remedy (2) from the CB-WP-0005 retrospective, built one task earlier than
|
||
T08 planned because the class it guards is the newest and the most
|
||
dangerous.
|
||
|
||
**M-D1-MUT: 4 → 5 of 14.**
|
||
|
||
## CB-WP-0006-T02
|
||
|
||
**Delivered — but the two rows resolved differently, and the difference is
|
||
the point.**
|
||
|
||
**AM-2 is instrumented and enforced.** `tools/size-metrics.py` +
|
||
`make size-metrics`, in `make all`:
|
||
|
||
```text
|
||
AM-2: 27.2 LOC/rule [ok target <= 40] (1.47x headroom)
|
||
1,575 code lines before the first #[cfg(test)] / 58 numbered rules
|
||
```
|
||
|
||
Tests are excluded because AM-2 asks what a rule *costs*, not how much it
|
||
is exercised — `lib.rs` is ~18% test code and including it would have
|
||
flattered the number. Verified red by a **property** mutation: ~800 lines
|
||
of filler injected into the impl, pushing the ratio past 40. `expect` is
|
||
the precise failure signature `FAIL target <= 40`, not the row name, which
|
||
would have matched passing output too.
|
||
|
||
**AM-3 is BLOCKED, not uninstrumented — and this is a finding, not a
|
||
deferral.** The row measures "LOC to express the CB-RES-0001 synthetic
|
||
game on our kernel" against a boardgame.io baseline of ~36 LOC for a
|
||
declarative **3p commit/reveal game object**. That artifact has never been
|
||
built: `games/` contains only `ground`, and `benches/synthetic.rs`
|
||
*drives* GROUND rather than *defining* a synthetic game.
|
||
|
||
Measuring GROUND's 1,575 impl lines against a 36-line synthetic game
|
||
object would compare **two different games** and call the difference a D1
|
||
result. So the tool ships the measurement mechanism — a marker-delimited
|
||
`// AM-3:BEGIN` / `// AM-3:END` region, self-tested — and **reports the row
|
||
blocked, naming the missing artifact**. A number here would have been
|
||
worse than a blank.
|
||
|
||
It therefore stays `unmutatable` and **still counts against M-D1-MUT**, per
|
||
ADR-0005 §1: a row that cannot fail asserts nothing, however good the
|
||
reason. Resolving it needs an artifact, not a metric tweak — carried
|
||
forward, not silently dropped.
|
||
|
||
**M-D1-MUT: 5 → 6 of 14.**
|
||
|
||
## CB-WP-0006-T03
|
||
|
||
**Delivered — `tools/runtime-metrics.py`. One passes, one breaches.**
|
||
|
||
**AM-9: met, and comfortably.** 13.4 MB peak RSS against a ≤64 MB target,
|
||
**4.8× headroom**. Gated and in `make all` (`--fast`, ~1 s). The row that
|
||
CB-EV-0001 called "very unlikely to bind" was right — but it is now
|
||
*measured* rather than assumed, and verified red by a property mutation
|
||
(a 300 MB allocation in the workload).
|
||
|
||
**AM-5: ~~BREACHED~~ MET — the first reading was wrong.**
|
||
|
||
> **Corrected 2026-08-01 (during T04).** T03 reported 87.0 s / 61.3 s and
|
||
> called AM-5 a 45% breach. **It is not.** Re-measured on a quiet machine
|
||
> with the fixed instrument:
|
||
>
|
||
> ```text
|
||
> load before measuring: 0.14 per CPU over 8 CPUs — quiet
|
||
> dev toolchain (default features) 37.3 s [ok target <= 60 s]
|
||
> best of 3: 37.3, 42.9, 46.2 (spread 1.24x)
|
||
> shipped runtime (--no-default-features) 41.2 s [ok target <= 60 s]
|
||
> best of 3: 41.2, 50.8, 54.2 (spread 1.32x)
|
||
> ```
|
||
>
|
||
> **1.6× headroom, comfortably met.** The 87.0 s was measured while the
|
||
> machine was busy running `mutation-check` and cargo builds — a timing
|
||
> measurement under contention measures the contention. That is the *same
|
||
> error class as AM-6's*, committed two tasks later in the same session,
|
||
> by the same author, in the row immediately after the one where it was
|
||
> diagnosed.
|
||
>
|
||
> The instrument now (a) reads `/proc` load average and **refuses to
|
||
> measure** above 0.5 per CPU, and (b) takes the **best of 3** — best, not
|
||
> worst, because a build-time *ceiling* asks "can this machine do it in
|
||
> 60 s", the mirror of AM-6's best-of-N for a throughput *floor*. It also
|
||
> warns when the spread exceeds 1.25×, which fired on the shipped-runtime
|
||
> samples: consecutive clean builds degrade (37.3 → 46.2), so even a quiet
|
||
> machine is not a uniform one.
|
||
|
||
The original, now-withdrawn finding follows for the record:
|
||
|
||
```text
|
||
dev toolchain (default features) 87.0 s [FAIL target <= 60 s]
|
||
shipped runtime (--no-default-features) 61.3 s [FAIL target <= 60 s]
|
||
```
|
||
|
||
The tool **reports and exits 0**, because the spec says the row is not
|
||
gated (and on the corrected number there is nothing to escalate anyway). Gating it is a spec change and needs an ADR; a tool that promotes
|
||
itself is how a target starts binding without anyone deciding it should.
|
||
So AM-5 stays `unmutatable` — it cannot fail while the spec declares it
|
||
ungated, which is the accurate reason. **The escalation is withdrawn**:
|
||
there is no breach to decide about.
|
||
|
||
A build profile was investigated anyway while the breach was believed
|
||
real, and the findings stand on their own: 51 compile units, 174 s of CPU
|
||
work compressed into ~54 s wall, **parallelism only 3.2× on 8 cores**
|
||
because `proc-macro2 → syn → serde_derive → serde_core` is a ~22 s serial
|
||
chain. `lto = "thin"` costs ~6 s (55.8 → 49.4 s mean) and is the only real
|
||
lever; pinning `ppv-lite86` down to drop `zerocopy` (the largest single
|
||
unit, 21.8 s) makes it **worse** — 23 → 25 crates, because the older
|
||
version pulls `zerocopy-derive`. Recorded here rather than acted on: with
|
||
1.6× headroom there is nothing to buy.
|
||
|
||
**The measurement had a real bug, found by cross-validation.**
|
||
`getrusage(RUSAGE_CHILDREN)` is a high-water mark across *every* reaped
|
||
child, so it attributed `cargo`'s memory to the workload and reported
|
||
**38.2 MB for a run that used 12.3 MB** — a 3× over-report. Fixed with
|
||
`os.wait4`, which returns that specific child's rusage. The self-test now
|
||
**cross-checks against `/usr/bin/time -v`** (13.4 vs 12.4 MB), which is
|
||
the only reason the bug was visible at all: the wrong number was
|
||
plausible, passed its target, and would have been published.
|
||
|
||
That is FA in the measurement layer rather than the mutation layer — an
|
||
instrument confidently reporting a number it had not earned.
|
||
|
||
**M-D1-MUT: 6 → 7 of 14.**
|
||
|
||
## CB-WP-0006-T04
|
||
|
||
**Delivered: withdrawn from the acceptance table, retained as a
|
||
diagnostic.** `specs/GameKernel.md` §5a carries the argument.
|
||
|
||
**The ratio has no monotone better direction.** INTENT's rule is *own the
|
||
semantics; assimilate the implementation*. Rising can mean owning
|
||
semantics properly, or reimplementing what should have been assimilated.
|
||
Falling can mean good leverage, or dependency bloat. A target requires
|
||
knowing which way is better, and this metric does not.
|
||
|
||
**It is also redundant:** AM-4a/AM-4b bound the denominator and AM-2 bounds
|
||
own-source density per rule. AM-4c is a ratio of two already-targeted
|
||
quantities.
|
||
|
||
Measured at withdrawal: **1,426** own lines per 100k third-party (shipped),
|
||
**1,107** (dev). `make dep-weight` now prints both, labelled
|
||
`diagnostic, not targeted` — it was never actually reported before.
|
||
|
||
**M-D1-MUT keeps AM-4c in its denominator, deliberately**, and says so in
|
||
the output. Dropping it would move the score 7/14 → 7/13 without enforcing
|
||
anything: a score improved by deleting the question.
|
||
|
||
### A T01 correction found here
|
||
|
||
**The AM-6 gate failed inside `make all` at 38,753 ev/s against 341,280
|
||
measured in isolation — a 9× drop.** `cargo test` runs test binaries and
|
||
threads concurrently, so **a throughput assertion inside a parallel
|
||
harness measures contention, not throughput.** T01's measurement was valid
|
||
and its gate placement was not.
|
||
|
||
Fixed by running the measurement only where it is valid — `#[ignore]`,
|
||
plus `make am6` in release with `--test-threads=1`, now **2.0M ev/s,
|
||
20.2× headroom** — and *not* by lowering the target, which T01 explicitly
|
||
forbade and which would have reproduced the defect being fixed. My first
|
||
attempt did drift that way (a debug "sanity floor" of 50,000) and was
|
||
backed out: a second threshold is still a second chance to tune.
|
||
|
||
**And the mutation went `SURVIVED` on the first run after the move** —
|
||
4,000 `black_box` iterations were calibrated against debug's 3.4×
|
||
headroom and are invisible against release's 20×. Raised to 100,000; back
|
||
to `red`. A weak mutation is not a fixed property of a row: **it can
|
||
become weak when the row's measurement conditions change.**
|
||
|
||
**M-D1-MUT: 7 of 14** (unchanged — AM-4c was always going to stay
|
||
uncounted; what changed is that the reason is now correct and recorded).
|
||
|
||
## CB-WP-0006-T05
|
||
|
||
**Delivered: K9's real assertion, K11's durable format, the `LogStore`
|
||
port, and the shared conformance suites that finally earn AM-11.**
|
||
|
||
**K11 — `crates/cb-events/src/store.rs`.** Magic + version header, 4-byte
|
||
little-endian length prefix per record, append-only. Reimplemented, not
|
||
assimilated (ADR-0005 §2): no new dependency, charged to AM-4a, and
|
||
AM-4a/AM-4b are unchanged at 246,250 / 317,021 because nothing was added
|
||
to the graph.
|
||
|
||
The operative clause is **detected**, so corruption is tested, not assumed:
|
||
a tail short by one byte, a half-written length prefix, a length prefix
|
||
corrupted to claim more than the file holds, foreign magic, and a future
|
||
format version are each rejected with a distinct error. A reader that
|
||
accepts a truncated tail is worse than no format, because it silently
|
||
returns a short history that looks complete.
|
||
|
||
**The port and the suite (AM-11).** `LogStore` has two impls —
|
||
`MemLogStore` and `FileLogStore` — and **one** `conformance()` that both
|
||
are driven through. The trait carries `raw`/`set_raw` specifically so the
|
||
corruption controls live in the *shared* suite: a format contract only one
|
||
impl enforces is not a contract.
|
||
|
||
The same shape was retro-fitted to `KernelRng`, which is what AM-11
|
||
actually names. `ChaChaRng` and `NullRng` now pass one
|
||
`conformance()` asserting the properties true of both — bounds,
|
||
`draw(1) == 0`, determinism across fresh instances, and shuffle preserving
|
||
the multiset. Previously they were exercised by two *separate* tests,
|
||
which is precisely why `AM-11 | met, narrow` was never earned and ADR-0005
|
||
§4 downgraded it.
|
||
|
||
**K9 — the assertion it did not have.** *Snapshot at seq N + events
|
||
N+1..M ≡ genesis fold*, hash-compared, on `GroundState`, single-seed on
|
||
purpose (AM-7's probe folds a multi-seed log, which is not a replay of
|
||
anything; that defect is not repeated). Two positive controls: the log
|
||
must exceed 50 events, and the mid-log snapshot must **differ** from the
|
||
end state, or "apply the remainder" would be vacuous.
|
||
|
||
**Proof it works:** the exact mutation that *survived* in CB-WP-0005 —
|
||
making `Snapshot::take` discard its `EventSeq` — now fails:
|
||
|
||
```text
|
||
assertion `left == right` failed: K9: the snapshot must carry the EventSeq it includes
|
||
test result: FAILED. 0 passed; 1 failed
|
||
```
|
||
|
||
**AM-11 mutation:** break `NullRng::draw` to return its bound and the
|
||
*shared* suite fails. That is what M-D4-SWAP claims — that either impl can
|
||
be substituted — and it is exactly what two separate per-impl tests could
|
||
never demonstrate.
|
||
|
||
**M-D1-MUT: 7 → 8 of 14.** `evidence/CB-EV-0001`'s scoreboard is refreshed
|
||
with AM-2, AM-5 and AM-9 added, AM-6 moved to enforced, and the headline
|
||
total corrected from 4 to 8 — it had gone stale within the same workplan
|
||
that produced it.
|