Commit graph

13 commits

Author SHA1 Message Date
1edadac9a2 CB-WP-0006 T06: K10 replay bundles, --replay, and AM-7 re-earned
INTENT design decision 8 of 10, unimplemented for six passes. cb-sim had
no flag parsing at all, so --replay had nowhere to go.

The bundle is manifest + commands.log + initial.snapshot + expected.yaml,
dev-only behind the scenarios feature and charged to AM-4b. The command
stream goes through the K11 framing built in T05, so a truncated bundle is
detected rather than replayed short — the two tasks compose rather than
duplicating.

The reviewer's D2 correction was real: this was not "a directory of four
files". Pass carried only the end state, RunOutcome::Failed was a
formatted String, and scenario.rs created an EventLog, appended to it and
never read it. All three had to change.

The first round trip failed to reproduce, and the cause is worth keeping:
state_hash_hex over a serde_json::Value is a different canonical form than
over the typed aggregate — Value's map is key-sorted, a struct serializes
in declaration order. The bundle was written with one basis and verified
with the other. A round trip written to recompute its own comparison value
would have PASSED this bug; it failed because the recorded hash came from
the producing process, which is control 2's entire purpose.

make replay-test implements ADR-0005 §6's four controls, 14/14: a
committed deliberately-failing fixture outside the corpus with covers: []
so it neither fails `make sim` nor inflates AM-1; a tampered recorded hash
must fail; a log short by one byte and a corrupted length prefix must be
rejected; and a mutated manifest seed must fail — which bites only because
replay re-derives the initial state from seed+setup and checks it against
the recorded snapshot, since restoring from the snapshot alone would leave
the seed inert. Plus a control on the controls: the bundle must still
replay after every mutation is reverted.

AM-7's hash-identical clause is re-earned. The probe records a hash per
per-game segment and replays each from its own genesis; folding from the
wrong seed now fails. That is the clause ADR-0005 §4 withdrew as
mutation-proven inert. The scaling >= 0.9x clause is still unenforced, so
AM-7 stays PARTIAL — reported, not rounded up.

Kernel coverage 15/18 -> 16/18. facts-check immediately caught the spec's
copy of that number going stale, on a number that moved the same hour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 11:05:37 +02:00
98c6cd24c3 CB-WP-0006 T05: K9's assertion, K11's format, and the AM-11 suites
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>
2026-08-01 10:50:52 +02:00
db6445ae37 CB-WP-0006 T04: withdraw AM-4c; and fix where AM-6 is measured
AM-4c is withdrawn from the acceptance table and retained as a reported
diagnostic. GameKernel §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 leverage or dependency bloat. A target requires knowing
which way is better. It is also redundant — AM-4a/AM-4b bound the
denominator and AM-2 bounds own-source density, so 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 — the
row was never actually reported before.

M-D1-MUT keeps AM-4c in its denominator on purpose 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.

Decided before Phase B deliberately, since ADR-0005 predicts own-source
growth that will move this ratio; deciding after would be the retarget
§Step 4 forbids.

A T01 correction found here. The AM-6 gate failed inside `make all` at
38,753 ev/s against 341,280 in isolation — a 9x drop, because cargo test
runs binaries and threads concurrently. A throughput assertion inside a
parallel harness measures contention, not throughput. T01's measurement
was valid; its gate placement was not.

Fixed by running it only where valid — #[ignore] plus `make am6` in
release with --test-threads=1, now 2.0M ev/s at 20.2x headroom — and not
by lowering the target, which T01 forbade. My first attempt did drift that
way, adding a debug "sanity floor" of 50,000, and was backed out: a second
threshold is still a second chance to tune.

The mutation then went SURVIVED on the first run after the move. 4,000
black_box iterations were calibrated against debug's 3.4x headroom and are
invisible against release's 20x. 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.

