Some checks failed
ci / check (push) Has been cancelled
CB-EV-0009. Tier S did not produce a worse outcome than tier L would have. The roll deleted a survey that would have opened on 2D toolkit selection; the decomposition it forced instead found that the existing text renderer was showing 24 of 41 view fields. The structural trigger fires on a property of the plan, not of the code, so nothing in the tier derivation could have said the port was the wrong first question. Recorded honestly in both directions: the pass also made an interface change with no review, which is the cost side. One favourable fire is not a calibration; the window stays open to 2026-09-30. Corrects a number this pass asserted: the T01 commit message says '42 of 43, up from 24'. Measured by splicing the old renderer back in, it is 41 of 42 with 1 declared omitted, up from 24 -- and 16 fields were genuinely absent, not 17, because outcome.winners was rendered in a different format. Both original figures were counted by hand. CHAOS gains its first caught entry. Cheapest pass per response yet recorded (0.094 vs a previous best of 0.123).
236 lines
11 KiB
Markdown
236 lines
11 KiB
Markdown
---
|
||
id: CB-WP-0011
|
||
kind: product
|
||
title: "Stage 1, first slice: an inspector that shows everything"
|
||
status: done
|
||
state_hub_workstream_id: "1142442a-65f1-483a-800d-6ca8490c5f1a"
|
||
---
|
||
|
||
# Purpose
|
||
|
||
Tier **S** — and this is the **first time the chaos roll has changed
|
||
anything** in six declarations.
|
||
|
||
```
|
||
structural tier L (INTENT stage 1 creates a new capability port)
|
||
chaos d4 = 4 → override
|
||
override roll shuf -e S M L → S
|
||
declared tier S (structural L, chaos 4 → S)
|
||
```
|
||
|
||
Declaration 6 of 12 in the calibration window. The `CHAOS` gate in
|
||
`gates.toml` has `caught = []` and a `review_by` of 2026-09-30; this pass
|
||
is the first entry it can earn, whichever way the outcome falls.
|
||
|
||
## What tier S is allowed to mean here
|
||
|
||
A rolled-down tier relaxes *process weight*. It does not relax the hard
|
||
gate — **no implementation code for a capability exists before its ADR is
|
||
committed** — because that gate is stated unconditionally and the tier
|
||
table governs only "weight of steps 1–3", not whether step 3 happens.
|
||
|
||
The two rules meet head-on: stage 1's headline deliverable is a rendering
|
||
port (`cb-render-api` / `-null` / `-wgpu`, ArchitectureBlueprint §Port
|
||
pattern), a port needs an ADR, and tier S has no ADR.
|
||
|
||
**Resolution: split the declaration rather than lawyer either rule.**
|
||
This packet is the part of stage 1 that creates no port, adds no
|
||
dependency, and therefore trips no ADR gate. The GUI half gets its own
|
||
tier declaration and its own chaos roll later, informed by what this pass
|
||
learns. Two facts make that ordering the right one anyway:
|
||
|
||
- **AM-4a has 3,750 lines of headroom** (246,250 of 250,000 measured by
|
||
`make dep-weight`). Any 2D toolkit is a five- to six-figure line count.
|
||
The dependency decision needs a survey, and the roll just deleted the
|
||
survey — so the honest move is to not make the decision this pass.
|
||
- The second-use rule cuts the same way. A renderer that has never had to
|
||
show the whole of `GroundView` does not yet know what a view needs.
|
||
|
||
## The finding this packet acts on
|
||
|
||
`tools/cb-play/src/table.rs:113` already has
|
||
`render(view: &GroundView) -> String`. It is stage 1's renderer, written
|
||
in stage 0 to prompt a human, and **it drops roughly a third of what the
|
||
projection carries**:
|
||
|
||
| in `GroundView` | in `render` |
|
||
|---|---|
|
||
| `mode`, `focus`, `solution_discard` | absent |
|
||
| `ground_modes`, `ground_choices` | absent |
|
||
| `support_responses`, `darvo_targets` | absent |
|
||
| `protection`, `freedom_gate_lifted` | absent |
|
||
| `protected_this_round` on a Problem | absent |
|
||
| `personal`, `coalitions`, `mastery` on the outcome | absent |
|
||
|
||
Nothing is wrong: it was written to show a human their legal moves, and
|
||
it does. But "inspectable" is the word INTENT uses for stage 1, and an
|
||
inspector that silently omits the DARVO state machine and the GROUND
|
||
practice — the two mechanics this game exists to model — is not one.
|
||
|
||
The interesting part is that **no test could have caught this**, because
|
||
every assertion a renderer test naturally makes is satisfied by any
|
||
render. That is the harness-does-nothing class again, in its presentation
|
||
form.
|
||
|
||
## Task: the inspector shows everything, and a gate says so
|
||
|
||
```task
|
||
id: CB-WP-0011-T01
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "b8cb4c20-9d06-47a4-890c-bf8390015439"
|
||
```
|
||
|
||
Extend `render` to cover every field of `GroundView`, and — the load-
|
||
bearing half — add the check that keeps it covering them.
|
||
|
||
**The check.** Serialize the view to `serde_json::Value`, walk it for
|
||
every object key reachable in a populated state, and require each key to
|
||
be classified in exactly one of two lists in the test:
|
||
|
||
- `RENDERED` — the inspector shows it, asserted against a state where the
|
||
field holds a distinctive value that appears in the output;
|
||
- `OMITTED` — with a stated reason per entry (e.g. `hand_size`, which is
|
||
redundant when `hand` is shown to its owner).
|
||
|
||
A new field on `GroundView` that is in neither list **fails the test**.
|
||
That is the same shape as `not_control_gates` in `gates.toml`, for the
|
||
same reason: silence must cost something.
|
||
|
||
**Vacuity controls (M-D1-MUT).** The test must go red for a stated reason
|
||
when (a) a field is deleted from the render, (b) a field is moved from
|
||
`RENDERED` to `OMITTED` while still being rendered, and (c) the walk is
|
||
made to return an empty key set — the EXPECT-VACUOUS control, since a
|
||
coverage test over zero keys passes trivially and that is precisely how
|
||
this check would rot.
|
||
|
||
Build the fixture from a **mid-DARVO, mid-GROUND** state, not a fresh
|
||
deal: most of the missing fields are empty at deal time, and a coverage
|
||
test run against a state where the fields are absent is the same lie in a
|
||
different costume.
|
||
|
||
**Done 2026-08-02.** The renderer moved to `tools/cb-play/src/inspect.rs`
|
||
and now covers **41 of the 42 leaf paths** in a populated `GroundView`.
|
||
The one omission is `players.*.hand` — `null` for a non-viewer seat,
|
||
where the absence is what `hand N card(s)` renders.
|
||
|
||
**Measured, not counted** (T03): the old renderer produced 24 of the 41
|
||
tokens. One of the 17 it missed, `outcome.winners.*`, it did in fact
|
||
show — in a different format (`["P1", "P2"]` rather than `winners P1,
|
||
P2`), so the token misses it. **16 fields were genuinely absent**, and
|
||
that is the number to quote. The commit message for this task says
|
||
"42 of 43, up from 24"; both figures were counted by hand before the
|
||
measurement and both were wrong.
|
||
|
||
`every_view_field_is_classified` walks the serialized view for leaf
|
||
*paths* (not keys — `problem` occurs under a DARVO target, a GROUND
|
||
choice and a Selection, and a key-set walk would let one vouch for the
|
||
other two). Four controls, each run and each red for its stated reason:
|
||
|
||
| control | result |
|
||
|---|---|
|
||
| a field deleted from `render` | `players.*.protection is claimed rendered, but the output has no "protect 2"` |
|
||
| a field present in neither list | fired **for real on the first run** — `players.*.hand` |
|
||
| the walk returns no paths (EXPECT-VACUOUS) | `the walk found 0 leaf path(s) — it is not walking the view` |
|
||
| a classified path that no longer exists | `classified path(s) no longer exist in GroundView: ["outcome.nonexistent"]` |
|
||
|
||
The second is the one worth keeping: the gate caught a field I had missed
|
||
while writing the gate, before it had ever been committed.
|
||
|
||
## Task: `cb-play --inspect` walks a recorded game
|
||
|
||
```task
|
||
id: CB-WP-0011-T02
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "08f8a9c7-3dfe-4f72-b510-df9efa457370"
|
||
```
|
||
|
||
Stage 1 asks for a *debug inspector*. The inspector above renders one
|
||
state; this makes it walk a game.
|
||
|
||
Deliver `cb-play --inspect <bundle-or-scenario> [--as SEAT|spectator]`:
|
||
replay the recorded commands and print the full table after each step,
|
||
from the chosen viewpoint. A `.cbreplay` bundle and a scenario YAML are
|
||
both accepted — both already reconstruct a command sequence.
|
||
|
||
This is the first thing in the project that answers *"what did the table
|
||
look like when it went wrong?"* without adding a `dbg!` and re-running.
|
||
It costs no dependency: replay, projection and render all exist.
|
||
|
||
**Acceptance.** Inspecting a recorded game must reproduce the end-state
|
||
hash the bundle records — an inspector that shows a state the replay did
|
||
not reach is worse than none. Assert the hash, and mutate the replay to
|
||
prove the assertion fires.
|
||
|
||
**Projection is not optional here.** `--as 2` must show P3's hand and
|
||
must not show anyone else's. The stage-0 test that checked this was
|
||
vacuous twice before it held (CB-EV-0007); seat the assertion where the
|
||
hidden thing is actually hidden.
|
||
|
||
**Done 2026-08-02.** `cb-play --inspect PATH [--as SEAT|spectator]`
|
||
walks a `.cbreplay` bundle or a scenario YAML and renders the table after
|
||
every step.
|
||
|
||
The design decision worth recording: `replay::replay` was split, and its
|
||
bundle reader extracted as `replay::open`, so **the inspector walks
|
||
through the same reader the replay gate uses** — including the control
|
||
that makes the recorded seed load-bearing. A second reader would let the
|
||
inspector show states a replay never reached, and it would be a
|
||
duplicated fact in the one place where being wrong is silent. The hash is
|
||
then asserted anyway, because a structural argument that is never
|
||
executed is the class of claim this project keeps finding to be wrong.
|
||
|
||
The two modes take **disjoint** flags in both directions
|
||
(`--inspect --seed 9` and a bare `--as 2` are both refused, exit 64): a
|
||
flag accepted and ignored is how a user comes to believe they inspected
|
||
seed 9 when they inspected whatever the recording holds.
|
||
|
||
Three controls, each red for its stated reason:
|
||
|
||
| control | result |
|
||
|---|---|
|
||
| the hash comparison removed | `a_walk_that_does_not_reproduce_fails` fails — the tampered bundle walks clean |
|
||
| `Eyes::Seat` resolved to `Viewer::Spectator` | `P3 shows a hand in every table and nobody else does` |
|
||
| the per-step render dropped | `one table per step plus the opening one` |
|
||
|
||
The third is the one that matters: without it a walk that rendered
|
||
nothing at all would still have reported a matching hash and passed.
|
||
|
||
## Task: evidence, and what the chaos roll cost
|
||
|
||
```task
|
||
id: CB-WP-0011-T03
|
||
status: done
|
||
priority: medium
|
||
state_hub_task_id: "db722865-1233-4918-952d-0bf4761199e4"
|
||
```
|
||
|
||
Write `evidence/CB-EV-0009-inspectable-table.md` covering:
|
||
|
||
1. **Did tier S produce a worse outcome than tier L would have?** The
|
||
only honest way to answer is to name what the deleted survey would
|
||
have decided and check whether this pass needed it. State the answer
|
||
even if it is "no, and that is one data point against the tier table",
|
||
because that is the finding the calibration window is for.
|
||
2. The field-coverage gap as measured before and after T01.
|
||
3. Cost and session shape for the pass, per `make status`. The previous
|
||
pass opened at a **HARD** session-shape breach (338,852 mean context
|
||
against a 300,000 ceiling) and a **45%** trailing-3 meta share against
|
||
a soft 25%. This pass is `product`; report where both land.
|
||
4. Update `gates.toml`: `CHAOS` gets its first `caught` entry — the roll
|
||
fired, and what it changed.
|
||
|
||
**Do not update the ADR or INTENT.** Stage 1 is not shipped when this
|
||
lands; the port half is untouched and INTENT's stage-1 line stays open.
|
||
Marking a stage complete because part of it works is the failure mode
|
||
stage 0 avoided by leaving the CLI player open for three passes.
|
||
|
||
**Done 2026-08-02.** [CB-EV-0009](../evidence/CB-EV-0009-inspectable-table.md).
|
||
The headline: tier S did **not** produce a worse outcome, and the reason
|
||
is not that tier L was unnecessary — the roll forced a decomposition that
|
||
found a defect the survey would have walked past. Recorded as the CHAOS
|
||
gate's first `caught` entry, with the window left open.
|
||
|
||
Also on record, against my own prior: I recommended tier L in the turn
|
||
before the roll, and the roll was right.
|