clay-borg/evidence/CB-EV-0014-the-drop-target.md
tegwick a55e878bf0
Some checks failed
ci / check (push) Failing after 4s
CB-WP-0017: legible interaction, and the chaos window's verdict
Provenance (tier M, structural S, chaos d4=4 -> OVERRIDE drawn M):
the maintainer could drag after CB-WP-0016 but could not tell what was
pickable, held, or droppable. Underneath that, the page was WRONG about
which moves exist: 9 legal commands rendered as 5 cards each claiming
all three target kinds, from a const string in the emitter. Investigate
is legal on problems 2 and 3 but not 1; Solve on 1 but not 2 or 3. The
live page now says 'Solve onto problem 1'.

ADR-0010 restates control 5, which this work would otherwise have
outgrown in silence: every game fact the page acts on must arrive from
Rust as data; the script may read, match and render it, never compute,
infer, filter or default one. The survey's real finding is that the
permitted and forbidden designs are indistinguishable from outside, so
the vocabulary grep is demoted to a cheap first line and two behavioural
properties become the controls -- the highlighted set EQUALS the set
Rust emitted, and anything the page marks legal must resolve. Both
mutation-proven; the derive-legality mutation produces a plausible
highlight (seat-0,1,2 where only seat-1 is legal) and is caught.

Visible now: .pick resting shadow, .held on the grabbed element, .dropok
on every legal target including BOTH drawings of a seat, and a ghost
following the pointer. Nothing perceptual is verified and ADR-0010 D5
says so.

The DOM stub now models classList/querySelectorAll/createElement and
builds its node set from the real emitted page. Trap recorded: QuickJS
fixes its stack limit at Context creation relative to that frame, so a
helper returning a Context makes every later eval report
'SyntaxError: stack overflow'.

CHAOS WINDOW CLOSED, 12 declarations, 2 overrides, one each way. Both
changed the outcome, so the retirement condition is not met. Verdict:
keep, and recommend d4 -> d8 with a second window of 12 -- that is a
change to the loop's own constraints and is owed to the next declaration
as tier-M work, not made here.

CB-EV-0014 corrected: it quoted CB-WP-0015 at $15.14/136 and called it
the first settled figure quoted. Now $22.70/166. The number had been
read during CB-WP-0015 itself, so there are two defects -- the boundary,
and quoting from memory instead of re-running the instrument.

make all exits 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 22:42:45 +02:00

7.1 KiB

CB-EV-0014 — what the human check bought

CB-WP-0016 T03. Measured 2026-08-02 at 4df2d0a+. Pass kind product, tier S (chaos d4=3, no override). Declaration 11 of 12.

Cost quotes CB-WP-0015's figure, per CB-EV-0012's rule and with the correction CB-EV-0013 §5 attached to it. See §5.


1. The check found a defect every test in the repo was blind to

CB-EV-0012 §4 kept INTENT stage 1 open on one action the loop could not perform. The maintainer ran it. The table reads. The drag did not work.

Diagnosed against the live server before any code changed, which is what made the rest cheap:

POST down=action-attack&up=seat-1        →  ok   (the game advanced)
POST down=action-attack&up=action-attack →  "not a legal move here"

Socket, token guard, resolve and dispatch: all correct. The defect was in the page, and it had a 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 — which 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.

2. Why nothing caught it, stated precisely

control why it was blind
jsrun::gesture calls __down(id), which synthesized {target:{id}}. It feeds element ids straight in and never hit-tests — it establishes "the script posts the ids it was given", never "there is an element there to give"
the 42-path coverage gate asserts each view field is present in the parsed document. A <div> with no id satisfies that perfectly
resolve's unit tests test the mapping from a fact to a command. The fact never arrives
M-D1-MUT its population is the AM-* acceptance rows. None of them is about the page

Every one was green. This is the shape CB-WP-0015 closed one layer in — a harness answering a narrower question than its name implies — recurring in the presentation layer.

