diff --git a/Cargo.lock b/Cargo.lock
index 305976a..86e13e2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -93,7 +93,6 @@ version = "0.1.0"
dependencies = [
"cb-kernel",
"games-ground",
- "quick-js",
"serde_json",
]
@@ -105,16 +104,6 @@ dependencies = [
"games-ground",
]
-[[package]]
-name = "cc"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
-dependencies = [
- "find-msvc-tools",
- "shlex",
-]
-
[[package]]
name = "cfg-if"
version = "1.0.4"
@@ -173,15 +162,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
-[[package]]
-name = "copy_dir"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3"
-dependencies = [
- "walkdir",
-]
-
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -263,12 +243,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
-[[package]]
-name = "find-msvc-tools"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
-
[[package]]
name = "games-ground"
version = "0.1.0"
@@ -357,16 +331,6 @@ version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
-[[package]]
-name = "libquickjs-sys"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f0b24e9bd171b75ae0295bd428fb8fe58410fb23156e5f34a4657a70c3cee96"
-dependencies = [
- "cc",
- "copy_dir",
-]
-
[[package]]
name = "memchr"
version = "2.8.3"
@@ -412,16 +376,6 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "quick-js"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19cb4cefcb00f4ab9b332664d06005a74f582ac16aa959c6ad5912957bd83e5f"
-dependencies = [
- "libquickjs-sys",
- "once_cell",
-]
-
[[package]]
name = "quote"
version = "1.0.47"
@@ -558,12 +512,6 @@ dependencies = [
"digest",
]
-[[package]]
-name = "shlex"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
-
[[package]]
name = "syn"
version = "2.0.119"
diff --git a/INTENT.md b/INTENT.md
index 8fb0fbd..ebf9939 100644
--- a/INTENT.md
+++ b/INTENT.md
@@ -64,11 +64,6 @@ game.
and scenario tests, simple bots. No rendering, no physics.
1. **Inspectable 2D table** — card/token/hand/relationship-graph
visualization, drag-to-propose, debug inspector, hot-seat play.
- *Open on one human verification (CB-EV-0012 §4): the inspector,
- drag-to-propose and hot-seat play are evidenced by executing code;
- the visualization is evidenced only as correctly emitted, because no
- browser is available to the loop. Run `cb-play --serve 0`, open the
- printed URL, and confirm the table reads and a drag works.*
2. **Physical 3D tabletop** — wgpu renderer, Rapier-backed physics, camera
and pointer controls, snap zones, asset importer.
3. **Networked sessions** — authoritative host, private projections,
diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md
index adca1ae..ff6ed48 100644
--- a/WORK-RECORDS.md
+++ b/WORK-RECORDS.md
@@ -100,6 +100,3 @@
| task | CB-WP-0013-T02 | done | — | workplans/CB-WP-0013-instrument-corrections.md |
| task | CB-WP-0013-T03 | done | — | workplans/CB-WP-0013-instrument-corrections.md |
| task | CB-WP-0013-T04 | done | — | workplans/CB-WP-0013-instrument-corrections.md |
-| task | CB-WP-0014-T01 | done | — | workplans/CB-WP-0014-execute-the-javascript.md |
-| task | CB-WP-0014-T02 | done | — | workplans/CB-WP-0014-execute-the-javascript.md |
-| task | CB-WP-0014-T03 | done | — | workplans/CB-WP-0014-execute-the-javascript.md |
diff --git a/crates/cb-render-html/Cargo.toml b/crates/cb-render-html/Cargo.toml
index 204baaa..4435a33 100644
--- a/crates/cb-render-html/Cargo.toml
+++ b/crates/cb-render-html/Cargo.toml
@@ -4,28 +4,16 @@ edition.workspace = true
version.workspace = true
license-file.workspace = true
-[features]
-# ADR-0009: the JS harness is exposed to cb-play's tests behind a feature,
-# so the engine stays a dev cost there too and never a shipped one.
-js-harness = ["dep:quick-js"]
-
-# ADR-0007 Decision 1: the browser is the renderer, so the rendering path
-# has **no third-party dependencies at all** beyond what the game already
+# ADR-0007 Decision 1: the browser is the renderer, so this crate has
+# **no third-party dependencies at all** beyond what the game already
# carries. Adding one here needs an argument against ADR-0007 §Decision 3.
[dependencies]
cb-kernel.workspace = true
games-ground.workspace = true
-# ADR-0009: an EMBEDDED engine, not `node` — CI runs on rust:1.97, which
-# has no node, so requiring one would make our build acquire a runtime
-# nobody audits while it scored zero on the only instrument that governs
-# dependencies. Optional, and never in the shipped-runtime configuration:
-# AM-4a measures 157,202 against 161,000 and has no room for it.
-quick-js = { version = "0.4", optional = true }
+
+[lints]
+workspace = true
[dev-dependencies]
# The coverage gate walks the serialized view; nothing else needs it.
serde_json.workspace = true
-quick-js = "0.4"
-
-[lints]
-workspace = true
diff --git a/crates/cb-render-html/src/jsrun.rs b/crates/cb-render-html/src/jsrun.rs
deleted file mode 100644
index 484462b..0000000
--- a/crates/cb-render-html/src/jsrun.rs
+++ /dev/null
@@ -1,290 +0,0 @@
-//! Execute the emitted JavaScript for real (ADR-0009).
-//!
-//! ADR-0007 control 5 says the page **may not construct commands**: it
-//! reports raw pointer facts and Rust decides what they mean. Until now
-//! that contract was held up by a test that greps the emitted script for
-//! game vocabulary — and grepping for the absence of words is a weak
-//! proxy for *"this code cannot construct a command"*.
-//!
-//! This runs it. QuickJS, a DOM stub with exactly the surface `SCRIPT`
-//! touches, and a Rust callback standing in for `fetch` so the test can
-//! see precisely what would have gone on the wire.
-//!
-//! **The scripts are lifted from a real emitted document, not pasted
-//! here.** Both `` body out of a document, in order.
-pub fn scripts(html: &str) -> Vec {
- let mut out = Vec::new();
- let mut rest = html;
- while let Some(i) = rest.find("") {
- Some(j) => {
- out.push(after[..j].to_string());
- rest = &after[j..];
- }
- None => break,
- }
- }
- out
-}
-
-/// The DOM surface `SCRIPT` touches, and nothing more.
-///
-/// Deliberately minimal: every additional stubbed API is a way for the
-/// script to do something in the test that it could not do in a browser,
-/// or vice versa. If `SCRIPT` ever needs more than this, that is a signal
-/// about the script, not about the stub.
-const DOM: &str = r#"
-var __handlers = {};
-var __status = { textContent: "" };
-var document = {
- addEventListener: function (type, fn) { __handlers[type] = fn; },
- getElementById: function (id) { return __status; }
-};
-var window = { location: { reload: function () { __reloaded(); } } };
-function fetch(url, opts) {
- __post(url, (opts && opts.body) || "");
- // The script chains .then(...).then(...); give it something to chain on
- // without ever resolving, so response handling stays out of scope here.
- var chainable = { then: function () { return chainable; } };
- return chainable;
-}
-function __down(id) { __handlers['pointerdown']({ target: { id: id } }); }
-function __up(id) { __handlers['pointerup']({ target: { id: id } }); }
-"#;
-
-/// Run the document's scripts, then a pointer gesture, and report what
-/// the page tried to send.
-///
-/// `gesture` is `(down_id, up_id)`. Errors are JS errors, and they are
-/// returned rather than swallowed: a script that throws must not look
-/// like a script that sent nothing.
-pub fn gesture(html: &str, down: &str, up: &str) -> Result, String> {
- let ctx = quick_js::Context::new().map_err(|e| format!("quickjs init: {e}"))?;
-
- // `Arc>` rather than `Rc>`: quick-js requires the
- // callback to be unwind-safe, since a panic inside it would cross the
- // C boundary.
- let posted: Arc>> = Arc::new(Mutex::new(Vec::new()));
- let sink = posted.clone();
- ctx.add_callback("__post", move |url: String, body: String| {
- sink.lock().expect("posted").push(Posted { url, body });
- 0i32
- })
- .map_err(|e| format!("register __post: {e}"))?;
- ctx.add_callback("__reloaded", || 0i32)
- .map_err(|e| format!("register __reloaded: {e}"))?;
-
- ctx.eval(DOM).map_err(|e| format!("dom stub: {e}"))?;
-
- let found = scripts(html);
- if found.is_empty() {
- return Err("the document carries no ", "a", "b").unwrap_err();
- assert!(e.contains("no pointer handlers"), "{e}");
- }
-
- #[test]
- fn both_script_blocks_are_extracted_in_order() {
- let found = scripts(&page());
- assert_eq!(
- found.len(),
- 2,
- "the page carries the endpoint and the script"
- );
- assert!(found[0].contains("CB_ENDPOINT"), "{}", found[0]);
- assert!(found[1].contains("pointerdown"), "{}", found[1]);
- }
-}
diff --git a/crates/cb-render-html/src/lib.rs b/crates/cb-render-html/src/lib.rs
index c39b300..d910f7e 100644
--- a/crates/cb-render-html/src/lib.rs
+++ b/crates/cb-render-html/src/lib.rs
@@ -43,8 +43,6 @@
pub mod doc;
pub mod input;
-#[cfg(any(test, feature = "js-harness"))]
-pub mod jsrun;
pub mod serve;
pub use doc::{document, text_of};
diff --git a/decisions/ADR-0009-embed-the-js-engine.md b/decisions/ADR-0009-embed-the-js-engine.md
deleted file mode 100644
index f41f32b..0000000
--- a/decisions/ADR-0009-embed-the-js-engine.md
+++ /dev/null
@@ -1,121 +0,0 @@
-# ADR-0009: embed the JS engine; do not put `node` in the toolchain
-
-status: accepted
-date: 2026-08-02
-decided by: agent, under the standing loop authorization
-tier: M (structural M — adds an external dependency to the toolchain,
-InnerLoop v1.6; chaos d4=2 → no override). Tier M merges survey and
-decision into one document, which this is.
-references: [CB-WP-0014](../workplans/CB-WP-0014-execute-the-javascript.md),
-[ADR-0007](ADR-0007-render-html-not-a-port.md) D3 (the acquisition rule)
-and D5 (control 5),
-[ADR-0008](ADR-0008-instrument-corrections.md) D2 (AM-4b left uncorrected),
-[CB-EV-0010](../evidence/CB-EV-0010-render-port.md) §4
-
-## Context
-
-ADR-0007 control 5 says the emitted page **may not construct commands** —
-it reports raw pointer facts and Rust decides what they mean. That
-contract is currently held up by a test that greps the emitted script for
-game vocabulary. **Grepping for the absence of words is a weak proxy for
-"this code cannot construct a command."**
-
-And the script has never been executed at all, which is the sole remaining
-reason INTENT stage 1 is open.
-
-## The survey
-
-`node` v24.11.1 is on this machine, so the obvious move is to shell out to
-it. Measured alternatives, same method as `dep-weight.py`, marginal against
-the 29-crate dev-toolchain graph, under the positive control:
-
-| option | marginal Rust lines | notes |
-|---|---:|---|
-| `boa_engine` | 896,410 | 27× AM-4b's headroom |
-| `rquickjs` | 69,985 | 2.1× headroom |
-| **`quick-js`** | **11,434** | bindings + vendored QuickJS C |
-| `node`, shelled out | **0** | and that zero is the problem |
-
-AM-4b headroom is **32,979** (317,021 of 350,000), so `quick-js` fits at
-35% of it and `rquickjs` does not.
-
-## Decision — embed `quick-js`; `node` is refused
-
-**This is ADR-0007 Decision 3's acquisition rule biting its author, which
-is the only real test of whether it was written honestly.** The rule:
-
-> AM-4 counts third-party code the project causes to be **acquired**. It
-> does not count runtimes the user already has independently of us. It
-> **does** count a library our build or install instructions cause to be
-> fetched, pinned, or linked, whether or not its source is Rust.
-
-A browser is not counted because a developer has one regardless of us. But
-**CI runs on `rust:1.97`, which has no `node`** — so adding this test
-would make our CI fetch a JavaScript runtime. That is our build causing an
-acquisition, of tens of millions of lines nobody here will audit, scoring
-**zero** on the only instrument that governs dependencies.
-
-Taking `node` would mean using the rule to exempt a browser we do not
-install while also exempting a runtime we do. `quick-js` costs 11,434
-lines that are vendored, pinned, auditable, and counted.
-
-The secondary reasons matter less but all point the same way: the test
-runs anywhere `cargo test` runs, needs no CI change, cannot skip because a
-binary is missing, and pins one engine version rather than whatever the
-image happens to ship.
-
-**`python3` is not a precedent for `node`.** It is already a toolchain
-dependency, and the honest reading is that it was never argued — it
-predates the acquisition rule. Leaning on it would be using an unexamined
-decision to license a second one. It is left alone here and noted as
-owed.
-
-### What is bought, and what is not
-
-Executing the script proves the **input contract**: what the page puts on
-the wire in response to a pointer gesture. It does **not** prove the SVG
-renders legibly, that a drag feels like a drag, or that anyone can play a
-game. QuickJS has no layout engine and this ADR claims no rendering
-evidence.
-
-### Cost accepted — and a correction, measured after the fact
-
-The paragraph originally here read *"35% of AM-4b's remaining headroom,
-for a test."* **That was wrong, and finding out how wrong is the more
-important result of this pass.**
-
-After landing, `make dep-weight` reported AM-4b **unchanged at 317,021**.
-`quick-js` is a dev-dependency of `cb-render-html`, and AM-4b measures
-`cargo tree -p games-ground --edges normal` — one package, no dev edges.
-It cannot see it. Measured:
-
-| | crates | lines |
-|---|---:|---:|
-| AM-4b as instrumented (`games-ground`, normal) | 29 | 317,021 |
-| the whole workspace, including dev edges | 57 | 725,258 |
-| **uncounted by AM-4b** | **28** | **408,237** |
-
-**The dev-toolchain budget is blind to more source than its entire
-target** — `criterion`, `clap`, `ciborium`, and now `quick-js`.
-
-This is the same defect this ADR refuses `node` for: a real acquisition
-scoring zero because the instrument does not look there. The difference is
-that `quick-js` is *auditable and pinned* and `node` is neither, so the
-decision stands. But it stands on the acquisition rule, **not** on an
-affordability argument, because there is no affordability argument to be
-had until AM-4b can see what it is buying.
-
-Recorded as owed, and it is now the third defect in the AM-4 family: the
-shipped-runtime proc-macro count (fixed, ADR-0008 D2), AM-4b's own
-proc-macro share (owed), and AM-4b's scope (this).
-
-## Consequences
-
-- `quick-js` is a **dev-dependency of `cb-render-html` only**. It must
- never reach the shipped-runtime configuration; AM-4a would catch that,
- and now measures 157,202 against 161,000 with no room for it.
-- The grep test stays. It is cheap, and it fails faster and more legibly
- than an execution test when someone adds a word to the script.
-- If `quick-js` becomes unmaintained, the fallback is not `node` — it is
- `rquickjs` plus an AM-4b decision, or dropping the execution test and
- saying so.
diff --git a/evidence/CB-EV-0012-execute-the-javascript.md b/evidence/CB-EV-0012-execute-the-javascript.md
deleted file mode 100644
index c180a64..0000000
--- a/evidence/CB-EV-0012-execute-the-javascript.md
+++ /dev/null
@@ -1,150 +0,0 @@
-# CB-EV-0012 — the loop is closed, and the budget is blind
-
-CB-WP-0014 T03. Measured 2026-08-02 at `55212d7`+. Pass kind `product`,
-tier **M** (chaos d4=2, no override). Declaration 9 of 12.
-
-**Applying CB-EV-0011 §4's proposed rule, and hereby adopting it:** the
-cost table below quotes the **previous** pass's final figure and marks
-this pass's own as provisional. See §5.
-
----
-
-## 1. What was actually blocking stage 1, and what it cost to find out
-
-CB-EV-0010 §4 said the emitted JavaScript had never run. That sentence was
-carried for a pass on an assumption nobody checked. **`node` v24.11.1 was
-on the machine the whole time.**
-
-That is the fourth consecutive pass whose central finding was a claim
-carried because nobody ran the command that settles it — after the 3,750
-headroom figure, the "empty" sub-100k dependency region, and SH-3's 0.0%.
-The pattern is stable enough now to name: **this project's characteristic
-error is not faulty reasoning, it is unexecuted verification.** Every one
-was cheap to check and none had been checked.
-
-## 2. The loop, closed
-
-The real server serves the real page → QuickJS runs *that page's own two
-`