agents agents/A-shell-wiring.md
Owns: RRABBIT/m2/, RRABBIT/test/, RRABBIT/docs/ Must not touch: anything outside RRABBIT/. Root *.md belongs to lane F; the site to lane E.

Lane A — wire publishing into the shell

Owns: RRABBIT/m2/, RRABBIT/test/, RRABBIT/docs/ Must not touch: anything outside RRABBIT/. Root *.md belongs to lane F; the site to lane E.

## ⚠️ CHECK FOR A COLLISION BEFORE YOU START m2/ was being actively worked by another session on 2026-08-15 (tracks, reel, replay). Run git -C RRABBIT log --oneline -3 and git -C RRABBIT status --short. - Newest commit is `a4ea112` and the tree is clean → the lane is free. - Anything newer, or modified files in m2/ → another session is live. Stop and ask Travis. Two agents in m2/ is not a merge conflict, it is one overwriting the other.

Read first

  1. RRABBIT/docs/TRACKS_HANDOFF.md — the authoritative rung table. Written by the session that

built tracks/reel/replay.

  1. RRABBIT/docs/RUNBOOK.md — the port map and the probes that settle arguments.

  2. PUBLISH_PATH.md §0a — what was built on 2026-08-15 and what deliberately was not.

  3. RRABBIT/m2/publish.js and m2/trackstore.js — both pure, both tested, neither wired in.

What is already true

  • DRIVE, TRACK and REPLAY are `live_local`. Three rungs stand.

  • `publish.js` seals a track into a content-addressed bundle. *The address is the hash of the

bundle and the guard is inside the bundle*, so a tampered track fails either on id mismatch or on a derived-vs-stated comparison. 33 tests.

  • `trackstore.js` puts and fetches bundles, and never trusts the store — every read

re-derives the hash. Two adapters (memoryAdapter, httpAdapter), logic in neither. 34 tests.

  • The machine-A-publishes / machine-B-refuses demo exists as a test (test/trackstore.mjs §5).

  • Whole suite: 507 tests, 0 failures, eight files.

What is missing — this lane's work

1. Wire publishing into the reel

There is no publish verb, no button, nothing in the UI. The reel (gear R) is where a track is managed, so it is where "publish this one" belongs. Seal → show the id → offer the bytes.

Design constraint: the shell must keep working with no network. Publishing is an action a user takes, never something that happens on a timer or on save.

2. Connect a fetched track to ops.replay

check() answers may this run here. Handing the steps to the seam is the remaining piece, and it is small on purpose — ops.replay is already a for loop over apply, deliberately not a second code path. Do not add a second one here. A fetched track's steps go through the same apply every local replay uses, or the §9 claim in OP_VOCABULARY_DRAFT.md is false.

Order is fixed: store.get (verifies) → check (roads, before anything moves) → ops.replay.

3. Fix the silent-persistence defect

Measured 2026-08-15: tracks.js permits ~212.9 MB (999 tracks × REC_MAX 4096), the real localStorage quota is 5,187,584 bytes (4.95 MB) — a 43× gap, so only ~23 full tracks fit. save() wraps setItem in try {} catch {}, right for private-browsing mode and wrong for `QuotaExceededError`, which it swallows too. The shell keeps running, stops persisting, and loses the drive on the next reload with nothing said.

workspaces.js and layout.js have the same bare-catch pattern — check them too.

Direction (not a ruling): probe once at load with a tiny key to tell private-mode from quota; surface the quota case via #status, which already un-hides itself when written; and consider whether the caps should be bounded by the real store rather than by numbers 43× above it.

4.95 MB is one Chrome. The guest runs a Firefox kiosk, which was never measured. Measure it before hard-coding a threshold.

Done means

  • node test/publish.mjs && node test/trackstore.mjs && node test/ops.mjs green, plus the rest of

test/.

  • A track published from the reel and replayed on a second profile or machine — and a refusal

demonstrated when a road is missing. The refusal is the interesting half.

  • Quota exhaustion produces a visible message, proven by filling it rather than by reading code.

Traps this tree has already paid for

  • `REC_MAX`/`MAX` do not protect you — they are 43× larger than the store.

  • Measure the EFFECT before the INPUT. A control that "does not respond" cost four rounds when

#transport { display: flex } from an id selector beat [hidden]. It was told to hide 55 times.

  • A stopped instrument reads as a measurement. A frozen counter looked like evidence about

handlers. Put a heartbeat on anything you intend to trust.

  • The dev host may not run the 3D shell — other sessions hold the WebGL context pool. The

FreeBSD guest is the test environment; the deploy loop is in TRACKS_HANDOFF.md §1.

  • `agent-browser click` takes a selector, not coordinates. click 1120 737 matches nothing,

exits 0, and dispatches no event. A coordinate click is mouse move → mouse down → mouse up, and you should arm a capture listener to prove the event arrived.

Reserved for Travis

  • studbook §10.5 — the `track-` prefix. It is provisional. Anything published before the ruling

may need re-sealing under a different id. Wire it; do not publish anything you would miss.

  • Whether the reel is the right home for publish, or whether it wants its own scene.

Open in the interactive atlas