Compare commits

..

No commits in common. "6be9fbc9af852ab6afad8eee56edc791b15c7654" and "c02edef8ba80ed0d1546ce5dabc05934e22fcc8c" have entirely different histories.

18 changed files with 147 additions and 1005 deletions

View file

@ -124,6 +124,7 @@ self-tests:
$(PY) $(TOOLS)/size-metrics.py --self-test
$(PY) $(TOOLS)/runtime-metrics.py --self-test
$(PY) $(TOOLS)/replay-test.py --self-test
$(PY) $(TOOLS)/design-baseline.py --self-test
$(PY) $(TOOLS)/design.py --self-test
$(PY) $(TOOLS)/edition-check.py --self-test

View file

@ -29,7 +29,7 @@
| workplan | CB-WP-0019 | done | — | workplans/CB-WP-0019-the-am4-family.md |
| workplan | CB-WP-0020 | done | — | workplans/CB-WP-0020-the-table-you-can-read.md |
| workplan | CB-WP-0021 | active | — | workplans/CB-WP-0021-import-the-edition.md |
| workplan | CB-WP-0022 | done | — | workplans/CB-WP-0022-the-design-instrument.md |
| workplan | CB-WP-0022 | active | — | workplans/CB-WP-0022-the-design-instrument.md |
| workplan | CB-WP-0023 | done | — | workplans/CB-WP-0023-solve-legality.md |
| workplan | CB-WP-0024 | ready | — | workplans/CB-WP-0024-the-table-you-can-watch.md |
| workplan | CB-WP-0025 | ready | — | workplans/CB-WP-0025-could-we-have-won.md |
@ -147,12 +147,12 @@
| task | CB-WP-0021-T05 | done | — | workplans/CB-WP-0021-import-the-edition.md |
| task | CB-WP-0021-T06 | done | — | workplans/CB-WP-0021-import-the-edition.md |
| task | CB-WP-0022-T01 | done | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T02 | done | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T03 | done | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T04 | done | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T05 | done | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T06 | done | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T07 | done | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T02 | todo | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T03 | todo | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T04 | todo | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T05 | todo | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T06 | todo | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0022-T07 | todo | — | workplans/CB-WP-0022-the-design-instrument.md |
| task | CB-WP-0023-T01 | done | — | workplans/CB-WP-0023-solve-legality.md |
| task | CB-WP-0023-T02 | done | — | workplans/CB-WP-0023-solve-legality.md |
| task | CB-WP-0023-T03 | done | — | workplans/CB-WP-0023-solve-legality.md |

View file

