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).
11 KiB
| id | kind | title | status | state_hub_workstream_id |
|---|---|---|---|---|
| CB-WP-0011 | product | Stage 1, first slice: an inspector that shows everything | done | 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
GroundViewdoes 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
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 whenhandis 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
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
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:
- 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.
- The field-coverage gap as measured before and after T01.
- 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 isproduct; report where both land. - Update
gates.toml:CHAOSgets its firstcaughtentry — 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.
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.