122 lines
5.4 KiB
Markdown
122 lines
5.4 KiB
Markdown
|
|
# 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.
|