Commit graph

5 commits

Author SHA1 Message Date
c1bdb37b86
docs(invarianter): the B gate's row, rewritten around the denominators that turned out to be its own
The row claimed three things the checkpoint measured as false: that every denominator comes from
the source (row 1's M=13 was a curated list in the gate's own b_gate.json), that the surface is
what is published (it was a hand list of eleven roots seeing 433 of 512 files), and that the gate
counts an MCP-registered door as callable (there was no code for it — "kind" appeared 0 times).
The 435-file figure was the working tree, two gitignored .local.md files included, so it was not
reproducible from the commit either.

Rewritten to what is true after the repair, with the old numbers kept as history so the row reads
as a correction and not as a clean slate: 3 of 17 · 0 of 2 · 9 of 9 · 0 of 3 · 4 of 5 · IKKE MAALT.
The markdown-fence rule's own measurement is refreshed too: on the bigger surface it now carries
TWO prose lines, not one — the second is a plan document quoting a command the operator ran in his
own session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 19:53:20 +02:00
f78d57a7b7
docs(invarianter): the seven that survived, and the denominator they hid behind
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>
2026-09-19 10:28:25 +02:00
207337c8a2
docs(invarianter): the B gate's row — what it measures, what it refuses to conclude
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>
2026-09-19 10:02:39 +02:00
db38cfc1a4
feat(round-builder): one command turns a run's outbox into a round the gate can read [skip-docs]
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>
2026-09-19 06:09:12 +02:00
98f1a771f3
docs(claude-md): move the invariant ledger to docs/invarianter.md, verbatim
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>
2026-09-18 06:21:10 +02:00