CB-WP-0021 T01/T02/T05: the engine plays its own data — AM-7 blocks

ADR-0011 decided it: vendor the CSV with a checked digest, read it with
a ~50-line reader, and let the hashes move.

The declaration's constraint was measured against the WRONG BUDGET. It
said a CSV crate costs 21,613 against AM-4a's 3,798 of headroom, '5.7x
over, settled by measurement'. But setup and problem_priorities are
cfg(scenarios) and are not in the shipped runtime at all, so AM-4a never
sees them. Against AM-4b, csv costs 17,651 against 19,742 -- it FITS,
with 2,091 to spare. It is refused anyway, on proportion: 89% of the
budget's remaining capacity to read 20 rows. The revisit condition is
stated (nested quoting, embedded newlines, multiple dialects).

GR-S01 now deals Surface + hidden 1..=k as ruled, with edition values and
suits. Measured: 6/9/12 available against thresholds 5/7/9 -- the game is
winnable at every seat count, which is what the maintainer could not do.
gd0001 is INVERTED, not deleted, and now also asserts the 6/9/12 so a
deal that is reachable for the wrong reason still fails.

Blast radius was scenario expectations, exactly as the ADR predicted: no
scenario pinned a hash and no bundle is committed. Six scenarios and two
unit tests updated, each with a note. gr-e01-threshold-unreachable-2p is
RENAMED to -reachable- and rewritten as the non-provisional import check
ground-game asked for by name. gr-e03's setup was restructured, not just
renumbered: with values 2,2,2 its personal-edge test would have tied
three ways and asserted nothing.

BLOCKING: AM-7 fails at median 0.845 against its 0.9 floor. Isolated
across three runs -- 3 problems + stand-in 0.97, 3 problems + edition
0.909, 4 problems + edition 0.845. State is BOUNDED (proven: identical
after 5k and 100k events), so this is not the unbounded-growth defect
AM-7 exists to catch; it is a bigger working set streaming a long log.
Whether AM-7's floor is still right for a larger aggregate is a spec
question and lowering it requires an ADR, so it is not being tuned here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-04 00:47:56 +02:00
parent f2281fa86c
commit 2da19a49b7
16 changed files with 593 additions and 167 deletions

View file

@ -0,0 +1,118 @@
# ADR-0011: vendor the edition, read it by hand, and let the hashes move
status: accepted
date: 2026-08-04
decided by: agent, under the standing loop authorization
tier: M (structural M — adds or refuses an external dependency and changes
how a game is set up; chaos d8=7 → no override). Tier M merges survey and
decision into one document, which this is.
references: [CB-WP-0021](../workplans/CB-WP-0021-import-the-edition.md),
[ADR-0007](ADR-0007-render-html-not-a-port.md) D3 (the acquisition rule),
GROUND-WP-0002 T01 and GROUND-WP-0004 (ground-game's rulings)
## Context
`editions/ground-darvo-r0/Problems.csv` is authoritative (ground-game,
2026-08-03) and the engine has been inventing Problem values and suits.
GR-S01's deal was then ruled (2026-08-04): **Surface always hidden
`1..k`**, k = 2/3/4, giving available points **6 / 9 / 12** — which holds
*only* with the real values.
## Correction: the constraint was measured against the wrong budget
**CB-WP-0021's declaration said a CSV crate costs 21,613 lines against
AM-4a's 3,798 of headroom — "5.7× over, settled by measurement rather
than preference."**
`setup` and `problem_priorities` are `#[cfg(feature = "scenarios")]`.
**They are not in the shipped runtime at all**, so AM-4a never sees them
and never would have. The budget that applies is AM-4b, and measured
against *its* graph:
| | lines |
|---|---:|
| AM-4b headroom (745,000 725,258) | **19,742** |
| `csv` marginal cost (`csv` + `csv-core`; `ryu`, `itoa`, `memchr` already present via `serde_json`) | **17,651** |
**It fits, with 2,091 lines to spare.** The declaration's confident
"settled by measurement" was measurement of the wrong thing — the third
premise this pass has had to correct, and the second where a real number
was computed against a mis-chosen denominator.
## Decision 1 — refuse `csv`, on proportion rather than impossibility
It fits and it is still refused, and the distinction matters because the
argument has to survive someone re-running the numbers.
**17,651 lines is 89% of everything AM-4b has left, to read 20 rows.** The
next dependency after it would have 2,091 lines to live in. A hand-rolled
reader for this grammar is ~50 lines we own.
INTENT's rule is *"assimilate the implementation"* — but that is about
**mature optimized libraries** for hard problems. Splitting quoted CSV
fields is not one, and `Problems.csv` is 20 rows read once at setup.
Taking a general parser here would spend the budget's remaining capacity
on the easiest problem we have.
**If the data grows into something a hand reader should not own — nested
quoting, embedded newlines, multiple files with differing dialects — this
decision is wrong and `csv` is the answer.** That is the condition to
revisit under, stated now rather than left to taste.
## Decision 2 — vendor the file, with a checked provenance
`ground-game` is a separate repository. Two options:
| | cost |
|---|---|
| **sibling checkout** | the build depends on a path that may not exist. CI runs `rust:1.97` with this repo only, so `make all` would fail or silently skip — and a silent skip is the class this project has found seven times |
| **vendor a copy** | clay-borg carries content it does not own, and a stale copy is worse than no copy |
**Vendored**, at `editions/ground-darvo-r0/Problems.csv`, with a
`PROVENANCE` note naming the upstream repo, path and revision.
The staleness answer is a **committed digest of the upstream file**. A
check compares it when `../ground-game` is present, and reports
**`upstream not checked out`** as a distinct outcome when it is not — never
a pass. That is the shape ADR-0009 used for `node`: an absent thing is
reported absent, not treated as satisfied.
**Acquisition rule (ADR-0007 D3):** this is content the build causes to be
present, and it is ours now. It is not third-party *code* and does not
enter AM-4, but the provenance note is what stops it becoming
unattributed.
## Decision 3 — the hashes move, and nothing pins them
Problem values and suits enter `GroundState`, which is hashed (K7). The
declaration called this *"the reason the ADR exists."* Measured:
| | |
|---|---|
| scenario files pinning a state hash | **0** |
| replay bundles committed | **0** |
| scenarios asserting on `problems.N.*` | **10 of 25** |
**So the feared blast radius is not there.** K8's double-run compares two
runs of the same build; AM-7's probe compares segments within one run;
`replay-test` generates its bundle at run time. All are self-consistent
and survive a content change by construction.
What breaks is **scenario expectations** — which is exactly what *should*
break when the content changes, and is why they are written as `expect`
blocks rather than hashes.
**No hash is grandfathered and none is recorded as "was".** A recorded
hash that outlives the content it describes is a lie with a timestamp.
## Consequences
- The deal fix and the import land **together**. The ruled 6/9/12 holds
only with real values; the same deal with the stand-in gives 6/10/15, a
game nobody ruled on.
- `gd0001` is **inverted, not deleted** — it is the record of why the game
became winnable.
- If ground-game revises `r0` in place, the digest check fails loudly.
Per GROUND-WP-0002 T01's proposed contract, `point_value` and
`required_solution` may not change within a revision; this is the
mechanism that notices if they do.