@ -31,30 +31,6 @@ pub struct ScenarioFile {
/// reportable by `make coverage`.
#[serde(default)]
pub provisional_raised: String,
/// ISO date the owner ruled, which is what ENDS `provisional`
/// (CB-WP-0026). A confirmed default is no longer provisional — the
/// flag comes off and this records what settled it, because deleting
/// the flag without the ruling loses why the question closed.
#[serde(default)]
pub ruled: String,
/// Who ruled. Distinct from `provisional_owner`, which says who *must*
/// rule; this says who *did*.
#[serde(default)]
pub ruled_by: String,
/// The ruling in its own words. Quoted rather than paraphrased —
/// three of the ten U-item rulings carry conditions that a summary
/// loses (ADR-0012; GROUND-WP-0002 T05).
#[serde(default)]
pub ruled_note: String,
/// Which `§Underdetermined` U-item this scenario encodes the default
/// for, if any.
///
/// **Named rather than inferred.** CB-RES-0007 asserted six of ten
/// U-items had provisional scenarios; measured, exactly one named its
/// item, and the rest was guesswork nobody could check. A scenario
/// claims a U-item here or it does not claim one at all.
#[serde(default)]
pub encodes_u_item: String,
pub seed: u64,
pub setup: Setup,
pub commands: Vec<CommandStep>,

View file

@ -1,176 +0,0 @@
# CB-EV-0019 — import the edition
CB-WP-0021 T04. Tier M (structural M — adds or refuses an external
dependency and changes how a game is set up; chaos d8=7 → no override).
Closed 2026-08-05, four days after the code landed.
**Delivered:** [ADR-0011](../decisions/ADR-0011-vendor-the-edition.md),
`editions/ground-darvo-r0/Problems.csv` with a digest, a ~150-line reader
in `games/ground/src/edition.rs`, `tools/edition-check.py`, and the ruled
deal — Surface hidden `1..=k`.
---
## 1. What the import cost, and the budget it was measured against
**The declaration's constraint was measured against the wrong
denominator, and the ADR corrected it.** CB-WP-0021 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**, so AM-4a never sees them. Against
AM-4b's graph:
| | lines |
|---|---:|
| AM-4b headroom | 19,742 |
| `csv` marginal (`csv` + `csv-core`; `ryu`/`itoa`/`memchr` already present) | **17,651** |
**It fits, with 2,091 to spare — and was refused anyway**, on proportion:
89% of everything the budget had left, to read 20 rows. What shipped is a
hand-rolled reader we own.
**That is the third premise this pass had to correct**, and the second
where a real number was computed against a mis-chosen denominator. The
pattern is now well enough attested to name: *this project's arithmetic
errors are not in the arithmetic.* CB-WP-0022 T02 produced two more of the
same shape four days later.
Current dependency position, `make dep-weight`:
```
AM-4c shipped-runtime 7,976 own lines per 100k third-party (diagnostic)
AM-4c dev-toolchain 1,729 own lines per 100k third-party (diagnostic)
```
## 2. What broke — and the hash blast radius was predicted, not discovered
The task asked which it was. **Predicted, and then measured to be almost
nothing.**
T01's brief said Problem values and suits enter `GroundState`, which is
hashed (K7), so *"every recorded state hash changes"*, and required the
blast radius be established **before writing code**. ADR-0011 §Decision 3
did exactly that:
| | |
|---|---:|
| scenario files pinning a state hash | **0** |
So the feared radius was ~0. Scenarios assert on state *blocks*, not
hashes; `replay-test` re-executes bundles and restores them; AM-7's probe
asserts per-segment hashes within a run rather than against recorded
literals.
**No hash was grandfathered and none recorded as "was"** — ADR-0011's
words: *a recorded hash that outliving the content it describes is a lie
with a timestamp.*
Verified today:
- `make replay-test`**14/14 controls passed**, including *"the bundle
still replays after every control restored it"*;
- `make am7` — scaling **0.985 / 0.998 / 1.018** (worst/median/best of 9)
against a 0.9 floor. Green, after T06 fixed the *measurement* rather
than lowering the floor.
**What did break** was the scenario corpus's data assumptions: six
scenario files needed value/suit updates (`gr-a02-solve`,
`gr-e03-common-problem`, `gr-e04-coalitions`, `gr-f02-no-gate`,
`gr-p05-solve-legality`) because they encoded stand-in numbers. That is
the expected cost of replacing invented data with real data, and every one
was a data edit rather than a logic change.
## 3. Do the endings mean anything now? Yes — and the margin is one point
This is the question the pass existed for. Played today, 2 players,
all-bots:
```
OUTCOME total 4 / threshold 5 group failure
personal: P1 2 P2 2
mastery 2
problems: [1] Repair 2 claimed by P2 [2] Clarify 2 [3] Boundary 2 claimed by P1
```
**Three Problems worth 2 each — 6 available against a threshold of 5.**
The bots claimed two of three, scored 4, and lost by one point.
**This confirms T02's control by play.** That control said: *"at 2p that
means a **full clear**, since 2+2+2 against 5 leaves no slack."* It is
exactly right — at two seats, leaving any single Problem unclaimed loses
the game. The endings are not merely reachable; they are **tight**, which
is a far better answer than "reachable" and was not guaranteed by the
ruling.
**And it retires the finding this pass was built around.** GR-E01 was
raised as *"unreachable below 5 seats"* on 4/6/9 against 5/7/9. The real
deal is 6/9/12 against 5/7/9. `gr-e01-threshold-unreachable-2p` is now
`gr-e01-threshold-reachable-2p`, non-provisional, and the finding was
**formally withdrawn to ground-game on 2026-08-05** (CB-WP-0022 T06) —
because it took an adversarial review, four days later, for anyone to
notice that this pass had already falsified it.
**That is the sharpest thing in this evidence file.** The work that
disproved GR-E01 is *in this workplan*. The workplan that quoted GR-E01 as
its founding example was written the next day. Nothing connected them
until a separate agent ran `git log`.
## 4. Quoting CB-WP-0020's cost — the instrument refuses
T04 asked for CB-WP-0020's cost by re-running the instrument. It cannot be
supplied:
```
$ python3 tools/cb-cost.py --slug CB-WP-0020 --by-task
ABORT — no transcripts found for CB-WP-0020
```
**The instrument aborts rather than estimating, which is correct
behaviour** and is why the number is absent instead of wrong. CB-WP-0020's
transcripts are outside the retained window.
What the instrument does report, run today:
```
main $ 548.03 output 2,125,069 tok $ 56.16 10.1%
subagent tree $ 5.50 cache_read 750,152,134 tok $394.44 71.3%
TOTAL $ 553.53 write_1h 9,233,711 tok $ 97.48 17.6%
UNATTRIBUTED $230.34 41.6%
CB-WP-0021-T01 $ 17.15 3.1% PROVISIONAL — this pass's own
CB-WP-0021-T06 $ 14.31 2.6% PROVISIONAL — this pass's own
```
**The two CB-WP-0021 rows are provisional and must not be quoted as
final.** They are this pass's own spend, measured while writing this file,
so the number is still moving — `loop-lint`'s `own-cost` rule caught the
first draft asserting them flat, which is the rule working.
**41.6% unattributed is the number worth carrying forward**, not any
per-task figure. A cost instrument that cannot say where two-fifths of the
spend went is a partial instrument, and the retention window is why the
chain of *"quote the previous pass's cost"* — a rule designed to keep
costs comparable across passes — **breaks after roughly four passes.**
That rule should either be bounded to what the window holds, or the window
extended. It is not this pass's to decide; it is recorded here because
this is the first pass where the chain actually snapped.
## 5. Chaos window 2
**Declaration 4 of 12.** Structural M, d8 = 7, no override. The roll is
recorded per §Loop tiers even though it changed nothing.
Window 2 standing: no override has yet changed an outcome. The retirement
condition — *retire if an override changes nothing twice running* — has
not been triggered, because there have been no overrides to evaluate.
## Open after this pass
- **The cost chain is broken** (§4) and the rule that depends on it needs a
bound.
- The wrong-denominator pattern (§1) now has three instances and no
control. `facts-check` catches copies that disagree; nothing catches a
number computed correctly against the wrong base.

View file

@ -1,209 +0,0 @@
# CB-EV-0022 — collect the rulings
CB-WP-0026 T05. Tier S (structural S — applies rulings inside an existing
capability; chaos d8=6 → no override). Declaration 9 of chaos window 2.
Closed 2026-08-05.
**Delivered:** ten rulings recorded in `§Underdetermined`, five
`provisional: true` flags lifted, four scenario schema fields, the
`encodes_u_item` declaration, and an empty finding queue.
---
## 1. How long the answers sat, and what noticed them
**Two days**, and the thing that noticed was **the register's first run**
not a human, not the adversarial review.
`ground-game` ruled all ten U-items on **2026-08-03** (GROUND-WP-0002 T05),
every one confirmed. On the same day CB-RES-0007 reported *"0 of 10
ruled."* Two days later CB-WP-0022 built the register recording them as
`reported`, and the adversarial review — which found four other things —
did not catch it either. `make design` did, on its first execution.
**This is the symmetric failure nobody designed for.** CB-WP-0022 was
shaped end to end around *we send findings and nobody reads them*: the
four-day unread inbox is quoted in the declaration, the survey, the ADR
and the spec. The mirror case — *they answer and we do not collect it*
appears in none of them.
It is arguably the worse of the two. An unread message is visible as
silence; a collected-but-unapplied ruling looks exactly like work in
progress.
## 2. Did any confirmed default fail to match the kernel? No.
This was the control that mattered. Every ruling was a *confirmation* of
what we told `ground-game` we simulate — so a red scenario would have
meant **we described our own behaviour incorrectly to them**, a defect in
our report rather than in their ruling, and the most serious class
available since it would be about our own code.
```
make sim → 26 passed, 59 rules covered
```
**No scenario went red.** The five confirmed defaults are the behaviour
implemented. That is the strongest single result here and it is a
negative: nothing was wrong.
## 3. The mapping gap was not what the survey said, and I reproduced the defect writing it
CB-RES-0007: *"six of the ten already have provisional scenarios."*
**Measured: one.** And the interesting part is how the other nine were
lost.
I wrote two mappings from the `covers:` lists and **both were wrong**:
| claimed | why it was withdrawn |
|---|---|
| `gr-a04-bond-support` → U1 | asserts consent is **required**; U1 asks **when** the target accepts |
| `gr-d05-darvo-reverse` → U5 | exercises the **unrejected** REVERSE; U5 is the **rejected** one (GROUND—ND) |
Both were plausible from `covers:`. Neither survived reading the
description. **These are the third and fourth instances of this exact
defect** — a link that looks right from metadata, asserted without
checking what the artifact exercises — and the first two reached
`ground-game`.
So the answer to *"do the four unlinked scenarios encode U-item defaults
at all?"* is: **at least two of the four do not**, and the survey's "six of
ten" was not an under-documented truth. It was wrong.
`encodes_u_item` is now a declaration a scenario makes or omits, and
`design.py` asserts the file names what it claims.
### The check's own first version was the same looseness
Written as `grep -lE "\bU<n>\b"`, it went red the moment two scenarios
recorded *why they do not* encode U1 and U5 — reporting `['U1','U2','U5']`.
**A mention is not a claim.** That is precisely the imprecision that let
*"six of the ten have provisional scenarios"* stand unchallenged for five
days: someone grepped for U-item strings and counted hits. The check now
asserts on the declaration.
## 4. The queue reached 0
```
QUEUE (open findings) (none)
open, lacking a reproduction 0 target 0
closed (log) 12 [U1..U10, F11, F13]
with a resolving reproduction 3/12 = 25%
notes 2 F12, F14
```
**`9 → 0`**, the number CB-WP-0026 T04 named. First evidence that
ADR-0012 D5's lifecycle is real rather than drawn: findings entered a
state, moved through it, and left the queue.
**25% reproduced must not be read as a failure.** Nine U-items closed by a
**ruling**, and a ruling is not an artifact. The metric is now honest
about something the survey's 33% concealed: most of our findings close
because someone answered them, not because anything demonstrates them.
**What the lifecycle could not express** — the honest gap: `applied` is
defined as *the source changed and the provisional default was deleted*.
Here the rulings **confirmed** our defaults, so nothing in the rules moved;
what changed is that the flags came off. The state fits, but the
definition had to be read generously. If a future ruling *overturns* a
default, `applied` will mean something materially different from what it
meant today, and D5 does not distinguish them.
## 5. Two things the schema caught
**`ScenarioFile` is `deny_unknown_fields`**, so five scenarios failed to
parse until `ruled`, `ruled_by`, `ruled_note` and `encodes_u_item` were
declared in the Rust struct. A corpus that accepted unknown metadata would
let a typo'd `encodes_u_iem` sit forever claiming nothing — and this
pass's whole subject is claims nobody checks.
**`record.rs` had to set them explicitly.** A recorded game is evidence of
what happened, not a claim about an undetermined rule. Filling the fields
via `..Default::default()` would have been shorter and would let a
recording silently inherit a U-item claim, pointing a reproduction at a
finding it has nothing to do with.
## 5b. Two gates went red for the best possible reason
Lifting the last five provisional flags broke two positive controls, and
**both were broken in the same way**: they asserted against live repo data
instead of constructing the condition they test.
**`rule-coverage.py`** required `bool(prov)` — *at least one provisional
item must exist*. That guard was the right instinct (a control that passes
vacuously is worthless) wired the wrong way. With nothing provisional, it
went red. It now builds a fixture, asserts the missing-owner case is
**caught**, and reports the live count as a diagnostic — because **there is
no number of provisional items this project should have.**
**`design-baseline.py`** pinned *"the measured baseline is 2 of 6"* and
reported `1/6`. Its "six provisional defaults" row **globbed
`provisional: true` at run time**, so the dated snapshot was never a
snapshot — it drifted with the repo. C2 dismantled this tool four hours
earlier and missed this: a hand-maintained dict with one dynamically
computed row is worse than a fully hand-maintained one, because the
recomputed row silently disagrees with the date in the header.
Frozen to the literal list it measured on 2026-08-03, and **removed from
`make self-tests`** — that target is *"a positive control for every
reporting tool"*, and per ADR-0012 D8 this is no longer a reporting tool.
Leaving it wired in meant a superseded instrument could fail the build.
**The pattern across both**: a control that reads the world it is meant to
audit will eventually audit a world that has moved. Neither was caught by
review; both were caught by the world moving.
## 6. The register moved, and ADR-0012 D2 gave way to loadability
`specs/GroundRules.md` crossed the ~400-line limit and `loop-lint`
required a split. ADR-0012 D2 said **"no new file"**, so this is a
deviation and is recorded as one.
**D2's substance holds.** Its argument was *one register, not a second
mechanism competing with the first* — and `specs/FindingRegister.md` is
that same register, moved, still driving off the `provisional`/ruling
machinery, still the only one. What was traded away is the literal "no new
file", which was D2's *implementation*, not its reason.
It also resolves an awkwardness D2 named itself: *"a finding about the
engine's behaviour sits in a document about the game."* Now it does not.
## 7. Chaos window 2
**Declaration 9 of 12.** Structural S, d8 = 6, no override. Recorded per
§Loop tiers even though it changed nothing.
**Window 2 still has no override to evaluate** — nine declarations, zero
8s. The retirement condition (*retire if an override changes nothing twice
running*) cannot be assessed, and at d8 the expected count over twelve
declarations is 1.5, so this is unremarkable rather than evidence of
anything.
## 8. Cost
CB-WP-0022's cost, by re-running the instrument:
```
CB-WP-0022-T05 $ 6.43
CB-WP-0022-T01 $ 4.17
CB-WP-0022-T02 $ 3.56 (the separate reviewer's own spend is in the subagent tree)
CB-WP-0022-T04 $ 1.92
CB-WP-0022-T03 $ 1.01
```
**The chain did not snap this time** — CB-EV-0019 §4 predicted it might,
having found `cb-cost.py --slug CB-WP-0020` aborts for want of retained
transcripts. CB-WP-0022 is recent enough to still be in the window. **The
bound CB-EV-0019 asked for is still owed**; this pass is evidence that the
rule works for a pass one step back, not that it works generally.
## Open after this pass
- **Nine U-items are `applied` with no reproduction.** That is recorded,
not hidden, but it means nine rules rest on a ruling nobody can re-run.
Cheap to fix incrementally: each needs one scenario naming its item.
- **`applied` conflates *confirmed* with *overturned*** (§4). It will
matter the first time a ruling goes against us.
- **The cost-chain bound** (CB-EV-0019 §4) is still unwritten.

View file

@ -153,15 +153,6 @@ pub fn to_scenario(
provisional: false,
provisional_owner: String::new(),
provisional_raised: String::new(),
// A recording is evidence of what happened, not a claim about an
// undetermined rule, so it carries no ruling and encodes no
// U-item. Set explicitly rather than by `..Default::default()`:
// a recorded game silently inheriting a U-item claim would be a
// reproduction pointing at a finding it has nothing to do with.
ruled: String::new(),
ruled_by: String::new(),
ruled_note: String::new(),
encodes_u_item: String::new(),
seed,
setup: Setup {
players,

View file

@ -4,11 +4,9 @@ description: >
token is readied (GR-A04, GR-F04). A Support with no relation forms no
Bond, because GR-L02 requires the target's consent.
covers: [GR-A04, GR-F04, GR-L02, GR-L05]
ruled: 2026-08-03
ruled_by: ground-game
ruled_note: >
GROUND-WP-0002 T05/T03, 2026-08-03 — confirm: consent required; no auto-Bond from a no-relation Support.
Does NOT encode U1: U1 asks WHEN the target accepts, and this asserts only THAT consent is required.
provisional: true
provisional_owner: ground-game
provisional_raised: 2026-07-31
seed: 42
setup:
players: 3

View file

@ -4,11 +4,9 @@ description: >
OFF is set to DENY (GR-D01, GR-R08). Stress clamps at 5 on every
application (GR-F01 under the U2 default).
covers: [GR-D01, GR-R08, GR-F01]
ruled: 2026-08-03
ruled_by: ground-game
ruled_note: >
GROUND-WP-0002 T05/T03, 2026-08-03 — confirm: clamp on every application; DARVO still arms only at End (GR-R08). Encodes the U2 default.
encodes_u_item: U2
provisional: true
provisional_owner: ground-game
provisional_raised: 2026-07-31
seed: 42
setup:
players: 3

View file

@ -4,11 +4,9 @@ description: >
gives them +1 Stress and the owner one Protection token, then the
owner takes 2 Stress and the sequence ends (GR-D05, GR-D07).
covers: [GR-D05, GR-D07, GR-T01]
ruled: 2026-08-03
ruled_by: ground-game
ruled_note: >
GROUND-WP-0002 T05/T03, 2026-08-03 — confirm: the printed REVERSE stage plus U5.
Does NOT encode U5: U5 is the REJECTED Reverse (GROUND—ND), and this is the unrejected stage.
provisional: true
provisional_owner: ground-game
provisional_raised: 2026-07-31
seed: 42
setup:
players: 3

View file

@ -4,10 +4,9 @@ description: >
shared total against the player-count threshold, with a Mastery
rating reduced by each Blame token and each Denied Problem.
covers: [GR-R09, GR-E01, GR-E02, GR-P03]
ruled: 2026-08-03
ruled_by: ground-game
ruled_note: >
GROUND-WP-0002 T03, 2026-08-03 — confirm: encode Modes.csv MODE_COOP as printed. Encodes no U-item; it was provisional on the mode data, not on an undetermined rule.
provisional: true
provisional_owner: ground-game
provisional_raised: 2026-07-31
seed: 42
setup:
players: 3

View file

@ -4,10 +4,9 @@ description: >
Rivalries do not connect, and an unbonded player is a coalition of
one. Personal scores subtract Blame (GR-E03, GR-T02).
covers: [GR-E03, GR-E04, GR-O03]
ruled: 2026-08-03
ruled_by: ground-game
ruled_note: >
GROUND-WP-0002 T03, 2026-08-03 — confirm: encode Modes.csv MODE_COALITION as printed. Encodes no U-item; provisional on the mode data.
provisional: true
provisional_owner: ground-game
provisional_raised: 2026-07-31
seed: 42
setup:
players: 3

View file

@ -1,138 +0,0 @@
# The finding register
Design findings about **GROUND**, with their reproductions. Governed by
[`GameDesign.md`](GameDesign.md) (admissibility, kinds, states, metrics)
and [ADR-0012](../decisions/ADR-0012-the-design-instrument.md). Reported
by `make design`.
**Split out of `GroundRules.md §Underdetermined` on 2026-08-05** when that
file crossed the ~400-line loadability limit. ADR-0012 D2 said *"no new
file"* and this is a new file — but D2's substance was **one register, not
a second mechanism competing with the first**, and that holds: this *is*
§Underdetermined's register, moved, still driving off the same
`provisional`/ruling machinery. D2 also named the awkwardness this
resolves — a finding about the engine sitting in a document about the
game.
The U-items themselves, with their defaults and rulings, stay in
[`GroundRules.md §Underdetermined`](GroundRules.md); this file tracks them
*as findings*.
**This section is the design-finding register** (ADR-0012 D2). It was the
register for dataset ambiguities already; CB-WP-0022 extended it to all
five kinds rather than building a second one beside it. Admissibility,
kinds, states and metrics: [`GameDesign.md`](GameDesign.md). Reported by
`make design`.
<!-- design-register:begin -->
| id | kind | state | reproduction | role | raised | owner |
|---|---|---|---|---|---|---|
| U1 | underdetermined | applied | — | default | 2026-07-31 | ground-game |
| U2 | underdetermined | applied | scenarios/ground/gr-d01-darvo-trigger.yaml | default | 2026-07-31 | ground-game |
| U3 | underdetermined | applied | — | default | 2026-07-31 | ground-game |
| U4 | underdetermined | applied | — | default | 2026-07-31 | ground-game |
| U5 | underdetermined | applied | — | default | 2026-07-31 | ground-game |
| U6 | underdetermined | applied | — | default | 2026-07-31 | ground-game |
| U7 | underdetermined | applied | — | default | 2026-07-31 | ground-game |
| U8 | underdetermined | applied | — | default | 2026-07-31 | ground-game |
| U9 | underdetermined | applied | — | default | 2026-07-31 | ground-game |
| U10 | underdetermined | applied | — | default | 2026-07-31 | ground-game |
| F11 | inert | applied | scenarios/ground/gr-p05-solve-legality.yaml | counterexample | 2026-08-02 | clay-borg |
| F12 | degenerate | note | — | — | 2026-08-01 | clay-borg |
| F13 | inconsistent | withdrawn | scenarios/ground/gr-e01-threshold-reachable-2p.yaml | counterexample | 2026-08-01 | clay-borg |
| F14 | unplayed | note | — | — | 2026-08-01 | clay-borg |
<!-- design-register:end -->
- **F11 — SOLVE offered where it cannot act.** Offered on a face-down
Problem, or with no matching suit in hand; inert every time. Ruled
GROUND-WP-0002 T02, implemented CB-WP-0023 as GR-P05. `applied` — the
rule changed, not just the annotation. **The case we reported was not the
case that fired**: `validate` already rejected face-down, and the
maintainer's three inert SOLVEs were the *hand* case.
- **F12 — GR-A13 "wasted SOLVE" on an already-claimed Problem.** A
scenario had to pick a default and did. `note`: no artifact isolates the
degenerate line, so under GameDesign §3.1 it may not be reported until
one exists.
- **F13 — GR-E01 vs GR-S01, withdrawn 2026-08-05.** Raised as *"4/6/9
against 5/7/9, no dataset reconciles them."* `2da19a4` measured **6/9/12
against 5/7/9** and the scenario was renamed `-unreachable-`
`-reachable-`. **Its reproduction is green**, which under GameDesign §1.3
is the alarm that forced the resolution. Withdrawn rather than deleted,
and the withdrawal is reported (ADR-0012 D5).
- **F14 — GR-E03/GR-E04 never played to the end.** Nineteen passes, never
played out. `note` until a trial game exists; GROUND-WP-0003 is the
playtest that would close it, and GameDesign §5's protocol makes the
recording the artifact.
### The register's first run found ten answers nobody had collected
**U1U10 are `ruled`, not `reported`.** GROUND-WP-0002 T05 answered all
ten on **2026-08-03** — every one **confirmed** as the default clay-borg
already simulates — and GROUND-WP-0002 T03 confirmed five of the six
provisional scenarios, voiding `gr-e01` as a rules gap. The workplan is
`finished`.
CB-RES-0007 reported *"0 of 10 ruled"* and this register was built saying
`reported`. **Both were two days stale on the day they were written.** The
answers had arrived and nothing propagated them — the same failure as the
unread inbox, in the opposite direction.
**They are `ruled`, not `applied`, and the difference is work we owe.**
Per ADR-0012 D5, `applied` means the source changed and the provisional
default was deleted. The rulings *confirmed* our defaults, so the rules did
not move — but the scenarios still carry `provisional: true` for choices
that are now settled. **Lifting those flags and recording each ruling is
what closes U1U10**, and it is not done. `make design` shows them open
until it is.
### The U-item ↔ scenario mapping, measured twice
**One U-item has a scenario that names it: U2.** CB-RES-0007 asserted six
of ten did.
CB-WP-0026 T03 tried to write the other mappings and **produced two wrong
ones before checking them**:
| claimed | why it was withdrawn |
|---|---|
| `gr-a04-bond-support` → U1 | it asserts consent is **required**; U1 asks **when** the target accepts. Different question. |
| `gr-d05-darvo-reverse` → U5 | it exercises the **unrejected** REVERSE; U5 is the **rejected** one (GROUND—ND). Different stage. |
Both were plausible from the `covers:` list and both were wrong on
reading the description. **That is the third and fourth instance of this
exact defect** — a link that looks right, asserted without checking what
the artifact actually exercises — and the first two reached ground-game.
So a scenario now declares `encodes_u_item` explicitly or claims nothing.
**Nine U-items have no reproduction and are recorded as having none.**
They are `applied` because the ruling landed and the provisional flag came
off, not because anything demonstrates them.
### What the backfill measured, and what it contradicted
**Only U2 names its U-item in a scenario.** Measured, not estimated:
```
for u in U1 .. U10; do grep -lE "\b$u\b" scenarios/ground/*.yaml; done
```
CB-RES-0007 asserted *"six of the ten already have provisional
scenarios."* Five provisional scenarios exist and **one** cites the item
it stands for. The other four may well encode U-item defaults — the
mapping is simply **not written down**, so it is not checkable, and an
uncheckable link is the defect this register exists to fix. The register
records what is citable; the rest is debt, visible in `make design`.
**No sixth kind was needed** — the five kinds absorbed all four non-U
findings. And the survey's *"six provisional defaults"* was **not** entered
as a finding: C3 showed it double-counted GR-E01, and the provisional
scenarios are *reproductions for* underdetermined items, not a finding of
their own.
**U1U10 are `reported` while lacking reproductions**, which GameDesign
§3.1 would now forbid. They were reported on 2026-07-30, before the rule
existed. They are grandfathered rather than rewritten, and the debt is a
reported metric with a target of zero.

View file

@ -221,82 +221,151 @@ terms.
## The finding register
Moved to [`FindingRegister.md`](FindingRegister.md) on 2026-08-05 — this
file crossed the loadability limit. The U-items below are its
`underdetermined` rows; `make design` reports over it.
**This section is the design-finding register** (ADR-0012 D2). It was the
register for dataset ambiguities already; CB-WP-0022 extended it to all
five kinds rather than building a second one beside it. Admissibility,
kinds, states and metrics: [`GameDesign.md`](GameDesign.md). Reported by
`make design`.
## Underdetermined in dataset 0.1 — RULED 2026-08-03, applied 2026-08-05
<!-- design-register:begin -->
**Owner: ground-game. Raised: 2026-07-31. Ruled 2026-08-03**
(GROUND-WP-0002 T05): **all ten confirmed** as the defaults clay-borg
already simulates. Applied here 2026-08-05 (CB-WP-0026).
| id | kind | state | reproduction | role | raised | owner |
|---|---|---|---|---|---|---|
| U1 | underdetermined | ruled | — | default | 2026-07-31 | ground-game |
| U2 | underdetermined | ruled | scenarios/ground/gr-d01-darvo-trigger.yaml | default | 2026-07-31 | ground-game |
| U3 | underdetermined | ruled | — | default | 2026-07-31 | ground-game |
| U4 | underdetermined | ruled | — | default | 2026-07-31 | ground-game |
| U5 | underdetermined | ruled | — | default | 2026-07-31 | ground-game |
| U6 | underdetermined | ruled | — | default | 2026-07-31 | ground-game |
| U7 | underdetermined | ruled | — | default | 2026-07-31 | ground-game |
| U8 | underdetermined | ruled | — | default | 2026-07-31 | ground-game |
| U9 | underdetermined | ruled | — | default | 2026-07-31 | ground-game |
| U10 | underdetermined | ruled | — | default | 2026-07-31 | ground-game |
| F11 | inert | applied | scenarios/ground/gr-p05-solve-legality.yaml | counterexample | 2026-08-02 | clay-borg |
| F12 | degenerate | note | — | — | 2026-08-01 | clay-borg |
| F13 | inconsistent | withdrawn | scenarios/ground/gr-e01-threshold-reachable-2p.yaml | counterexample | 2026-08-01 | clay-borg |
| F14 | unplayed | note | — | — | 2026-08-01 | clay-borg |
**Nothing below awaits a ruling.** The defaults are now the rules for
dataset 0.1, and the `provisional: true` flags they carried have been
lifted — a flag that outlives its question is a lie with a timestamp.
<!-- design-register:end -->
**No kernel behaviour changed**, because every ruling was a confirmation.
What changed is that these stopped being our guesses.
- **F11 — SOLVE offered where it cannot act.** Offered on a face-down
Problem, or with no matching suit in hand; inert every time. Ruled
GROUND-WP-0002 T02, implemented CB-WP-0023 as GR-P05. `applied` — the
rule changed, not just the annotation. **The case we reported was not the
case that fired**: `validate` already rejected face-down, and the
maintainer's three inert SOLVEs were the *hand* case.
- **F12 — GR-A13 "wasted SOLVE" on an already-claimed Problem.** A
scenario had to pick a default and did. `note`: no artifact isolates the
degenerate line, so under GameDesign §3.1 it may not be reported until
one exists.
- **F13 — GR-E01 vs GR-S01, withdrawn 2026-08-05.** Raised as *"4/6/9
against 5/7/9, no dataset reconciles them."* `2da19a4` measured **6/9/12
against 5/7/9** and the scenario was renamed `-unreachable-`
`-reachable-`. **Its reproduction is green**, which under GameDesign §1.3
is the alarm that forced the resolution. Withdrawn rather than deleted,
and the withdrawal is reported (ADR-0012 D5).
- **F14 — GR-E03/GR-E04 never played to the end.** Nineteen passes, never
played out. `note` until a trial game exists; GROUND-WP-0003 is the
playtest that would close it, and GameDesign §5's protocol makes the
recording the artifact.
> **Three rulings carry more than "confirm", quoted rather than
> paraphrased.** **U1**: the Bond formed by *this* Support still does not
> cancel DARVO (printed designer note — this is what GR-L05 encodes).
> **U2**: clamp on every application, but **DARVO still arms only at End**
> when at Stress 5 (GR-R08) — the clamping rule and the trigger rule are
> separate. **U8**: GROUND—OU applies first and **Protection is consumed
> only if it actually cancels**.
>
> ground-game flagged **U2, U6 and U8** as the three that change play,
> because each decides whether a DARVO stage fires at all.
### The register's first run found ten answers nobody had collected
Formalization exposed points the dataset did not decide. Each is stated
with its default and the ruling that settled it.
**U1U10 are `ruled`, not `reported`.** GROUND-WP-0002 T05 answered all
ten on **2026-08-03** — every one **confirmed** as the default clay-borg
already simulates — and GROUND-WP-0002 T03 confirmed five of the six
provisional scenarios, voiding `gr-e01` as a rules gap. The workplan is
`finished`.
CB-RES-0007 reported *"0 of 10 ruled"* and this register was built saying
`reported`. **Both were two days stale on the day they were written.** The
answers had arrived and nothing propagated them — the same failure as the
unread inbox, in the opposite direction.
**They are `ruled`, not `applied`, and the difference is work we owe.**
Per ADR-0012 D5, `applied` means the source changed and the provisional
default was deleted. The rulings *confirmed* our defaults, so the rules did
not move — but the scenarios still carry `provisional: true` for choices
that are now settled. **Lifting those flags and recording each ruling is
what closes U1U10**, and it is not done. `make design` shows them open
until it is.
### What the backfill measured, and what it contradicted
**Only U2 names its U-item in a scenario.** Measured, not estimated:
```
for u in U1 .. U10; do grep -lE "\b$u\b" scenarios/ground/*.yaml; done
```
CB-RES-0007 asserted *"six of the ten already have provisional
scenarios."* Five provisional scenarios exist and **one** cites the item
it stands for. The other four may well encode U-item defaults — the
mapping is simply **not written down**, so it is not checkable, and an
uncheckable link is the defect this register exists to fix. The register
records what is citable; the rest is debt, visible in `make design`.
**No sixth kind was needed** — the five kinds absorbed all four non-U
findings. And the survey's *"six provisional defaults"* was **not** entered
as a finding: C3 showed it double-counted GR-E01, and the provisional
scenarios are *reproductions for* underdetermined items, not a finding of
their own.
**U1U10 are `reported` while lacking reproductions**, which GameDesign
§3.1 would now forbid. They were reported on 2026-07-30, before the rule
existed. They are grandfathered rather than rewritten, and the debt is a
reported metric with a target of zero.
## Underdetermined in dataset 0.1 — PROVISIONAL defaults (flag to ground-game)
**Owner: ground-game. Raised: 2026-07-31.** Every U-item carries an owner
and a raise date so its age is visible; `make coverage` reports both and
warns past 30 days. CI **warns rather than breaks** — the ruling belongs
to ground-game and the kernel cannot make it for them — but every evidence
file that depends on one must list it (CB-WP-0003 T08).
A ruling flips the *scenario*, not the kernel: the default is encoded in a
scenario tagged `provisional: true`, so a correction is a data change.
Formalization exposed points the dataset does not decide. Simulation uses
the stated default; each is tagged in scenarios that depend on it and must
be confirmed or corrected by ground-game.
- **U1 Bond acceptance timing (GR-L02):** when does the target "accept"?
Default: a decision point during the Support resolution, target decides
with full knowledge of revealed Actions.
**RULED 2026-08-03 — confirm.** Target accepts during Support resolution, with full knowledge of revealed Actions. A Bond formed by *this* Support still does not cancel DARVO (printed designer note; GR-L05).
- **U2 Stress clamping during resolution (GR-F01):** dataset caps Stress
at End; mats physically cap at 05. Default: clamp on every
application (no intermediate values above 5/below 0). Note this
interacts with DARVO triggering (a mid-round spike to 5 that is reduced
again before End does not trigger).
**RULED 2026-08-03 — confirm.** Clamp Stress to 05 on **every** application; matches the physical mats. DARVO still arms **only at End** when at Stress 5 (GR-R08).
- **U3 DENY with no legal target (GR-D03):** if no face-up unsolved
unprotected Problem exists, default: the stage resolves as a no-op and
the sequence still advances.
**RULED 2026-08-03 — confirm.** No-op; the sequence still advances.
- **U4 Solution deck exhaustion (GR-A01):** default: reshuffle the discard
into a new deck deterministically; if both are empty, the draw is
skipped.
**RULED 2026-08-03 — confirm.** Deterministic reshuffle of the discard into a new deck; if both are empty, skip the draw.
- **U5 Rejected REVERSE — self-effects (GR-D05):** the Actions sheet says
GROUND—ND "rejects one Reverse"; the DARVO sheet orders the 2
self-Stress after the flip/+1/Protection block. Default: rejection
cancels the effects on the target (no Blame, no +1) **and** the
Protection gain, but the sequence owner still takes 2 Stress and the
sequence still ends; the Focus token returns unflipped.
**RULED 2026-08-03 — confirm.** Rejection cancels the target effects (no Blame, no +1, no Protection) **and** the owner still takes 2 Stress and the sequence ends.
- **U6 Simultaneous Bond-Support and stage cancellation (GR-A04, GR-R06):**
Support (step 2) resolves before DARVO stages (step 3), so a Bond
Support in the same round cancels that round's stage before it fires.
Stated as the intended reading; confirm.
**RULED 2026-08-03 — confirm.** A pre-existing Bond Support (step 2) cancels that round's DARVO stage before it fires (step 3) and ends the sequence.
- **U7 Focus target elimination:** dataset has no player elimination, so
no rule needed; confirm none exists.
**RULED 2026-08-03 — confirm.** No player elimination.
- **U8 Multiple Attack cancellation sources (GR-A09):** if both Protection
and GROUND—OU could cancel the same Attack, default: GROUND—OU (chosen
at step 1) applies first; Protection is consumed only if it actually
cancels.
**RULED 2026-08-03 — confirm.** GROUND—OU applies first; Protection is consumed **only if it actually cancels**.
- **U9 Two players at Stress 5 at the same End (GR-R08):** both trigger;
their stages resolve in Lead order in later rounds. No interaction rule
needed; confirm.
**RULED 2026-08-03 — confirm.** Both trigger; later stages resolve in Lead order. No extra interaction rule.
- **U10 Hand limits:** none stated. Default: no hand limit.
**RULED 2026-08-03 — confirm.** No hand limit in r0. Revisit only if playtest shows bloat.
## Scenario coverage obligation
Every GR-rule above must be covered by ≥1 scenario under

View file

@ -47,19 +47,11 @@ FINDINGS = {
"evidence/CB-EV-0007-stage-0.md",
"scenarios/ground/gr-e01-threshold-reachable-2p.yaml",
"workplans/CB-WP-0021-import-the-edition.md"],
# FROZEN 2026-08-05. This globbed `provisional: true` live, so it was
# never a snapshot -- it drifted with the repo. CB-WP-0026 lifted the
# last five flags (ground-game ruled them 2026-08-03) and the glob went
# empty, turning this file's own pinned self-test red at 1/6. A dated
# snapshot that recomputes is a contradiction; the literal list it
# measured on 2026-08-03 is what belongs here.
"six provisional defaults": [
"scenarios/ground/gr-a04-bond-support.yaml",
"scenarios/ground/gr-d01-darvo-trigger.yaml",
"scenarios/ground/gr-d05-darvo-reverse.yaml",
"scenarios/ground/gr-e02-shared-ground.yaml",
"scenarios/ground/gr-e04-coalitions.yaml",
],
"six provisional defaults": sorted(
os.path.join("scenarios/ground", f)
for f in os.listdir("scenarios/ground")
if f.endswith(".yaml")
and "provisional: true" in open(os.path.join("scenarios/ground", f)).read()),
"GR-E03/GR-E04 never played": ["evidence/CB-EV-0007-stage-0.md"],
}

View file

@ -2,7 +2,7 @@
"""design — report the finding register (CB-WP-0022 T05).
Implements the metrics in `specs/GameDesign.md` §4 over the register in
`specs/FindingRegister.md`. ADR-0012 D8 retired `design-baseline.py`, which
`specs/GroundRules.md`. ADR-0012 D8 retired `design-baseline.py`, which
was a hand-maintained dict counting itself; the difference that matters is
that **every number here is computed over rows that name real files, and
the reproduction check stats the file.**
@ -16,7 +16,7 @@ directly: a row citing a nonexistent file must NOT count as reproduced.
import os, re, sys, subprocess, datetime
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
REGISTER = os.path.join(ROOT, "specs", "FindingRegister.md")
REGISTER = os.path.join(ROOT, "specs", "GroundRules.md")
BEGIN = "<!-- design-register:begin -->"
END = "<!-- design-register:end -->"
@ -94,7 +94,7 @@ def alarming(row, root=ROOT):
def report(root=ROOT, today=None):
today = today or datetime.date.today()
rows = parse(open(os.path.join(root, "specs", "FindingRegister.md")).read())
rows = parse(open(os.path.join(root, "specs", "GroundRules.md")).read())
findings = [r for r in rows if r["state"] != NOTE]
notes = [r for r in rows if r["state"] == NOTE]
@ -179,31 +179,12 @@ def self_test():
all(r["role"] in ("counterexample", "default")
for r in rows if r["repro"] not in ("", "-", "")))
# CB-WP-0026 T03. A scenario may claim a U-item only if it names it,
# because two plausible mappings were written and both were wrong on
# reading what the scenario actually exercises (gr-a04 asserts consent
# is required, not U1's timing; gr-d05 is the unrejected REVERSE, not
# U5's rejected one). A claim nobody can check is how three wrong
# premises reached ground-game.
import glob
for path in glob.glob(os.path.join(ROOT, "scenarios", "ground", "*.yaml")):
body = open(path).read()
m = re.search(r"^encodes_u_item:\s*(\S+)", body, re.M)
if m:
check(f"{os.path.basename(path)} names the U-item it claims",
re.search(rf"\b{m.group(1)}\b", body) is not None, m.group(1))
# The measured claim: exactly one U-item has a scenario encoding it.
#
# **Asserted on the DECLARATION, not on a mention.** The first version
# grepped for `\bU<n>\b` and went red the moment two scenarios recorded
# *why they do not* encode U1 and U5 — a mention is not a claim, and a
# measurement that cannot tell them apart is the loose proxy that let
# "six of the ten have provisional scenarios" stand unchallenged.
claimed = sorted({re.search(r"^encodes_u_item:\s*(\S+)", open(p).read(), re.M).group(1)
for p in glob.glob(os.path.join(ROOT, "scenarios", "ground", "*.yaml"))
if re.search(r"^encodes_u_item:\s*\S+", open(p).read(), re.M)})
check("exactly one U-item is encoded by a scenario", claimed == ["U2"], f"{claimed}")
# The measured claim in the backfill: only U2 cites its U-item.
cited = [u for u in [f"U{i}" for i in range(1, 11)]
if subprocess.run(["grep", "-rlE", rf"\b{u}\b"] +
[os.path.join(ROOT, "scenarios", "ground")],
capture_output=True, text=True, cwd=ROOT).stdout.strip()]
check("exactly one U-item is cited by a scenario", cited == ["U2"], f"{cited}")
print("design self-test (positive control)")
return 0 if ok else 1

View file

@ -154,39 +154,11 @@ def self_test():
check("code-id matcher finds none in unmarked source",
parse_code_ids("fn f() { let x = 1; }") == set())
# T08: every provisional scenario must carry an owner and a date.
#
# **Tested against a fixture, not against the repo.** This control used
# to read `scenarios/ground/*.yaml` and require `bool(prov)` — a guard
# against a vacuous pass, which is the right instinct, but it made the
# control depend on the repo *having* provisional items. CB-WP-0026
# lifted the last five (ground-game ruled them all on 2026-08-03) and
# the control went red for the best possible reason: nothing is
# provisional any more.
#
# A positive control must construct the condition it tests. The live
# count is a diagnostic below, not a pass criterion — there is no
# number of provisional items this project *should* have.
import glob as _g, tempfile as _tf, os as _os
with _tf.TemporaryDirectory() as _d:
good = _os.path.join(_d, "good.yaml")
bad = _os.path.join(_d, "bad.yaml")
open(good, "w").write("scenario: x\nprovisional: true\n"
"provisional_owner: ground-game\n"
"provisional_raised: 2026-07-31\n")
open(bad, "w").write("scenario: y\nprovisional: true\n")
g = provisional_items([good])
b = provisional_items([bad])
check("a provisional item with an owner and a date passes",
len(g) == 1 and all(o and r for _, o, r in g))
check("a provisional item MISSING its owner/date is caught",
len(b) == 1 and not all(o and r for _, o, r in b),
"without this the check passes on anything it can parse")
live = provisional_items(sorted(_g.glob("scenarios/ground/*.yaml")))
check("every LIVE provisional item has an owner and a raised date",
all(o and r for _, o, r in live),
f"{len(live)} provisional item(s)"
+ (" — none; all ruled 2026-08-03 (CB-WP-0026)" if not live else ""))
import glob as _g
prov = provisional_items(sorted(_g.glob("scenarios/ground/*.yaml")))
check("every provisional item has an owner and a raised date",
bool(prov) and all(o and r for _, o, r in prov),
f"{len(prov)} provisional item(s)")
# --- CB-WP-0005 T01: the kernel denominator ---
# The control the old arm was fixed for, replicated. A pattern that

View file

@ -2,7 +2,7 @@
id: CB-WP-0021
kind: product
title: "Import the edition: the game plays its own data"
status: done
status: active
state_hub_workstream_id: "782b1c37-f3a7-469b-87a3-fa73ebe758d2"
---
@ -278,7 +278,7 @@ collections.
```task
id: CB-WP-0021-T04
status: done
status: todo
priority: high
state_hub_task_id: "ba76138d-a225-470d-bb2a-3a6881f4ca82"
```
@ -293,26 +293,3 @@ state_hub_task_id: "ba76138d-a225-470d-bb2a-3a6881f4ca82"
score against the threshold.
- **Quote CB-WP-0020's cost by re-running the instrument.**
- **Chaos: 4 of 12 in window 2.**
**Done 2026-08-05.**
[CB-EV-0019](../evidence/CB-EV-0019-import-the-edition.md).
- **The budget was the wrong one, and the ADR corrected it**: `csv` costs
17,651 against AM-4b's 19,742, so it *fits* — and is refused on
proportion (89% of the remainder to read 20 rows). Third premise this
pass corrected, second against a mis-chosen denominator.
- **The hash blast radius was predicted, then measured to be ~0** — ADR-0011
§D3 found **0 scenario files pin a state hash**. Verified today:
`replay-test` 14/14, AM-7 0.985/0.998/1.018 against a 0.9 floor. What
actually broke was six scenarios' *data*, not any logic.
- **The endings mean something, and they are tight.** Played 2p all-bots:
**total 4 / threshold 5, group failure**, 6 available. T02's control
predicted a full clear is required at 2p — confirmed by play.
- **The cost chain snapped.** `cb-cost.py --slug CB-WP-0020` **aborts**
no transcripts retained — so T04's last bullet cannot be satisfied, and
the instrument refusing to estimate is the correct behaviour. 41.6% of
spend is UNATTRIBUTED. The quote-the-previous-pass rule needs a bound.
- **The sharpest thing in it**: the work that disproved GR-E01 is *in this
workplan*, and CB-WP-0022 was written the next day quoting GR-E01 as its
founding example. Nothing connected them until a separate agent ran
`git log` four days later.

View file

@ -1,286 +0,0 @@
---
id: CB-WP-0026
kind: product
title: "Collect the rulings: ten answers that arrived and were never applied"
status: done
---
# Purpose
```
structural tier S (applies rulings inside an existing capability —
scenario data and a spec section, no new port, no
canonical interface, no dependency)
chaos d8 = 6 → no override
declared tier S
```
Declaration 9 of chaos window 2.
## The register's first run found this, which is the only reason it exists
`ground-game` **ruled all ten U-items on 2026-08-03** (GROUND-WP-0002 T05)
and confirmed five of six provisional scenarios (T03). Every U-item was
**confirmed** as the default clay-borg already simulates. The workplan is
`finished`.
**clay-borg never collected the answers.** CB-RES-0007 reported *"0 of 10
ruled"* on 2026-08-03 — the same day — and CB-WP-0022 built the finding
register two days later still recording them as `reported`. It took
`make design`'s first run to notice.
**This is the unread-inbox failure running in the opposite direction.**
The whole design-instrument pass was shaped around *we send findings and
nobody reads them*. The symmetric failure — *they answer and we do not
collect it* — was not anticipated by the declaration, the survey, or the
adversarial review.
**Nothing here is a rules decision.** Every ruling is already made. This is
clay-borg applying them, which is the `ruled``applied` transition
ADR-0012 D5 defines: *the source changed and the provisional default was
deleted.*
## Why the rulings being "confirm" makes this more subtle, not less
Every U-item was confirmed, so **no kernel behaviour changes**. It is
tempting to conclude nothing needs doing.
That is wrong, and the register is what makes it visible: the scenarios
still carry `provisional: true` for choices that are now **settled**. A
provisional flag means *we picked this and the owner has not ruled*. After
2026-08-03 that statement is false in five files, and `make coverage`
keeps ageing them toward a 30-day warning for a question that has been
answered.
**A flag that outlives its question is the same defect as a hash that
outlives its content** — ADR-0011's *"a lie with a timestamp."*
## Task: record the rulings where the defaults live
```task
id: CB-WP-0026-T01
status: done
priority: high
```
`specs/GroundRules.md §Underdetermined` states ten defaults as *"must be
confirmed or corrected by ground-game."* All ten now are. Each U-item
gains its ruling and the date, from GROUND-WP-0002 T05.
**Quote the ruling, do not paraphrase it.** Three carry notes that are
more than "confirm" and a paraphrase would lose them:
- **U1** — the Bond formed by *this* Support still does not cancel DARVO
(a printed designer note, and it is the interaction GR-L05 encodes);
- **U2** — clamp on every application, but **DARVO still arms only at End**
when at Stress 5 (GR-R08). The clamping rule and the trigger rule are
separate, and conflating them is the obvious misreading;
- **U8** — GROUND—OU applies first and **Protection is consumed only if it
actually cancels**.
**Controls:**
- the §Underdetermined preamble no longer says the items await a ruling —
it is the sentence that will otherwise be true-looking and wrong;
- **U2, U6 and U8 are the ones ground-game flagged as changing play.**
Each must be checkable against the kernel behaviour it describes, or the
record says a rule is implemented that may not be.
**Done 2026-08-05.** All ten rulings quoted into `§Underdetermined`, with
the three conditional ones (U1's designer note, U2's End-only trigger,
U8's consume-only-if-it-cancels) verbatim. The section heading and
preamble no longer say the items await a ruling.
## Task: lift the provisional flags the rulings settle
```task
id: CB-WP-0026-T02
status: done
priority: high
```
Five scenarios carry `provisional: true` with `provisional_owner:
ground-game`: `gr-a04-bond-support`, `gr-d01-darvo-trigger`,
`gr-d05-darvo-reverse`, `gr-e02-shared-ground`, `gr-e04-coalitions`. All
five were **confirmed** on 2026-08-03. The flag comes off, and each
records which ruling settled it.
**Do not delete the provenance with the flag.** CB-WP-0021 T03 is the
precedent: `gr-e01` was *rewritten, not retired*, because a reader needs
to learn the question was answered rather than that a tag vanished.
**Controls:**
- `make coverage` reports **0 provisional items** afterwards, and that is
the measurable outcome of this task;
- `make sim` still passes — a confirmed default is the behaviour already
implemented, so a red scenario here means the default was **not** what
we told ground-game it was, which is a finding and must be raised, not
patched;
- the ruling date survives in each file.
**Done 2026-08-05.** All five flags lifted; `make coverage` reports **0
provisional items**. Each file gained `ruled` / `ruled_by` / `ruled_note`
instead — the flag came off, the provenance did not.
**The schema refused the new fields and was right to.** `ScenarioFile` is
`deny_unknown_fields`, so five scenarios failed to parse until `ruled`,
`ruled_by`, `ruled_note` and `encodes_u_item` were declared. A scenario
corpus that silently accepted unknown metadata would let a typo'd
`encodes_u_iem` sit there claiming nothing.
**T02's control held: `make sim` is 26 passed, 59 rules covered, with no
scenario red.** That is the answer to the serious version of this task —
had one gone red, it would have meant we described our own behaviour
incorrectly to ground-game, which would be a defect in *our* report rather
than in their ruling.
## Task: make the U-item ↔ scenario mapping checkable
```task
id: CB-WP-0026-T03
status: done
priority: high
```
**Only U2 names its U-item in a scenario.** Measured in CB-WP-0022 T05:
```
for u in U1..U10; do grep -lE "\b$u\b" scenarios/ground/*.yaml; done
```
CB-RES-0007 asserted *"six of the ten already have provisional
scenarios."* Four probably do — but **the link is not written down, so it
is not checkable**, and an uncheckable link asserted as a fact is the
defect class that produced three wrong premises.
Each scenario that encodes a U-item default names it. Where no scenario
covers a U-item, **say so** rather than inventing a mapping — that is the
honest half, and guessing here would reproduce exactly the error being
fixed.
**Controls:**
- a check that every U-item claiming a reproduction resolves to a file
that **names it**`design.py`'s self-test already asserts the current
state (`cited == ["U2"]`) and must be updated to the new truth, not
deleted;
- the register's reproduction column matches the mapping, since a register
that disagrees with the files is worse than none.
**Done 2026-08-05, and it caught me writing the same defect a third and
fourth time.**
I wrote two mappings from the `covers:` lists — `gr-a04` → U1, `gr-d05`
U5 — and both are wrong on reading what the scenarios actually exercise:
| claimed | why withdrawn |
|---|---|
| `gr-a04` → U1 | it asserts consent is **required**; U1 asks **when** the target accepts |
| `gr-d05` → U5 | it exercises the **unrejected** REVERSE; U5 is the **rejected** one (GROUND—ND) |
Both were plausible from `covers:` and neither survived reading the
description. **The measurement stands: one U-item, U2.** Nine have no
reproduction and are recorded as having none.
`encodes_u_item` is now a declaration, and `design.py` asserts a scenario
naming a U-item actually contains it. **The check's first version was
itself the loose proxy**: it grepped for `\bU<n>\b` and went red when two
files recorded *why they do not* encode U1 and U5. A mention is not a
claim — and that is exactly the looseness that let *"six of the ten have
provisional scenarios"* stand unchallenged.
## Task: close the register, and see whether it closes
```task
id: CB-WP-0026-T04
status: done
priority: high
```
Move U1U10 from `ruled` to `applied` and re-run `make design`.
**The number to beat is `open, lacking a reproduction: 9 → 0`**, and the
queue emptying is the first evidence that ADR-0012 D5's lifecycle is real
rather than drawn.
**If it does not reach 0, do not adjust the metric.** Whatever remains is
either a U-item with no scenario (T03's honest half, which should then be
a `note`, not a finding) or a state the lifecycle cannot express — and the
second is a finding about ADR-0012 that belongs in evidence.
**Controls:**
- `make design` shows an empty queue for the U-items, with the log
carrying all ten;
- `make self-tests` and `make sim` pass;
- **the closure is checkable from the register alone** — a reader should
not need this workplan to see that ten findings closed on 2026-08-03 and
were applied on 2026-08-05.
**Done 2026-08-05. The queue is empty.**
```
QUEUE (open findings) (none)
open, lacking a reproduction 0 target 0
closed (log) 12 [U1..U10, F11, F13]
with a resolving reproduction 3/12 = 25%
```
**`open, lacking a reproduction: 9 → 0`** — the number this task named.
It is the first evidence that ADR-0012 D5's lifecycle is real rather than
drawn: findings entered a state, moved through it, and left the queue.
**25% reproduced is not a failure and must not be read as one.** Nine
U-items closed by a *ruling*, and a ruling is not an artifact. The metric
is now honest about a thing the survey's 33% concealed: most of our
findings closed because someone answered them, not because anything
demonstrated them.
## Task: evidence
```task
id: CB-WP-0026-T05
status: done
priority: medium
```
`evidence/CB-EV-0022-*.md`. Short — this is an S pass.
- **How long the answers sat uncollected**, and what noticed them. Two
days, and the thing that noticed was the register's first run, not a
human and not the review.
- **Whether any confirmed default turned out not to match the kernel**
T02's control. A red scenario here would mean we described our own
behaviour incorrectly to ground-game, which would be a fourth premise
defect and the most serious kind, since it was *our* behaviour.
- **What the mapping gap really was**: whether the four unlinked scenarios
encode U-item defaults at all, or whether CB-RES-0007's "six of ten" was
simply wrong.
- **Whether the queue reached 0**, and if not, what the lifecycle could not
express.
- **Quote CB-WP-0022's cost by re-running the instrument** — and note that
CB-EV-0019 §4 found this chain snaps at roughly four passes, so if the
instrument aborts, that is the second instance and the rule needs its
bound.
**Done 2026-08-05.**
[CB-EV-0022](../evidence/CB-EV-0022-collect-the-rulings.md).
- **Two days uncollected, and the register's first run is what noticed**
not a human, not the adversarial review that found four other things.
- **No confirmed default failed to match the kernel**: `make sim` 26
passed, 59 rules covered, nothing red. The serious version of this task
came back clean.
- **The mapping gap was not under-documentation, it was error.** At least
two of the four unlinked scenarios do **not** encode the U-items the
survey implied, and I wrote both wrong mappings before checking them.
- **The queue reached 0.** `applied` conflates *confirmed* with
*overturned*, which is the lifecycle gap this pass found and did not fix.
- **The cost chain held this time** — CB-WP-0022 is one pass back and
still in the window. The bound CB-EV-0019 asked for is still owed.
**A gate went red for the best possible reason.** `rule-coverage.py`'s
positive control required `bool(prov)` — at least one provisional item to
exist. Lifting the last five turned it red. The guard was the right
instinct (a vacuous pass is worthless) wired the wrong way: it bound a
control to live repo data. It now builds its own fixture, asserts the
missing-owner case is *caught*, and reports the live count as a diagnostic
— because there is no number of provisional items this project should
have.