a73x

1b6635a1

test: the page's badge vocabulary and the attach it gates

a73x   2026-08-26 12:13

Commit message
test: the page's badge vocabulary and the attach it gates

Every rule in the terminal-status region was found by driving Chrome by
hand and none of it was pinned: deleting sendAttach's `exited` gate,
emptying TERMINAL, or renaming the refusal badge all left verify.js at
781 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

web/verify.js
Old New
@@ -2552,6 +2552,76 @@ async function verifySelectionShell(shell, html) {
2552 check('failed destructive reset also clears orphaned scroll request', failedResetPaint.tile.scrollRequest, null); 2552 check('failed destructive reset also clears orphaned scroll request', failedResetPaint.tile.scrollRequest, null);
2553 } 2553 }
2554 2554
2555 // The terminal-status region: which badge stops a tile re-attaching, and
2556 // the two words the hub and the page agree on. Nothing on the wire can
2557 // see any of it — wsclient attaches 0x0 and renders no badge — so this
2558 // shell is the gate, and a real browser is the gate for pixels and keys.
2559 async function verifyStatusShell(shell) {
2560 const h = browserShell(shell);
2561 const wall = h.document.createElement('div');
2562
2563 // ENV_FRAME/ENV_CONTROL and the exit_status opcode, spelled as the
2564 // browser receives them: mux.js reads these bytes off a WebSocket, so a
2565 // fixture that called its constants instead would pin nothing.
2566 const exitStatus = (code) => Uint8Array.from([0x00, 0x82, 1, 0, 0, 0, code]);
2567 const control = (state) =>
2568 Uint8Array.from([0x01, ...new TextEncoder().encode(JSON.stringify({ state }))]);
2569
2570 const makeTile = () => {
2571 const tile = new h.Tile(9, 'status fixture', wall, '');
2572 tile.core = {
2573 memory: { buffer: new ArrayBuffer(64) },
2574 mux_attach_payload: () => 20,
2575 mux_output_ptr: () => 0,
2576 mux_output_len: () => 20,
2577 };
2578 const sent = [];
2579 tile.sendFrame = (type) => { sent.push(type); return true; };
2580 tile.reflow = () => {};
2581 return { tile, sent };
2582 };
2583
2584 // --- the badge vocabulary, which webhub.zig's restore rule reads back ---
2585
2586 const refusal = makeTile();
2587 refusal.tile.onMessage(exitStatus(0));
2588 check('a refusal before any grid badges refused', refusal.tile.status, 'refused');
2589 check('the refusal badge names no reason the daemon never gave', refusal.tile.statusText, 'refused');
2590
2591 const ending = makeTile();
2592 ending.tile.gotState = true;
2593 ending.tile.onMessage(exitStatus(3));
2594 check('an exit after a grid badges exited', ending.tile.status, 'exited');
2595 check('the exit badge carries the code', ending.tile.statusText, 'exited 3');
2596
2597 // --- what each badge does to the next attach ---
2598
2599 const exited = makeTile();
2600 exited.tile.gotState = true;
2601 exited.tile.onMessage(exitStatus(0));
2602 exited.tile.zoomed = true; // a sized attach CREATES: the dangerous one
2603 exited.tile.sendAttach(false);
2604 check('a session the user ended is not re-attached', exited.sent.length, 0);
2605
2606 // The restore heal IS a refusal followed by a re-attach: the hub births
2607 // the session and re-dials, and this attach is what lands on it. Gate
2608 // the whole TERMINAL set and the browser wall stays dead after a daemon
2609 // restart.
2610 const refused = makeTile();
2611 refused.tile.onMessage(exitStatus(0));
2612 refused.tile.sendAttach(false);
2613 check('a refused tile keeps re-attaching', refused.sent.length, 1);
2614
2615 // --- the precedence the gate rests on ---
2616
2617 const narrated = makeTile();
2618 narrated.tile.gotState = true;
2619 narrated.tile.onMessage(exitStatus(0));
2620 narrated.tile.onMessage(control('up'));
2621 check('link news cannot clear exited', narrated.tile.status, 'exited');
2622 check('and the up it rode in on sends no attach', narrated.sent.length, 0);
2623 }
2624
2555 // --- wire builders (layouts golden-pinned in protocol.zig) --- 2625 // --- wire builders (layouts golden-pinned in protocol.zig) ---
2556 function snapshotPayload({ seq, history, cols, rows, epoch }, state) { 2626 function snapshotPayload({ seq, history, cols, rows, epoch }, state) {
2557 const stateBytes = Buffer.from(state, 'utf8'); 2627 const stateBytes = Buffer.from(state, 'utf8');
@@ -3249,6 +3319,7 @@ async function main() {
3249 await verifyClipboardShell(shell, html); 3319 await verifyClipboardShell(shell, html);
3250 await verifySelectionShell(shell, html); 3320 await verifySelectionShell(shell, html);
3251 await verifySettingsShell(shell, html); 3321 await verifySettingsShell(shell, html);
3322 await verifyStatusShell(shell);
3252 3323
3253 // Mutation fixture for the source checks below. A correct-looking route 3324 // Mutation fixture for the source checks below. A correct-looking route
3254 // in either kind of comment must be invisible, while live literal 3325 // in either kind of comment must be invisible, while live literal