3. The fix, and the check that is honestly insufficient

Drop keys are now data-drop, not id. Any number of elements may carry the same key, so a seat is droppable on its card and on its graph node. Measured on the live page: seat-0, seat-1, seat-2 each appear twice; id survives on exactly one element, cb-status, which is the only one the script looks up.

Two checks, and the difference between them is the finding:

check catches the reported defect?
every offered affordance names a key that exists — drives four real bot games through Policy::choose, renders at every real decision point NO. seat-0 did exist, on the graph circle
every seat card is a drop target, not only the graph node yes

The general check is worth having — it fails when a target is wholly absent, which is a real class — but it would not have found the bug the maintainer found, and saying otherwise would be the exact error this project keeps catching. An existence check over a whole document cannot tell you the element the user is being pointed at is the one that works.

Hooking Policy::choose rather than re-driving the game by hand matters: those are the same decision points cb-play --serve renders at, with the same legal list. A hand-rolled walk would be a second implementation of the loop, free to agree with itself while disagreeing with what ships.

Mutations, each red for its stated reason

mutation result
the seat card loses its drop key — the reported defect, reintroduced red, and only the targeted test fired; the general one stayed green
the table stops being a drop target red — "whose DROP id table is not an element in the document"
the silent return comes back red — "a drop on nothing said """

4. Silence was the second defect

SCRIPT did if (!down || !up) { down = null; return; } — no POST, no status line, nothing at all. That is why a human sitting in front of it could not tell a broken target from a working page.

The Rust side already held 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 right. Refusing silently is not, and the two had been conflated.

The page now reports the raw fact — "took action-attack, let go over nothing droppable". It names elements, not moves, so ADR-0007 control 5 is intact and the body-shape assertion still holds.

5. What the human check cost, and what it bought

Stage 1 was held open on this check for two passes (CB-WP-0014, CB-WP-0015), against a standing temptation to close it on green tests — CB-EV-0012 §4 recorded that temptation explicitly and refused it.

It bought a defect that made the stage's headline interaction non-functional on its primary target, plus a root cause in the document model, plus a control class that did not exist. Two passes of delay was the right price, and the reasoning that kept it open — "no test in this repo can reach it" — was exactly correct rather than merely cautious.

The stage does not close here either. The fix is verified by tests, by mutation, and against a live server; it is not verified by a human dragging. That is the same standard that found this, and the same one that would have missed it.

pass kind responses cost $/response
CB-WP-0015 product 136 166 $15.14 $22.70 0.111 0.137
CB-WP-0016 product provisional — not quoted

CORRECTED 2026-08-02 (CB-EV-0015 §6). This file originally reported CB-WP-0015 at $15.14 / 136 and claimed that figure was "the first this project has quoted at a boundary that had actually settled." The claim was false and the number was wrong. $15.14/136 was read from make status during CB-WP-0015 itself and carried forward — it was that pass's own in-flight figure, which is precisely what CB-EV-0012's rule exists to forbid. Measured after CB-WP-0017's declaration closed the window: $22.70 / 166, higher by 50%.

So the defect is not only the boundary CB-EV-0013 §5 identified. It is that a figure gets read once and quoted later, and a number read earlier in a session is an in-flight number no matter which pass's name is on it.

6. Open

  • INTENT stage 1: still one human verification, now of a fix rather than of an unknown. cb-play --serve 0, drag an action onto a seat card.
  • The self-quoting rule still names the wrong boundary — quote two passes back. Owed since CB-EV-0013 §5.
  • AM-4b's scope defect (408,237 uncounted lines) and its unmeasured proc-macro share.
  • python3 as a toolchain dependency was never argued.
  • AM-4a cannot survive stage 2 — 1,741,979 against 161,000.
  • ADR-0007 D3's acquisition rule remains unratified after deciding two dependency questions.
  • Chaos: 11 of 12 declarations, 1 override. The calibration window closes on the next declaration and owes an evaluation.