Tier S (amends one row, creates no capability), chaos d4=2, no override.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:06:00 +02:00
e7312b3b8a CB-WP-0006 T03: measure AM-5 and AM-9; AM-5 breaches
AM-9 is met and gated: 13.4 MB peak RSS against a 64 MB target, 4.8x
headroom, in `make all` via --fast. CB-EV-0001's "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 is BREACHED on both readings, on the machine the spec names:

  dev toolchain (default features)        87.0 s  [FAIL target <= 60 s]
  shipped runtime (--no-default-features) 61.3 s  [FAIL target <= 60 s]

bnt-lap001, 8 cores — a direct comparison, not a directional one. A row
declared "recorded not gated" and never recorded fails its own target by
45% on first measurement.

The tool reports and exits 0 because the spec says the row is ungated.
Gating it is a spec change needing an ADR; a tool that promotes itself is
how a target starts binding without anyone deciding it should. So AM-5
stays unmutatable — for the accurate reason now — and the breach is raised
as a maintainer decision: speed the build, move the target by ADR (arguing
why 60 s was wrong rather than why 87 s is convenient), or withdraw the
row.

The measurement itself had a real bug, found only 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 3x over-report that was plausible, passed its
target, and would have been published. Fixed with os.wait4, which returns
that specific child's rusage, and the self-test now cross-checks against
/usr/bin/time -v.

That is the false-accusation shape in the measurement layer rather than
the mutation layer: an instrument confidently reporting a number it had
not earned.

Also: the clean build measures into a throwaway CARGO_TARGET_DIR rather
than running `cargo clean`, so measuring the metric does not cost several
minutes of rebuild afterwards. A metric that punishes its own measurement
gets measured once and never again.

M-D1-MUT: 6 -> 7 of 14.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 18:59:32 +02:00
1c3019c7e8 CB-WP-0006 T02: instrument AM-2; report AM-3 blocked, with the argument
Both rows were unmutatable for the same stated reason. They resolved
differently, and the difference is the point.

AM-2 is instrumented and enforced — tools/size-metrics.py, in `make all`:

  AM-2: 27.2 LOC/rule  [ok  target <= 40]  (1.47x headroom)
        1,575 impl lines / 58 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. This matters because AM-2 is AM-1's anti-gaming pair: 100%
rule coverage means nothing if the rules are trivially small, and AM-1 has
been reported met since CB-WP-0001 with its pair uninstrumented.

Verified red by a property mutation — ~800 lines of filler injected into
the impl, pushing the ratio past 40 — not a threshold tweak. The expect
string is the precise failure signature "FAIL target <= 40"; my first
attempt used "AM-2", which also matches passing output and would have
made the FA guard vacuous.

AM-3 is BLOCKED, not uninstrumented, and that is a finding rather than a
deferral. It 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 — a marker-delimited region, self-tested
— and reports the row blocked, naming the missing artifact. A number would
have been worse than a blank. It stays unmutatable and still counts
against M-D1-MUT per ADR-0005 §1: a row that cannot fail asserts nothing,
however good the reason.

M-D1-MUT: 5 -> 6 of 14.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 18:38:15 +02:00
c43754f0fe CB-WP-0006 T01: assert the AM-6 throughput target
Nothing in the workspace compared any number to 100,000 events/s while
the evidence file reported "AM-6 | met, 16.5x". Now a test does — a test,
not a bench, because Criterion reports throughput and asserts nothing,
which is why this row measured nothing for six passes.

Measured on bnt-lap001: 341,280 ev/s in debug (3.4x the target), ~2.4-3.1M
in release. The spec target holds even in an unoptimized build, so the
gate needs no cfg split and runs in the ordinary `make test`.

The trap this task named — loosening a flaky timing assertion until it
never fires — is avoided by construction. The threshold is the spec value,
untouched; the constant says lowering it requires an ADR; and the failure
message repeats that, states measured headroom, and names reference
figures, so an agent hitting a red AM-6 is told not to tune it in the
place they are actually reading. Robustness comes from best-of-N, not from
a lower bar: a throughput floor asks whether the machine is capable, so
transient load should not fail the build.

