Provenance (tier S, one paragraph in lieu of survey and ADR): the human check that kept INTENT stage 1 open was run and the drag was broken. Root cause, worth more than the instance: drop targets were ids, and an id must be unique, so exactly one element could ever be seat-0. The relationship-graph circle took it and the seat card that every action card's own text points at -- 'drag Attack onto a seat' -- silently had none. A seat is drawn twice and both drawings are the seat; the document model could not express that. Drop keys are now data-drop. Any number of elements may carry the same key, so a seat is droppable on its card and on its graph node. Measured on a live server: seat-0/1/2 each appear twice, id survives only on cb-status which is the one element the script looks up, and down=action-attack&up=seat-1 returns ok. Second defect: a drop on nothing returned without posting and without touching the status line, so a broken target was indistinguishable from a working page. resolve already refuses rather than defaulting, which is right; refusing SILENTLY is not. The page now reports the raw fact -- 'took action-attack, let go over nothing droppable' -- which names elements, not moves, so ADR-0007 control 5 holds. And the honest part: the general check added here -- every offered affordance names a key that exists, driven through Policy::choose over four real bot games -- does NOT catch the reported defect. seat-0 did exist, on the graph circle. It is kept because a wholly absent target is a real class, and paired with a targeted regression test that does catch it. Three mutations, each red for its stated reason, including the reported defect reintroduced; only the targeted test fires on that one. A cb-play assertion matched id="action-ground" as a substring while describing itself as checking the page; rewritten through drop_keys. make all exits 0. Stage 1 stays open: verified by tests, mutation and a live server, not by a human dragging. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.3 KiB
| id | kind | title | status |
|---|---|---|---|
| CB-WP-0016 | product | The drop target that was never there | done |
Purpose
structural tier S (a defect fix inside an existing capability — no new
port, no canonical interface, no new dependency, and
the check it adds is a product test, not a control
gate)
chaos d4 = 3 → no override
declared tier S
Declaration 11 of 12. Tier S: one provenance paragraph in the commit message, no survey and no ADR. Steps 4–5 are not skipped.
Found by the human check, and by nothing else
CB-EV-0012 §4 kept INTENT stage 1 open on one action the loop could not
perform: run cb-play --serve 0, open the URL, confirm the table reads and
a drag works. The maintainer ran it on 2026-08-02. The table reads. The
drag does not.
Diagnosed against the running server before any code was changed:
POST down=action-attack&up=seat-1 → ok (the game advanced)
POST down=action-attack&up=action-attack → "not a legal move here"
So the socket, the token guard, resolve, and command dispatch are all
correct. The defect is in the emitted page.
seat-{n} ids exist only on the ~26 px circles inside the relationship
graph (<g id="seat-0">, doc.rs:185). The visible seats section
cards are emitted by player_card as <div class="card" data-viewer=…>
with no id. Every action card says "drag Attack onto a seat, a
problem, or the table", and the seat it points at is not a target.
#table, problem-{n} and freedom-{n} are real targets, so the page is
not wholly inert — the most natural target is.
Why every test in the repo passed
jsrun::gesture(html, down, up) calls __down(id), which synthesizes
{ target: { id: id } }. It feeds element ids straight in and never
hit-tests. So it establishes "the script posts the ids it was given"
and never "the page has an identified element where the instruction says
to drop."
The 42-path coverage gate is blind for a related reason: it asserts each
view field is present in the parsed document, which a <div> with no
id satisfies perfectly.
CB-WP-0015 closed a clause whose test was named for a property it did not check. This is the same shape one layer out — a harness answering a narrower question than its name implies — and it is the argument for having kept the human check as a blocker rather than closing stage 1 on green tests two passes ago.
Task: make the seat a drop target, and keep ids unique
id: CB-WP-0016-T01
status: done
priority: high
The seat card must carry the id affordance() already names for it.
seat-{n} is currently taken by the SVG node, and duplicate ids are
invalid HTML — so one of the two has to change, and both must remain
droppable. The graph circle works today and someone will have learned it.
Decide where the aliasing lives. Rust decides what a pointer fact means
(ADR-0007 D5), so widening what resolve accepts is in bounds and
inventing a new id vocabulary in the page is not.
Control: dropping on the seat card and dropping on the graph node must produce the same command, and a test must go red if either stops resolving.
Done 2026-08-02. The aliasing was not needed, because the constraint that caused the defect was removed instead.
Drop keys are now data-drop, not id. An id must be unique, so
exactly one element could ever be seat-0 — the graph circle took it and
the card went without. Any number of elements may carry the same
data-drop, so a seat is droppable on both of its drawings. Measured on
the live page: seat-0/1/2 each appear twice, and id survives on
exactly one element, cb-status, which is the only one the script looks
up. down=action-attack&up=seat-1 against a real server returns ok.
The mutation that reintroduces the reported defect — the seat card losing its key — goes red, and only the targeted test fires; the general existence check stays green. That is the point of T03's finding.
Task: a gesture that lands nowhere must say so
id: CB-WP-0016-T02
status: done
priority: high
SCRIPT currently does if (!down || !up) { down = null; return; } — no
POST, no status line, nothing. That silence is why this defect survived a
human sitting in front of it.
The Rust side already holds the right principle: resolve refuses rather
than substituting a default, because "a drag that means nothing must mean
nothing, not the first legal move." Refusing is correct. Refusing
silently is not.
Make the outcome visible. Keep ADR-0007 control 5 intact — whatever the page reports must still be raw pointer facts with no game vocabulary, and the existing body-shape assertion must still hold.
Done 2026-08-02. The page now writes "took action-attack, let go over
nothing droppable" to the status line. It names elements, not moves, so
control 5 holds and the body-shape assertion is untouched. Restoring the
bare return turns
jsrun::tests::a_drop_on_nothing_reports_instead_of_going_quiet red with
"a drop on nothing said \"\"".
The JS DOM stub had to grow a real getAttribute and a null-parent node
to model a pointer landing on nothing — it previously could not express
the case at all, which is part of why the silence was invisible.
Task: the check that closes the class
id: CB-WP-0016-T03
status: done
priority: high
For every legal command, both ids affordance() produces must occur in
the emitted document. Pure Rust, no JS engine, no browser, deterministic.
It fails on today's tree and it would have caught this the day the
renderer was written.
This is the deliverable that matters — T01 fixes one instance, this closes the class. An affordance naming an element that does not exist is exactly the harness-does-nothing shape, and it currently has no detector at all.
Then evidence/CB-EV-0014-*.md:
- What the human check bought, priced honestly against what it cost to keep stage 1 open for two passes.
- Whether stage 1 closes now. It needs the maintainer to drag again; say so rather than closing it on a passing test, which is the failure mode this whole pass is evidence for.
- Quote CB-WP-0015's final cost, not this pass's — and note that CB-EV-0013 §5 found the self-quoting rule fixes the wrong boundary, so say plainly whether the figure has settled.
- Chaos: declaration 11 of 12. The calibration window closes next declaration.
Done 2026-08-02.
CB-EV-0014. make all
exits 0.
- The general check does not catch the reported defect, and the evidence
says so plainly.
seat-0existed — on the graph circle — so an existence check over the whole document cannot tell that the element the user is pointed at is not the one that works. It is kept because a wholly absent target is a real class, and paired with a targeted regression test that does catch it. - Three mutations, each red for its stated reason, including the reported defect reintroduced.
- Stage 1 does not close here either. The fix is verified by tests, by mutation, and against a live server — not by a human dragging, which is the standard that found it.
- A cb-play assertion drifted: it matched
id=\"action-ground\"as a substring and silently described itself as checking the page. Rewritten throughdoc::drop_keys.