The round builder's row claimed "et sitat bærer ANTALLET siterte steder. Load-bearing
MÅLT" for a half that was not measured: the mutant dropping the count survived the whole
suite. The claim is true from today, and the row now says from WHEN — a ledger that
back-dates a measurement is worth less than one that admits the gap.
The new row records what the checkpoint found and what it cost to close: seven mutants
survived, none of them because the code was wrong, all of them because no arm looked.
Three were unreachable rather than merely unmeasured — the fixture wrote three of seven
artefact types, and every proposal carried the same citation stamp, so "1 av 1 siterte
steder" could not tell a dropped count from a kept one.
The denominator is written with its method, not as a number to be remembered: over the
four archived runs, -06/-07/-08 hold all seven types and -04 holds six (parse-failures is
written only when something failed to parse, so its absence is the signal). Union = 7,
from two commands.
And the citation cause is recorded as a measurement rather than a diagnosis, because
"the builder reads the wrong field" and "the outbox says the same thing five times"
want opposite fixes: one sha256 across all five proposals, in all four runs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Repo convention: every measured decision gets a row in the ledger with the load-bearing test that
turns red when the decision is undone. The row records the three choices the gate states in its
own output (CLI door over MCP, no budget ceiling in toolbox mode, row 6 never green without the
operator), the base64 reason, and the eight mutants that fell in the scratch clone — including
M-6, which shows row 3's green is a measurement and not a vacuous zero: without the
fenced-block rule one prose line in a research doc turns the row red.
v1 gate re-measured after the work: 0/3 · 0/3 · 3/8 · ingen rapport · 3/8 · IKKE MÅLT · 1/20 —
unchanged. PLAN.md § Ferdig-kriteriet untouched, as the order required.
Suite after git add: 2106 passed / 5 skipped / 5 xfailed, rc=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
python -m portfolio_optimiser.evals.round_builder --outbox <dir> --round <n> --ran-at <ISO>
writes <rounds-dir>/<n>/ with the run's artefacts COPIED in, outcome.json derived from that
copy, and report.md -- the one artefact in a round a domain expert reads and corrects. Round 0
of the v1 criterion can now be made; it counted 0 of 3 because it could not be, which is a
different failure from a round nobody had held.
What it derives it derives with the gate's own functions rather than a second copy: verify_run
decides whether the run stands up to itself (an artefact contradicting its coverage row, a
half-missing family and a stray artefact are all refused AT THE SOURCE, before a byte is
written), stage_of gives column (c), row_changed gives the report's "changed since the previous
round", parse_time refuses a stamp without a zone, safe_rounds_dir refuses a round directory the
repo would commit. The validated total is ledger.to_ore per amount, summed as integers.
Two things it never does, and both are the point. It never writes the operator's attestation --
the gate stops at FORM OK without one, and that is correct, because no arrangement of files can
witness that a run happened. And it never invents: --ran-at is required because no outbox
artefact carries a clock, and feedback_ids stays empty because no run records which feedback
item produced which row. The report says "ingen tilbakemelding forklarer dette" on every changed
row rather than hiding that model noise and an answered objection look alike.
Chosen and why: --ran-at as a required argument rather than the coverage file's mtime, because
an mtime is a filesystem attribute one call sets and reading it as evidence made row 2 green on
a tree nothing had run in (18.09). The report carries no raw stage identifier -- every stage
sentence is "<short name>: <explanation>" so the one-line diff of what changed has words a
reader can act on. A citation shows its COUNT, because a run that cited 446 places and one that
cited one must not look the same.
[skip-docs]: the ledger row is in docs/invarianter.md, which is where this repo's rules live.
README is the product's front door and this is an operator tool behind `python -m`, the same
class as costsim/hitl/preflight, which README deliberately does not carry; v1-rounds/ is
gitignored internal machinery and the gate itself is not in README either. CLAUDE.md was emptied
of exactly this kind of row in session 130 and is not the place to put one back.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CLAUDE.md had grown to 310 919 bytes against Claude Code's 150 000-character
injection limit, so every row past the cut reached no session. The 93 measured
rows move to docs/invarianter.md in their original order; CLAUDE.md keeps the
eight short standing rules and a pointer, and says new rows are written there.
Verified as a partition: every moved line appears in the original section in
order, the eight kept rows likewise, and head/tail of CLAUDE.md are byte-
identical apart from the visitor note. One code span is reworded and the ledger
head says so: the funn 99 row cited MAF's DEFAULT_MAX_CONSECUTIVE_ERRORS_PER_
REQUEST with its value inside the span, which the doc-constant-sync gate reads
as a citation of a constant in this package (fail-closed on an unknown name).
The ledger is registered in _LIVE_DOCS, so that gate now also guards it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>