Two positive controls in the test: a run that applied fewer than 50,000
events, or measured zero elapsed time, fails rather than scoring as
infinite throughput.

Verified by a PROPERTY mutation — 4,000 black_box iterations injected into
GroundState::fold, the hot path — not a threshold tweak, which would only
prove the comparison runs.

And the FA class found last pass 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 watching the verdict flip. This is remedy (2)
from the CB-WP-0005 retrospective, built a task earlier than planned
because the class it guards is the newest and most dangerous.

M-D1-MUT: 4 -> 5 of 14.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 18:32:16 +02:00
e25bac3af3 CB-WP-0005 T02: M-D1-MUT — 4 of 14 acceptance rows are enforced
For each acceptance row in GameKernel §5, invert the property and require
the verifying command to go red. adapted:mutation-testing, with the
denominator changed from source lines to acceptance rows.

  M-D1-MUT: 4/14 rows enforced
    PARTIAL       2   (AM-7, AM-8 — some clauses live, some inert)
    unmutatable   8   (no property to invert, reason stated per row)
    SURVIVED      0

Two corrections to our own numbers. There are 14 rows, not the twelve
ADR-0005 and CB-WP-0005 both asserted — AM-4 splits into a/b/c. And the
prediction of 9-of-12 (75%) becomes >=10 of 14; measured 4 (29%), badly
unmet. No target moved in this commit.

The second correction matters more. My first run reported two SURVIVED
rows and both were my own no-op mutations: `pub struct NullRng;` ->
`pub struct NullRng {}` is semantically identical, and renaming
max_age_days does nothing because CA-17 reads it with a default of 90.
Both would have been published as "this row asserts nothing" — a false
accusation against code that is fine. Replaced with real inversions (a
per-construction counter in the ChaCha seed; reverting AC-9's output
resolution to the first-wins bug it was fixed for), after which both go
red. T08 asks whether writing a weak mutation is the new grep. It is,
demonstrably, on the first attempt.

The finding is larger than the workplan assumed. 8 of 14 rows are
unmutatable — AM-2, AM-3, AM-4c, AM-5, AM-6, AM-9, AM-10, AM-11 have no
instrument at all. AM-6 is the sharpest: nothing in the workspace
compares any number to 100,000 events/s, the headline throughput claim.
The problem is not three unimplemented rules, it is that more than half
the acceptance table has nothing behind it.

Harness controls: a stale find-string reports HARNESS-BROKEN rather than
scoring the baseline as the mutant; a red baseline reports inconclusive
rather than red; the tree is restored in a finally and the restoration is
verified. Not in `make all` — it rebuilds the workspace once per row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 17:27:23 +02:00
7a374d37d2 CB-WP-0004 T05: control loop — 6 points recovered, not 25-30
cb-cost gains --since, so the baseline (--pin 578dcbe, 662 responses,
$135.60) and this pass (--since 578dcbe, 83 responses, $7.82) are two
disjoint windows over the same transcripts. Every verdict is on share of
pass, since the windows differ 17x in size.

Test 1 — did mechanical turns disappear? Partly. Mechanical share fell
38.2% -> 32.4%. Six points against a predicted 25-30; reported unmet, and
no target was moved in this commit.

  environment setup   11.5% -> 0.6%   (85 turns -> 1)   met, decisively
  hub + workplan      8.5%  -> 0.0%   (46 turns -> 0)   met
  text patching       10.4% -> 9.6%                     not met
  orientation         5.1%  -> 21.2%                    not met, worse

The confound is stated before any defence of the numbers: this is the
pass that built the tools, and the two categories that missed are exactly
the two whose tools were under construction. The clean test is the next
pass, and it is carried forward rather than waived.

