# ADR-0010: the script may render state it was given; it may not derive any status: accepted date: 2026-08-02 decided by: agent, under the standing loop authorization tier: M (structural S — presentation work inside an existing capability; chaos d4=4 → OVERRIDE, drawn M. InnerLoop v1.6). Tier M merges survey and decision into one document, which this is. references: [CB-WP-0017](../workplans/CB-WP-0017-legible-interaction.md), [ADR-0007](ADR-0007-render-html-not-a-port.md) D5 (control 5), [CB-EV-0014](../evidence/CB-EV-0014-the-drop-target.md) §4 ## Context ADR-0007 Decision 5 constrains the page: > the page reports **raw pointer facts and nothing else**, and Rust decides > what command they mean. and `doc.rs` says of `SCRIPT`: *"it does one thing"*. Every request in CB-WP-0017 needs it to do more than one thing — hold a drag in flight, follow the pointer, and add and remove classes on **other** elements. So the constraint has to be restated or the work does not happen. Restating a control in order to permit the thing it forbade is how controls die, and this ADR exists so that happens out loud or not at all. ## The survey: three ways to make interaction legible | option | where legality is decided | script size | |---|---|---| | **A. Rust emits legality as data; the script toggles classes** | Rust | ~2× today | | B. The script derives legality from the rendered page | **the page** | ~3× | | C. Server round-trip on pointerdown to ask what is legal | Rust | ~1.5×, plus a request per grab | **B is the one control 5 exists to forbid**, and it is not hypothetical: the page already contains every action id and every target id, so a script *could* infer plausible pairs. It would be wrong in exactly the cases that matter — Investigate is legal on problems 2 and 3 but not 1, and no amount of looking at the DOM reveals why. **C is honest and too slow.** A highlight that arrives after a network round trip is not a highlight; it also makes a pointerdown a state-changing request, which the token guard's threat model did not consider. **A is the decision.** But the survey's real output is that A and B are *indistinguishable from the outside*: both are "the page highlights some elements". A control that cannot tell them apart is not a control. ## Decision 1 — the script may render state it was given; it may not derive any The replacement for *"it does one thing"*: > **Every game fact the page acts on must arrive from Rust as data.** The > script may read it, match it, and turn it into presentation. It may not > compute, infer, filter, or default one. Under this rule, class-toggling is permitted: the legal target set is a `data-targets` attribute Rust wrote, and the script does string matching on it. Deriving that set by pattern-matching ids would be forbidden even though the visible result is identical. ## Decision 2 — the honest argument, and why the old test was weak The tempting defence is *"the page only renders what Rust said"*. That is exactly what a page constructing commands would also say, so it earns nothing on its own. The existing control is a test that greps `SCRIPT` for game vocabulary. CB-WP-0014 already found that shape too weak once — it was replaced for control 5 by an assertion on **what the script puts on the wire**. The same weakness applies here and is now worse, because the script legitimately handles more. So the vocabulary grep stays as a cheap first line, and the load-bearing control becomes a pair of properties a test can check: 1. **The highlighted set equals the set Rust emitted.** Not a subset, not a superset. A script that derived legality would drift from the attribute the moment the two disagreed, and this catches that with no reference to how the script is written. 2. **A target the page marks legal must `resolve`.** If the page can advertise a drop that Rust then refuses, the two have drifted and the highlighting is worse than none — it would teach the player something false. Property 1 is the real replacement for the grep. It is stated over observable behaviour rather than over source text, which is the same move that fixed control 5. ## Decision 3 — what the script may still never do Unchanged from ADR-0007 D5, and restated because this ADR widens the rest: - it may not name an action, a seat, a rule, or a phase; - it may not construct a command, in any encoding; - what it posts stays **exactly two fields**, `down` and `up`, carrying two element keys and nothing derived from them. The body-shape assertion from CB-WP-0014 continues to hold this, and nothing in this ADR touches it. ## Decision 4 — the size of the script is not the control `SCRIPT` roughly doubles. That is not a violation of anything: ADR-0007's concern was *rules leaking into the page*, not line count, and it said so — *"if a rule ever needs to appear in it, the decision is wrong"*. Marginal AM-4a cost is zero either way, so the dependency budget does not decide this. What decides it is Decision 1's rule plus the two properties in Decision 2. Recording this explicitly so that a future pass does not cite the growth as either a licence or an objection. ## Decision 5 — what this ADR does not claim **No visual property here is verified.** Whether a shadow reads as *"you can pick this up"*, whether a highlight is noticeable, whether a drag feels followable — none of that is reachable by any test in this repo, and CB-WP-0016 is the standing evidence for what happens when that gap is papered over. The testable core is the *correspondence* between what the page marks and what Rust will accept. The perceptual half stays a human check, and stage 1 stays open on it. ## Consequences - The JS DOM stub must model `classList` and element lookup. A stub too thin to express a failure is how the failure survives (CB-EV-0014 §2), and today's stub cannot express any of this. - The vocabulary grep is now explicitly the *weak* control, kept for cost. If it ever fails while property 1 passes, prefer property 1 and say so. - If a later pass needs the script to make a decision Rust cannot pre-empt — an animation that depends on rules, say — Decision 1 forbids it and this ADR is what to revisit.