Test 2 — did the work relocate? Not into prose. Output tokens per
response fell 896 -> 681. Output's rising share of cost is a shrinking
denominator, not more writing. Cost per response halved and the evidence
refuses to claim it: that is compaction (mean context 232,982 ->
117,822), and attributing it to tooling would repeat CB-WP-0002's
original error in a new direction.

Test 3 — did quality hold? Yes, recorded as explicit judgment. make all
green with two gates that did not exist before, and four findings
surfaced this pass, three caught by controls written this pass — one of
them a 5.2x attribution error that would have reached the hub as a
measured number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:27:09 +02:00
b52a9ec88a CB-WP-0004 T02: make task-done — close a task on measured numbers
Replaces the three hand-done steps of a task close (46 turns, $11.52 per
CB-RES-0003): the heredoc flipping status in the workplan file, the
hand-written hub call, and the hand-typed token counts.

The third is the reason this task exists. Every update_task_status this
repo produced carried estimated tokens_in/tokens_out — in a project whose
central finding is that estimated token counts are worthless. task-done
reads the measured figure from the transcripts, or refuses; there is no
path through it that emits an estimate.

cb-cost gains by_task_detail: cost, response count, model histogram and
token components per task. task-done imports cb-cost rather than parsing
its printed table, so the hub figure is not a copy that can drift from
its source.

The positive control found a real defect before the tool ran once.
Attribution keyed on a bare T\d\d from the commit subject, so CB-WP-0002
T01, CB-WP-0003 T01 and CB-WP-0004 T01 shared a bucket: the self-test
reported $12.10 for "T01" where the qualified figure is $2.33. That 5.2x
overstatement would have been pushed to the hub as a *measured* number —
the same fiction in a new form. task_label() now keys qualified subjects
on the full id and leaves unqualified ones bare rather than
retro-assigning them to a workplan. The pinned $93.15 benchmark is
unchanged, so historical attribution was not disturbed.

Fourth instance of trusted arithmetic: a number believed because a
program produced it rather than a hand.

Refusals, all exercised by --self-test: unknown id, typo'd id,
already-done task, missing state_hub_task_id, no measured spend, and a
status flip that produced no change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:17:51 +02:00
7e21df378a CB-RES-0003 + CB-WP-0004: 38% of pass cost is mechanical turns
Some checks failed
ci / check (push) Failing after 4s
Review of where token-priced turns did work a deterministic tool could
do. Method: classify every turn in both transcripts by the tool calls it
made. The classifier is committed in tools/cb-cost.py and emitted by
`make cost-mix`, so the baseline is reproducible and the same command
can later falsify the predictions.

  mech environment setup       84 turns  $15.33
  mech ad-hoc text patching    75 turns  $13.86
       git                     37 turns  $13.85
  mech hub task status         25 turns  $ 7.46
  mech orientation / inspect   49 turns  $ 6.87
       hub other               32 turns  $ 6.22
  mech ad-hoc transcript       39 turns  $ 4.56
  mech workplan status edit    21 turns  $ 4.06
  MECHANICAL (dedup)          290 turns  $51.26  = 38% of pass

Largest category is `cd` and `export PATH` -- pure friction, and
dep-weight.py already patched it at the leaf, which is evidence it was
noticed and fixed in the wrong place. Second is heredocs string-patching
markdown, which is also the mechanism behind duplicated-fact drift, the
error class InnerLoop v1.2 names and cannot gate.

Explicitly NOT automated: git (37 turns, $13.85) is mostly commit
message authorship -- the highest-output turns in the corpus and the
project's reasoning record. Automating it would save money and destroy
what makes corrections cheap.

CB-WP-0004 implements five candidates and predicts $33-41 recovery
(25-30%), below the 38% measured share on purpose: some inspection and
patching is genuinely exploratory.

The control loop is the deliverable, not a formality. T05 tests three
things and must report all: did mechanical turns disappear, did they
RELOCATE into prose, and did quality hold. If mechanical turns fall and
prose rises by as much, the saving is zero and that is the result to
publish.

Also a self-indictment worth recording: every hub update_task_status in
this project carried hand-typed token estimates, in a repo whose central
finding is that estimated token counts are worthless. T02 fixes it by
reading measured values from cb-cost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:49:57 +02:00
db731aa0bc T11: dated rates and staleness become data, ahead of the 2026-08-31 flip
The price sheet had two defects of one shape -- a schema that could not
hold the fact it needed, the same criticism the cost survey levelled at
the State Hub.

  CA-16  time-boxed rates are DATA. Sonnet's intro price lived in a
         `# intro ...` comment and was invisible to the collector that
         reads the file. Now promo_input/promo_output/promo_until,
         applied per response at its own timestamp.
  CA-17  the 90-day staleness rule was prose in MetricsAndScenarios 1a
         that every M-D2-CST verdict silently inherited. Now `recorded`
         + `max_age_days` in the sheet, and a stale sheet ABORTS.

Both are exercised by make cost-test: the promo rate must apply before
2026-08-31 and lapse after, and a 102-day-old sheet must trip.

Applying CA-16 moved AC-1 from $93.32 to $93.15 -- the $0.17 CB-EV-0002
predicted, now collected rather than noted. That is a legitimate
retarget under T07's distinction: the instrument disproved the target,
and its output is in this commit. The number has now been stated five
times ($248.46, $92.21, $92.87, $93.32, $93.15), each correction from a
different mechanism.

Evidence tables regenerated from the tool rather than hand-patched,
per CA-15 -- which is the rule that exists because hand-typed tables
were the only thing the adversarial review found wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:23:29 +02:00
06628e83e1 T05: live cost budget replaces the dead token budget
The 8k/10k per-task token budget was never referenced or enforced and
T08 blew past it silently. Replaced with a budget that can actually
fire.

The design constraint is the interesting part: per-task cost needs the
commit that CLOSES the task, so a per-task budget is unavoidably
retrospective -- it can only report a breach after the money is spent,
which is the dead-policy failure again. What IS observable mid-task is
spend since the last commit, because the transcript is append-live. So
the budget binds on the open remainder.

  CB-01  budget = USD since the last commit, via `make cost-budget`
  CB-02  soft $10.00 (state progress, decide), hard $22.00 (stop)

Calibrated on the 32 non-empty commit intervals of CB-WP-0001: p50
$1.40, p90 $9.36, max $10.80. Soft sits just below the observed maximum
-- it would have fired exactly once on the calibration pass. Hard is ~2x
the observed max, a value never reached in 32 intervals, so reaching it
means the session is doing something the data has no example of.

Both thresholds are set ABOVE every observed value, so they bind on
future work rather than ratifying present work -- the distinction T07
is about.

Stated limit: it is a command, not a daemon. An agent that never runs it
gets no signal, which is the dead-policy failure one level up. Mitigated
only by being free to run and on the one command surface.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 09:21:30 +02:00
c0eef604e8 T05: CB-EV-0002 — all 9 metrics met, and /compact measured at 3.1x
AC-1 $93.32 ($92.21 main + $1.11 subagent), residual $0.000000. No
unmeasured rows. Composition: 88.0% cache, 11.9% output, 249:1 context
re-read to text written.

The question that could not be answered before: what /compact costs.
The transcript records it directly — 542,991 -> 19,974 tokens, a 27x
reduction. Cost per turn fell from $0.457 (136 turns before) to $0.149
(202 turns after). The 202 post-compact turns cost less than half the
136 before them.

This qualifies CB-WP-0003 T04: sessions are bounded-quadratic, not
quadratic. Cost grows with context between compactions and resets at
each one, so the failure mode is a long UNCOMPACTED session. Whether a
fresh session beats a compaction is now measurable, and T04 should
measure it before prescribing one task per session.

Stale $0.66/$92.87 targets updated across survey and workplan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 08:51:50 +02:00