master
Colby Knox verb battery: stage the set_file_input sample under Downloads; nb now refuses uploads from hidden home folders such as… fe04783 5d ago
#!/usr/bin/env node
// nb verb battery — the DAILY end-to-end test of every nbrowser_* verb, run exactly the way users
// run them: through the `adom-bridge` CLI over the relay to a real desktop, against the real
// signed-in browser, using the fixture page + sample files in demo/. Run:
//
//   node tools/verb-battery.mjs --target AdomLapper --profile chrome:[email protected]
//
// EVERY verb in VERB_META must appear in COVERAGE below as one of:
//   live:<fn>       — executed against the desktop, strict assertions
//   guard:<fn>      — executed expecting a SPECIFIC refusal (guards are the product; a guard that
//                     stops refusing is a worse regression than a feature that stops working)
//   skip:"reason"   — consciously not auto-tested (destructive / human-gated), reason printed
// tools/check-guard-coverage.mjs fails the build if a verb is missing from this map.
import { execFileSync } from 'node:child_process';
import { readFileSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const root = join(dirname(fileURLToPath(import.meta.url)), '..');
const argv = Object.fromEntries(process.argv.slice(2).map((a, i, arr) => a.startsWith('--') ? [a.slice(2), arr[i + 1]] : []).filter(Boolean));
const TARGET = argv.target || 'AdomLapper';
// PROFILE: --profile wins; otherwise DISCOVER from the box itself. A hardcoded laptop default made a
// ConfRoomROG run fail 30/84 with one root cause (unknown profile → no drivable session → cascade,
// 2026-08-23). Prefer a live profile; else the first asleep-but-installed chrome, else edge.
let PROFILE = argv.profile || null;
const THREAD = 'nb verb battery';
const SID = 'nbbattery';
// The target machine's own ~/.adom/native-browser, read from the bridge. It was hard-coded to John's user
// folder, which made set_file_input fail on every other machine (found on winvm, 2026-09-20).
let NB_DIR = 'C:/Users/Public';
function resolveNbDir() { try { const r = call('nbrowser_settings', {}); const f = String(r.file || ''); if (f) NB_DIR = f.replace(/\\/g, '/').replace(/\/settings\.json$/i, ''); } catch {} return NB_DIR; }
const HARNESS = 'https://wiki.adom.inc/blob/app/nb-bridge/demo/test-harness.html';
const CB = Date.now();

let TARGET_LOST = null;
function call(verb, args = {}, timeoutMs = 90000) {
  // A daily test that reports 27 fake failures because the laptop slept is worse than no test. The
  // moment the relay says the target is gone, stop grading: every later assertion would be noise.
  if (TARGET_LOST) return { _targetLost: true, error: TARGET_LOST };
  try {
    const out = execFileSync('adom-bridge', ['--target', TARGET, '--ai-thread', THREAD, verb, JSON.stringify(args)], { encoding: 'utf8', timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024 });
    let d; try { d = JSON.parse(out); } catch { return { _raw: out, _parse: 'outer' }; }
    if (typeof d.output === 'string' && d.output.trim().startsWith('{')) { try { d = { ...d, ...JSON.parse(d.output) }; } catch {} }
    if (d && (d.errorCode === 'target_not_found' || /No connected Adom Bridge matches/i.test(JSON.stringify(d)))) TARGET_LOST = `target '${TARGET}' left the relay mid-run`;
    return d;
  } catch (e) {
    const out = String(e.stdout || '');
    // The relay-gone response exits NON-ZERO, so it lands HERE - and this branch didn't run the
    // TARGET_LOST check, so a mid-run relay drop graded 15 fake FAILs instead of aborting (2026-08-22).
    if (/No connected Adom Bridge matches|target_not_found/i.test(out + String(e.message))) TARGET_LOST = `target '${TARGET}' left the relay mid-run`;
    try { const d = JSON.parse(out); if (typeof d.output === 'string') { try { return { ...d, ...JSON.parse(d.output) }; } catch {} } return d; } catch {}
    return { _err: String(e.message).slice(0, 200), _raw: out.slice(0, 300) };
  }
}
const evalIn = (expr, extra = {}) => call('nbrowser_eval', { sessionId: SID, expression: expr, ...extra });
const settle = (ms = 450) => call('nbrowser_wait', { sessionId: SID, ms });

const results = []; let ctx = {};
function record(verb, mode, ok, note) {
  results.push({ verb, mode, ok, note: String(note || '').slice(0, 220) });
  console.log(`${ok ? 'PASS' : 'FAIL'}  ${verb.padEnd(30)} [${mode}] ${note || ''}`);
}
const A = (cond, note, bad) => cond ? [true, note] : [false, bad];

// ─── the checks ──────────────────────────────────────────────────────────────
const T = {
  // lifecycle / read-only
  status: () => { const r = call('nbrowser_status'); return A(r.ok === true && r.ext === 'nbe', `ext:${r.ext} v${r.version}`, JSON.stringify(r).slice(0, 160)); },
  readiness: () => { const r = call('nbrowser_readiness'); ctx.ready = r.ready; return A(typeof r.ready === 'boolean' && r.claudeExtension !== undefined, `ready:${r.ready} state:${r.state}`, JSON.stringify(r).slice(0, 160)); },
  ping: () => { const r = call('nbrowser_ping'); return A(r.ok === true && r.pong === true, 'pong', JSON.stringify(r).slice(0, 120)); },
  whoami: () => { const r = call('nbrowser_whoami'); return A(r.ok !== false, `keys:${Object.keys(r).slice(0, 5)}`, JSON.stringify(r).slice(0, 120)); },
  // The DEFAULT target must never be pup's own browser (2026-09-21: an unnamed open landed in a pup window).
  profiles: () => { const r = call('nbrowser_profiles'); const ps = r.profiles || []; ctx.profileCount = ps.length; const def = ps.find((p) => p.profile === r.active); const pups = ps.filter((p) => p.origin === 'pup').length; return A(ps.length > 0 && !(def && def.origin === 'pup'), `${ps.length} profiles, default ${r.active} (origin ${def ? def.origin : 'none'}), ${pups} pup connection(s) set aside`, `default is pup's browser or no profiles: ${JSON.stringify(r).slice(0, 160)}`); },
  resolve_profiles: () => { const r = call('nbrowser_resolve_profiles'); return A(r.ok !== false, 'resolved', JSON.stringify(r).slice(0, 120)); },
  // An Edge-only box is a PASS: nb drives whatever native browser is there (nb-bridge#46).
  // nb-bridge#55. The full export needs a HUMAN at the machine (Windows Hello), so the battery exercises
  // the refusals only: consent and reason. It must never fire a Hello prompt at whoever is sitting there.
  // nb-bridge#55 preflight: read-only, must report whether the dialog is drivable here.
  export_readiness: () => { const r = call('nbrowser_export_readiness'); return A(r.ok === true && typeof r.canDriveDialog === 'boolean' && !!r.browser, `canDriveDialog=${r.canDriveDialog}${(r.agents||[]).length?' (agents: '+r.agents.join(',')+')':''}`, JSON.stringify(r).slice(0,180)); },
  export_saved_logins: () => {
    const noConfirm = call('nbrowser_export_saved_logins', { profile: PROFILE, reason: 'battery: consent gate' });
    const noReason = call('nbrowser_export_saved_logins', { profile: PROFILE, confirm: true });
    const gated = noConfirm.errorCode === 'confirm_required' && noReason.errorCode === 'reason_required';
    const leak = /password/i.test(JSON.stringify(noConfirm.credentials || '')) ;
    return A(gated && !leak, 'refuses without consent and without a reason (full run needs a human for Windows Hello)', JSON.stringify({ a: noConfirm.errorCode, b: noReason.errorCode }));
  },
  // nb-bridge#54. The export must (a) refuse without confirm, (b) keep cookie VALUES out of the reply,
  // (c) write them owner-only on the user's machine, and (d) be droppable. Uses the harness's own domain.
  export_site_session: () => {
    const noConfirm = call('nbrowser_export_site_session', { domain: 'wiki.adom.inc', profile: PROFILE, reason: 'battery: consent gate' });
    const r = call('nbrowser_export_site_session', { domain: 'wiki.adom.inc', confirm: true, profile: PROFILE, reason: 'battery: verify the machine-local handoff' });
    const blob = JSON.stringify(r);
    const leaked = /"value"\s*:/.test(blob) || /_secret/.test(blob);
    const dropped = r.handoffId ? call('nbrowser_drop_handoff', { handoffId: r.handoffId }) : {};
    const gated = noConfirm.ok !== true && noConfirm.errorCode === 'confirm_required';
    const ok = gated && (r.ok === true ? (!leaked && !!r.handoffPath && typeof r.cookieCount === 'number' && dropped.dropped === true)
                                       : r.errorCode === 'no_session');
    return A(ok, r.ok === true ? `consent gated; ${r.cookieCount} cookie(s) handed off locally, no values in the reply, handoff dropped` : 'consent gated; no session for the harness domain (nothing to export)', JSON.stringify({ gated, leaked, handoff: !!r.handoffPath, dropped: dropped.dropped, err: r.errorCode }));
  },
  drop_handoff: () => { const r = call('nbrowser_drop_handoff', { handoffId: 'session-1-000000000000' }); return A(r.ok === true && r.dropped === false, 'a handoff that is not there reports dropped:false (safe outcome)', JSON.stringify(r).slice(0, 160)); },
  // A dead session must never report the USER's tabs (caught 2026-10-21: every dead session showed John's
  // Gmail and alive:true). A session-scoped list refuses; state then says the window is gone.
  dead_session_scoping: () => {
    const sid = SID + '-dead';
    const o = call('nbrowser_open_window', { sessionId: sid, thread: THREAD, purpose: 'battery: dead-session scoping', profile: PROFILE, url: HARNESS });
    if (o.ok !== true) return [false, 'could not open the probe window: ' + JSON.stringify(o).slice(0, 120)];
    call('nbrowser_close_window', { sessionId: sid });
    const t = call('nbrowser_list_tabs', { sessionId: sid });
    const st = call('nbrowser_state', { sessionId: sid, all: true });
    const row = (st.sessions || []).find((x) => x.sessionId === sid) || {};
    const refused = t.ok !== true && t.errorCode === 'session_window_gone';
    const honest = !row.url || row.alive === false;
    return A(refused && honest, `list_tabs refused (${t.errorCode}) and state reports the window gone`, JSON.stringify({ errorCode: t.errorCode, url: row.url, alive: row.alive }).slice(0, 180));
  },
  // nb-bridge#51 item 8: a CDP refusal must arrive as a structured errorCode + hint, never Chrome's raw text.
  eval_on_browser_page: () => {
    const sid = SID + '-cdpblock';
    const o = call('nbrowser_open_window', { sessionId: sid, thread: THREAD, purpose: 'battery: a page CDP cannot attach to', profile: PROFILE, url: 'https://chrome.google.com/webstore/category/extensions' });
    const r = o.ok === true ? call('nbrowser_eval', { sessionId: sid, expression: '1+1' }) : {};
    if (o.ok === true) call('nbrowser_close_window', { sessionId: sid });
    // Either it attached (some builds allow it) or it refused WITH a code - never a bare Chrome string.
    // A refusal has no `ok` field at all (success:false + errorCode), so do not test for ok === false.
    const refusedWell = r.ok !== true && !!r.errorCode && !!(r._hint || r.error);
    return A(r.ok === true || refusedWell, r.ok === true ? 'page was attachable; nothing to translate' : `refused as ${r.errorCode} with a hint`, JSON.stringify({ ok: r.ok, errorCode: r.errorCode, err: String(r.error || '').slice(0, 90) }));
  },
  // nb-bridge#53: with several signed-in identities connected, an unnamed open is REFUSED rather than
  // acting as whichever profile happens to be active (an agent nearly posted as the wrong person).
  guard_profile_required: () => {
    const live = (call('nbrowser_profiles').profiles || []).filter((p) => p.live && p.origin !== 'pup');
    const fresh = `${SID}-noprof-${Date.now()}`;   // never pinned: a pin would make the guard skip by design
    const r = call('nbrowser_open_window', { sessionId: fresh, thread: THREAD, purpose: 'battery: unnamed open must be refused when several identities are connected', url: HARNESS });
    if (live.length < 2) { if (r.ok === true) call('nbrowser_close_window', { sessionId: fresh }); return A(true, `[skip] only ${live.length} identity connected - defaulting is unambiguous, guard not applicable`, ''); }
    if (r.ok === true) { call('nbrowser_close_window', { sessionId: fresh }); return [false, 'unnamed open was ALLOWED with several identities connected']; }
    return A(r.errorCode === 'profile_required' && Array.isArray(r.profiles) && r.profiles.length >= 2, `refused: ${r.errorCode}, listed ${(r.profiles || []).length} identities`, `errorCode:${r.errorCode} listed:${(r.profiles || []).length}`);
  },
  // nb-bridge#52: a window can be opened OUT OF SIGHT in one step and stays drivable there.
  open_minimized: () => {
    const sid = SID + '-min';
    const o = call('nbrowser_open_window', { sessionId: sid, thread: THREAD, purpose: 'battery: minimized open', profile: PROFILE, url: HARNESS, state: 'minimized' });
    // nbrowser_window_state is a SETTER (no args = fullscreen), so read the state from the window list.
    const lw = o.ok === true ? call('nbrowser_list_windows', { profile: PROFILE }) : {};
    const st = { state: ((lw.sessions || lw.windows || []).find((w) => String(w.windowId) === String(o.windowId)) || {}).state };
    const ev = o.ok === true ? call('nbrowser_eval', { sessionId: sid, expression: '1+1' }) : {};
    if (o.ok === true) call('nbrowser_close_window', { sessionId: sid });
    return A(o.ok === true && String(st.state || '').toLowerCase() === 'minimized' && (ev.result === 2 || ev.ok === true), `opened minimized (state:${st.state}) and still drivable`, JSON.stringify({ o: String(o.error || o.ok), st: st.state, ev: ev.result }).slice(0, 200));
  },
  // The nbe tally (nb-bridge#50): browsers, profiles, where nbe is. Must be fresh: compare against readiness's live copy.
  prewarm: () => { const r = call('nbrowser_prewarm'); const rd = call('nbrowser_readiness'); const t = r.tally || {}; const live = (rd.nbe && rd.nbe.tally) || {}; const fresh = Date.now() - Date.parse(r.asOf || 0) < 60000; const same = JSON.stringify(t) === JSON.stringify(live); return A(r.ok === true && t.profiles > 0 && fresh && same && Array.isArray(r.installedIn), `${r.summary}; readiness agrees`, `tally ${JSON.stringify(t)} vs readiness ${JSON.stringify(live)} fresh:${fresh}`); },
  chrome_status: () => { const r = call('nbrowser_chrome_status'); const ok = r.installed === true || (r.nativeBrowserPresent === true && r.chromeNeeded === false && r.edgeInstalled === true); return A(ok, r.installed ? `chrome installed, phase:${r.phase}` : 'edge-only box: chromeNeeded:false, Chrome reported optional', JSON.stringify(r).slice(0, 140)); },
  describe: () => { const r = call('nbrowser_describe'); const n = (r.verbs || []).length; const meta = metaCount(); return A(n === meta, `describe:${n} == VERB_META:${meta}`, `describe:${n} != VERB_META:${meta} — regenerate!`); },
  scroll: () => { const r = call('nbrowser_scroll', { sessionId: SID, x: 200, y: 200, deltaY: 300 }); return A(r.ok === true && r.dispatched === 'wheel' && r.scrollAfter && typeof r.pageScrolled === 'boolean', `wheel dispatched; pageScrolled=${r.pageScrolled}`, JSON.stringify(r).slice(0, 160)); },
  input_unknown_type: () => { const r = call('nbrowser_input_dispatch', { sessionId: SID, type: 'zoom', x: 200, y: 200, deltaY: -300 }); return A(r.ok === false && r.errorCode === 'unknown_input_type' && r.dispatched === false, 'unknown type refused, nothing dispatched', JSON.stringify(r).slice(0, 160)); },
  hover_raw_xy: () => { const r = call('nbrowser_hover', { sessionId: SID, x: 200, y: 200, hoverDwellMs: 100 }); return A(r.ok === true && r.dispatched === 'hover', 'raw xy hover accepted without atScrollX/Y', JSON.stringify(r).slice(0, 160)); },
  progress: () => { const r = call('nbrowser_progress'); return A(r.ok === true && Array.isArray(r.active) && r.verbDurations && typeof r.verbDurations === 'object', `active[]=${(r.active || []).length}, ${Object.keys(r.verbDurations || {}).length} measured verbs`, JSON.stringify(r).slice(0, 160)); },
  activity: () => { const r = call('nbrowser_activity', { limit: 5 }); return A(Array.isArray(r.entries), `${(r.entries || []).length} entries`, JSON.stringify(r).slice(0, 120)); },
  os_windows: () => { const r = call('nbrowser_os_windows'); return A(r.ok === true && typeof r.count === 'number', `${r.count} windows`, JSON.stringify(r).slice(0, 140)); },
  wake_profile: () => { let r = call('nbrowser_wake_profile', { profile: PROFILE }); if (r.ok !== true) r = call('nbrowser_wake_profile', { profile: PROFILE }); return A(r.ok === true, `woke:${r.woke}`, JSON.stringify(r).slice(0, 160)); },
  rescan: () => { const r = call('nbrowser_rescan'); return A(r.ok !== false, 'rescanned', JSON.stringify(r).slice(0, 120)); },
  resync_sessions: () => { const r = call('nbrowser_resync_sessions'); return A(r.ok !== false, 'resynced', JSON.stringify(r).slice(0, 120)); },

  // window lifecycle (the spine — everything after rides ctx.sid)
  open_window: () => {
    const g = call('nbrowser_open_window', { sessionId: SID + '-noreg', url: HARNESS });
    if (g.errorCode !== 'session_unregistered' && !/session_unregistered|thread.*purpose|registration/i.test(JSON.stringify(g))) return [false, 'registration guard MISSING: unregistered open was not refused: ' + JSON.stringify(g).slice(0, 140)];
    const r = call('nbrowser_open_window', { sessionId: SID, thread: THREAD, purpose: 'daily verb battery run', profile: PROFILE, url: `${HARNESS}?cb=${CB}` });
    ctx.hwnd = r.hwnd; ctx.windowId = r.windowId;
    return A(r.ok === true && r.sessionId === SID, `opened hwnd:${r.hwnd} (+ registration guard verified)`, JSON.stringify(r).slice(0, 200));
  },
  pin_session: () => { const r = call('nbrowser_pin_session', { sessionId: SID }); return A(r.ok !== false, 'pinned', JSON.stringify(r).slice(0, 120)); },
  use_profile: () => { const r = call('nbrowser_use_profile', { profile: PROFILE }); return A(r.ok !== false, `active:${PROFILE}`, JSON.stringify(r).slice(0, 120)); },
  list_windows: () => { const r = call('nbrowser_list_windows'); const mine = JSON.stringify(r).includes(SID); return A(mine, 'my session listed', 'battery session missing from list_windows'); },
  window_state: () => { const r = call('nbrowser_window_state', { sessionId: SID, width: 1200, height: 860 }); return A(r.ok !== false, '1200x860', JSON.stringify(r).slice(0, 140)); },
  switch_window: () => { const r = call('nbrowser_switch_window', { sessionId: SID }); return A(r.ok !== false, 'switched to own', JSON.stringify(r).slice(0, 120)); },

  // navigation
  navigate: () => { const r = call('nbrowser_navigate', { sessionId: SID, url: `${HARNESS}?cb=${CB}&nav=2` }); return A(r.ok !== false, 'navigated', JSON.stringify(r).slice(0, 140)); },
  reload: () => { const r = call('nbrowser_reload', { sessionId: SID }); return A(r.ok !== false, 'reloaded', JSON.stringify(r).slice(0, 120)); },
  // back/forward POLL for the expected URL instead of sampling once - the fixed 800ms sample raced a
  // slower box (ROG failed 'back' while a manual check with a settle passed; history commits are async).
  settings: () => {
    // write-then-read-back, literal for literal (the settings-layout doctrine): flip overlayBadge
    // off, read it back, restore the ORIGINAL value, read again.
    const before = call('nbrowser_settings', {});
    const orig = before.settings && before.settings.overlayBadge;
    const w = call('nbrowser_settings', { set: { overlayBadge: false, taskbarActivity: 'flash' } });
    const r = call('nbrowser_settings', {});
    const back = call('nbrowser_settings', { set: { overlayBadge: orig !== false, taskbarActivity: (before.settings || {}).taskbarActivity || 'progress' } });
    const bad = call('nbrowser_settings', { set: { taskbarActivity: 'sideways' } });
    return A(w.ok === true && r.settings && r.settings.overlayBadge === false && r.settings.taskbarActivity === 'flash' && back.settings && back.settings.overlayBadge === (orig !== false) && bad.ok !== true,
      `stored literals read back; invalid enum rejected`, `w:${JSON.stringify(w).slice(0, 100)} r:${JSON.stringify(r.settings)} bad:${JSON.stringify(bad).slice(0, 80)}`);
  },
  back: () => { call('nbrowser_wait', { sessionId: SID, ms: 800 }); call('nbrowser_back', { sessionId: SID }); const u = pollUrl((q) => q.includes('cb=' + CB) && !q.includes('nav=2')); return A(u.ok, 'went back', `url after back: ${u.last}`); },
  forward: () => { call('nbrowser_forward', { sessionId: SID }); const u = pollUrl((q) => q.includes('nav=2')); return A(u.ok, 'went forward', `url after forward: ${u.last}`); },
  wait: () => { const t0 = Date.now(); call('nbrowser_wait', { sessionId: SID, ms: 700 }); return A(Date.now() - t0 >= 650, `waited ${Date.now() - t0}ms`, 'returned too fast'); },
  wait_for: () => { call('nbrowser_click', { sessionId: SID, selector: '#spawn-late' }); const r = call('nbrowser_wait_for', { sessionId: SID, selector: '#late-el', timeoutMs: 6000 }); return A(r.ok === true || r.found === true, 'late element appeared', JSON.stringify(r).slice(0, 140)); },
  keepalive: () => { const r = call('nbrowser_keepalive', { sessionId: SID, stop: true }); return A(r.ok !== false, 'keepalive verb responds', JSON.stringify(r).slice(0, 120)); },

  // reading + JS
  eval: () => { const r = evalIn('document.getElementById("page-title").textContent'); return A(String(r.result).includes('battery harness'), 'read page title', JSON.stringify(r).slice(0, 140)); },
  evaluate: () => { const r = call('nbrowser_evaluate', { sessionId: SID, expression: '1+41' }); return A(String(r.result) === '42', 'alias works', JSON.stringify(r).slice(0, 120)); },
  // nb-bridge#49: screenshots reply ab's delivery contract (fullPath, safePath when wide), never inline base64.
  screenshot: () => { const r = call('nbrowser_screenshot', { sessionId: SID }); return A(r.ok === true && (r.width || 0) > 100 && !!r.fullPath && !r.base64, `${r.width}x${r.height} as fullPath${r.localPath || r.localFullPath ? ', pulled here' : ''}`, JSON.stringify(r).slice(0, 200)); },
  screenshot_full_res: () => { const r = call('nbrowser_screenshot_full_res', { sessionId: SID }); const wide = (r.fullWidth || r.width || 0) > 1568; return A(r.ok === true && !!r.fullPath && !r.base64 && (!wide || !!r.safePath), `${r.fullWidth || r.width}px as fullPath${r.safePath ? ' + safePath ' + r.safeWidth + 'px' : ''}`, JSON.stringify(r).slice(0, 200)); },
  errors: () => { call('nbrowser_click', { sessionId: SID, selector: '#err-btn' }); call('nbrowser_wait', { sessionId: SID, ms: 500 }); const r = call('nbrowser_errors', { sessionId: SID }); return A(JSON.stringify(r).includes('battery-'), 'console error captured', JSON.stringify(r).slice(0, 160)); },
  fetch_url: () => { const r = call('nbrowser_fetch_url', { sessionId: SID, url: 'https://wiki.adom.inc/blob/app/nb-bridge/demo/sample-download.txt' }); return A(JSON.stringify(r).includes('MARKER-7f3a9'), 'fetched sample content', JSON.stringify(r).slice(0, 140)); },

  // input (isTrusted family)
  click: () => {
    const g = call('nbrowser_click', { sessionId: SID, text: 'Duplicate' });
    if (g.errorCode !== 'ambiguous_target' && !/ambiguous/i.test(JSON.stringify(g))) return [false, 'ambiguous_target guard MISSING: ' + JSON.stringify(g).slice(0, 140)];
    const r = call('nbrowser_click', { sessionId: SID, text: 'Click Me Once' });
    let txt = '';
    for (let i = 0; i < 3; i++) { settle(700); txt = String(evalIn('document.getElementById("btn-one").textContent').result); if (txt === 'Clicked Once OK') break; }
    return A(txt === 'Clicked Once OK', `clicked by text (+ ambiguity guard verified)${r.changed === false ? ' [verb changed:false - mutation-detect raced]' : ''}`, `btn text after 3 checks over ~2s: ${txt} (matchedText:${r.matchedText} changed:${r.changed}) - click-by-text DID NOT LAND`);
  },
  double_click: () => { call('nbrowser_double_click', { sessionId: SID, selector: '#dbl-btn' }); settle(); const v = evalIn('document.getElementById("dbl-btn").textContent'); return A(String(v.result) === 'Double OK', 'dblclick landed', `text: ${v.result}`); },
  right_click: () => { call('nbrowser_right_click', { sessionId: SID, selector: '#ctx-target' }); settle(); const v = evalIn('document.getElementById("ctx-target").textContent'); return A(String(v.result) === 'Context OK', 'contextmenu fired', `text: ${v.result}`); },
  hover: () => { call('nbrowser_hover', { sessionId: SID, selector: '#hover-zone' }); settle(); const v = evalIn('getComputedStyle(document.getElementById("hover-menu")).display'); return A(String(v.result) === 'block', 'hover menu revealed', `display: ${v.result}`); },
  type: () => { call('nbrowser_click', { sessionId: SID, selector: '#text-input' }); call('nbrowser_type', { sessionId: SID, selector: '#text-input', text: 'battery-typed' }); settle(); const v = evalIn('document.getElementById("text-input").value'); return A(String(v.result) === 'battery-typed', 'typed into input', `value: ${v.result}`); },
  press_key: () => { call('nbrowser_type', { sessionId: SID, selector: '#text-input', text: 'x' }); const r = call('nbrowser_press_key', { sessionId: SID, key: 'Backspace' }); return A(r.ok !== false, 'key dispatched', JSON.stringify(r).slice(0, 120)); },
  select: () => { const r = call('nbrowser_select', { sessionId: SID, near: 'Battery Select', optionText: 'Beta' }); settle(); const v = evalIn('document.getElementById("select-input").value'); return A(String(v.result) === 'beta', 'option selected by visible text', `select r:${JSON.stringify(r).slice(0,100)} value:${v.result}`); },
  set_date: () => { const r = call('nbrowser_set_date', { sessionId: SID, selector: '#seg-m', date: '2026-08-14' }); settle(); const v = evalIn('document.getElementById("date-note").textContent'); return A(String(v.result) === '2026-08-14', 'segmented date typed (MDY keydowns)', `r:${JSON.stringify(r).slice(0,100)} note:${v.result}`); },
  input_dispatch: () => { const r = call('nbrowser_input_dispatch', { sessionId: SID, type: 'click', selector: '#btn-one' }); return A(r.ok !== false, 'low-level dispatch ok', JSON.stringify(r).slice(0, 140)); },
  set_file_input: () => {
    // Staged under the user's Downloads: nb refuses to hand a page anything from a hidden home folder such as
    // ~/.adom (pup-bridge#145), which is where NB_DIR lives.
    const upDir = resolveNbDir().replace(/\/\.adom\/native-browser$/i, '') + '/Downloads/nb-battery';
    const up = call('send_files', { reason: 'verb battery: staging the sample upload file', filePaths: [join(root, 'demo', 'sample-upload.txt')], dest: upDir });
    if (up.success === false) return [false, 'send_files staging failed: ' + JSON.stringify(up).slice(0, 120)];
    call('nbrowser_set_file_input', { sessionId: SID, selector: '#file-input', filePaths: [upDir + '/sample-upload.txt'] });
    const v = evalIn('document.getElementById("file-note").textContent');
    return A(String(v.result).includes('sample-upload'), 'file attached to input', `note: ${v.result}`);
  },

  // presentation + signals
  caption: () => { const r = call('nbrowser_caption', { sessionId: SID, text: 'verb battery running', durationMs: 1500 }); return A(r.ok === true, 'caption painted', JSON.stringify(r).slice(0, 120)); },
  cursor: () => { const r = call('nbrowser_cursor', { sessionId: SID, x: 300, y: 300, label: 'battery' }); return A(r.ok === true, 'cursor shown', JSON.stringify(r).slice(0, 120)); },
  focus: () => { const r = call('nbrowser_focus', { sessionId: SID }); return A(r.ok !== false && r.foregrounded !== true, 'tab selected, no foreground steal', JSON.stringify(r).slice(0, 140)); },
  flash: () => { const r = call('nbrowser_flash', { sessionId: SID }); return A(r.ok !== false, 'flashed', JSON.stringify(r).slice(0, 120)); },
  flash_stop: () => { const r = call('nbrowser_flash_stop', { sessionId: SID }); return A(r.ok !== false, 'flash stopped', JSON.stringify(r).slice(0, 120)); },
  flash_mode: () => { const r = call('nbrowser_flash_mode', { mode: 'quiet' }); return A(r.ok !== false && r.flashMode === 'quiet', 'flashMode:quiet', JSON.stringify(r).slice(0, 120)); },
  taskbar: () => { const a = call('nbrowser_taskbar', { sessionId: SID, progress: { state: 'indeterminate' } }); const b = call('nbrowser_taskbar', { sessionId: SID, progress: { state: 'none' } }); return A(a.ok !== false && b.ok !== false, 'progress on/off', JSON.stringify(a).slice(0, 120)); },
  fullscreen: () => { const a = call('nbrowser_fullscreen', { sessionId: SID, on: true }); const b = call('nbrowser_fullscreen', { sessionId: SID, on: false }); return A(a.ok !== false && b.ok !== false, 'fullscreen roundtrip', JSON.stringify(a).slice(0, 120)); },

  // tabs
  open_tab: () => { const r = call('nbrowser_open_tab', { sessionId: SID, url: `${HARNESS}?cb=${CB}&tab=2` }); ctx.tab2 = r.tabId; return A(r.ok !== false, `tab:${r.tabId}`, JSON.stringify(r).slice(0, 140)); },
  list_tabs: () => { const r = call('nbrowser_list_tabs', { sessionId: SID }); const n = (r.tabs || []).length; return A(n >= 2, `${n} tabs`, JSON.stringify(r).slice(0, 140)); },
  switch_tab: () => { const r = call('nbrowser_switch_tab', { sessionId: SID, tabId: ctx.tab2 }); return A(r.ok !== false, 'switched', JSON.stringify(r).slice(0, 120)); },
  close_tab: () => { const r = call('nbrowser_close_tab', { sessionId: SID, tabId: ctx.tab2 }); const l = call('nbrowser_list_tabs', { sessionId: SID }); return A((l.tabs || []).length === 1, 'tab closed', JSON.stringify(r).slice(0, 120)); },

  // recording (WGC, background-safe)
  record_start: () => { const r = call('nbrowser_record_start', { method: 'wgc', hwnd: ctx.hwnd, fps: 30 }); ctx.recId = r.recordingId; return A(!!r.recordingId, `rec:${r.recordingId}`, JSON.stringify(r).slice(0, 160)); },
  record_status: () => { const r = call('nbrowser_record_status', { method: 'wgc', recordingId: ctx.recId }); return A(r.ok !== false, JSON.stringify(r).slice(0, 80), JSON.stringify(r).slice(0, 140)); },
  record_stop: () => { call('nbrowser_wait', { sessionId: SID, ms: 1500 }); const r = call('nbrowser_record_stop', { method: 'wgc', recordingId: ctx.recId }); return A(JSON.stringify(r).includes('.mp4') || r.ok === true, 'mp4 produced', JSON.stringify(r).slice(0, 160)); },
  record_list: () => { const r = call('nbrowser_record_list'); return A(r.ok !== false, 'listed', JSON.stringify(r).slice(0, 120)); },

  // downloads
  download: () => { const r = call('nbrowser_download', { sessionId: SID, url: 'https://wiki.adom.inc/blob/app/nb-bridge/demo/sample-download.txt', filename: 'nb-battery-download.txt' }); return A(r.ok !== false, `downloadId:${r.downloadId}`, JSON.stringify(r).slice(0, 140)); },
  download_wait: () => { const r = call('nbrowser_download_wait', { sessionId: SID, sinceMs: 90000, timeoutMs: 25000 }); const files = r.files || []; for (const f of files) if (/battery|sample-download/.test(f)) call('delete_file', { reason: 'verb battery cleanup: removing the test download from Downloads', path: `${resolveNbDir().replace(/\/\.adom\/native-browser$/i, '')}/Downloads/${f}` }); return A((r.count || files.length) >= 1, `landed: ${files.join(', ').slice(0, 80)} (cleaned)`, JSON.stringify(r).slice(0, 160)); },

  // spawn tracking + events + dialogs
  events_and_owned: () => {
    call('nbrowser_click', { sessionId: SID, selector: '#blank-link' });
    call('nbrowser_wait', { sessionId: SID, ms: 1500 });
    const own = call('nbrowser_owned', { sessionId: SID });
    const spawned = (own.spawned || []).length;
    const ev = call('nbrowser_events');
    const sawSpawn = JSON.stringify(ev).includes('spawned_window') || spawned > 0;
    ctx.spawned = spawned;
    return A(sawSpawn, `spawn tracked (${spawned} owned.spawned)`, `no spawn recorded: owned=${JSON.stringify(own).slice(0, 120)} events=${JSON.stringify(ev).slice(0, 120)}`);
  },
  handle_dialog: () => {
    // PRE-ANSWER contract (Chrome cancels dialogs itself while a debugger is attached, so "hold it
    // open and answer later" is impossible - measured 2026-08-22): arm accept:true BEFORE the click,
    // the page answers the confirm with true the moment it fires, disarm returns what was seen.
    // Re-anchor on the harness FIRST: the preceding spawn test can leave the session's active-tab
    // tracking on the spawned page, and the arm/click/eval would then hit a page with no #confirm-btn
    // (this was every earlier failure of this test). The pre-answer does not survive navigation, so
    // navigate BEFORE arming.
    call('nbrowser_navigate', { sessionId: SID, url: `${HARNESS}?dlg=${CB}` });
    call('nbrowser_wait_for', { sessionId: SID, selector: '#confirm-btn' });
    const arm = call('nbrowser_handle_dialog', { sessionId: SID, policy: 'manual', accept: true });
    call('nbrowser_click', { sessionId: SID, selector: '#confirm-btn', retry: false });
    settle(400);
    const off = call('nbrowser_handle_dialog', { sessionId: SID, policy: 'auto' });
    let v = evalIn('document.getElementById("dlg-note").textContent');
    if (v.result === undefined) { settle(900); v = evalIn('document.getElementById("dlg-note").textContent'); }
    const note = String(v.result);
    const sawConfirm = Array.isArray(off.seen) && off.seen.some((x) => x.type === 'confirm');
    return A(arm.armed === true && note === 'confirm:true' && sawConfirm, `pre-answer landed: ${note}; seen:${(off.seen || []).length}`, `arm:${JSON.stringify(arm).slice(0, 100)} off:${JSON.stringify(off).slice(0, 140)} note:${JSON.stringify(v).slice(0, 140)}`);
  },
  cleanup: () => { const r = call('nbrowser_cleanup', { sessionId: SID }); const own = call('nbrowser_owned', { sessionId: SID }); return A((own.spawned || []).length === 0, `spawns closed (was ${ctx.spawned})`, JSON.stringify(r).slice(0, 140)); },

  // credentials family — CONTRACT tests only, never real secrets
  login_state: () => { const r = call('nbrowser_login_state', { sessionId: SID, host: 'wiki.adom.inc' }); return A(r.ok !== false, 'login_state responds', JSON.stringify(r).slice(0, 120)); },
  login_form: () => { const r = call('nbrowser_login_form', { sessionId: SID }); return A(r.ok !== false && JSON.stringify(r).length > 20, `detected form:${r.hasForm}`, JSON.stringify(r).slice(0, 140)); },
  credentials: () => { const r = call('nbrowser_credentials', { host: 'battery-nonexistent.invalid' }); const leaked = /password"\s*:\s*"[^"]/.test(JSON.stringify(r)); return A(!leaked, 'no secret material in response', 'PASSWORD FIELD IN RESPONSE — contract broken'); },
  totp: () => { const r = call('nbrowser_totp', { host: 'battery-nonexistent.invalid' }); const leaked = /"(seed|secret|password)"\s*:\s*"[^"]/.test(JSON.stringify(r)); return A(!leaked && r.ok !== true, 'refused unknown host, no seed leak', JSON.stringify(r).slice(0, 140)); },
  clear_cookies: () => { const r = call('nbrowser_clear_cookies', { host: 'battery-nonexistent.invalid' }); return A(r.ok !== false && (r.removed === 0 || typeof r.removed === 'number'), `removed:${r.removed}`, JSON.stringify(r).slice(0, 140)); },
  add_extension_origin: () => { const r = call('nbrowser_add_extension_origin', { id: 'nncjgceobmlenfliamnibncjojpedbcc' }); return A(r.ok !== false, 'idempotent re-add ok', JSON.stringify(r).slice(0, 140)); },

  open_os_window: () => {
    const r = call('nbrowser_open_os_window', { sessionId: SID + '-l1', thread: THREAD, purpose: 'verb battery: layer-1 open + attested close', profile: PROFILE, url: `${HARNESS}?layer1=${CB}` });
    if (r.ok !== true || r.launched !== true) return [false, 'layer-1 open failed: ' + JSON.stringify(r).slice(0, 160)];
    if (!r.hwnd) return [true, 'launched (grouped into existing window - no hwnd to close, per contract)'];
    const c = call('nbrowser_close_window', { sessionId: SID + '-l1' });
    return A(c.closed === true || c.ok === true, 'layer-1 open + close_window os path (WM_CLOSE)', JSON.stringify(c).slice(0, 160));
  },
  state: () => {
    const r = call('nbrowser_state', { sessionId: SID, screenshot: true });
    const s = (r.sessions || [])[0];
    if (!s) return [false, 'no session reported by state: ' + JSON.stringify(r).slice(0, 160)];
    const hasShot = !!(r.screenshot && r.screenshot.fullPath) && !!r.fullPath;   // nb-bridge#49: a file, promoted for ab's pull
    return A(s.hwnd && String(s.url || '').includes('test-harness') && hasShot, `hwnd:${s.hwnd} url matched, shot file ${hasShot ? 'present' : 'MISSING'}`, `session:${JSON.stringify(s).slice(0, 160)} shot:${hasShot}`);
  },
  demo: () => {
    const r = call('nbrowser_demo', { sessionId: 'nbbattery-demo', url: HARNESS, profile: PROFILE, thread: THREAD }, 120000);
    const ok = r.ok === true && !!r.fullPath && !r.base64 && !!r.hwnd;
    call('nbrowser_close_window', { sessionId: 'nbbattery-demo' });
    return A(ok, `demo opened hwnd:${r.hwnd} + returned a ${r.width}x${r.height} shot file`, JSON.stringify(r).slice(0, 200));
  },
  // guards-as-tests
  guard_autologin: () => { const r = call('nbrowser_autologin', { sessionId: SID, host: 'wiki.adom.inc' }); return A(r.ok !== true && r.loggedIn !== true, 'refused without confirm:true', 'AUTOLOGIN RAN WITHOUT CONFIRM: ' + JSON.stringify(r).slice(0, 120)); },
  guard_close_all: () => { const r = call('nbrowser_close'); return A(r.ok !== true || /confirm/i.test(JSON.stringify(r)), 'close-all demands confirmCloseAll', 'CLOSE-ALL RAN UNCONFIRMED: ' + JSON.stringify(r).slice(0, 120)); },
  guard_record_fallback: () => { const r = call('nbrowser_record_start', { method: 'getdisplaymedia', sessionId: SID }); return A(r.ok !== true, 'picker fallback refused without fallbackConfirmed', 'FALLBACK RAN UNCONFIRMED: ' + JSON.stringify(r).slice(0, 140)); },
  // Since 0.4.6: force_close on YOUR OWN live session's hwnd yields the two-step attestation ticket
  // (the owner may clean up their own OS window - the open_os_window cleanup path depends on it), while
  // ANOTHER thread's window still refuses owned_by_other. The battery runs as the owner, so the pass is:
  // ticket offered, nothing closed by a single call. An outright closed:true here is the failure.
  guard_force_close: () => { const r = call('nbrowser_force_close', { hwnd: ctx.hwnd }); const refused = r.errorCode === 'owned_by_other' || /owned_by_other|Only that thread/i.test(JSON.stringify(r)); const ticketed = r.errorCode === 'attestation_required' || /needsAttestation|overrideTicket/i.test(JSON.stringify(r)); return A((refused || ticketed) && r.closed !== true, ticketed ? 'own-window ticket offered, nothing closed without attestation' : 'live-session hwnd refused (owned_by_other)', 'FORCE_CLOSE CLOSED (or mishandled) A LIVE SESSION WINDOW IN ONE CALL: ' + JSON.stringify(r).slice(0, 140)); },
  profile_block: () => { const b = call('nbrowser_profile_block', { profile: PROFILE }); const g = call('nbrowser_open_tab', { sessionId: SID, url: `${HARNESS}?blocked=1` }); const u = call('nbrowser_profile_unblock', { profile: PROFILE }); const blockedWorked = g.ok !== true || /block/i.test(JSON.stringify(g)); return A(b.ok !== false && u.ok !== false && blockedWorked, 'block refused a drive; unblock restored', `block:${JSON.stringify(b).slice(0, 80)} drive-under-block:${JSON.stringify(g).slice(0, 80)}`); },
  profile_unblock: () => { const r = call('nbrowser_open_tab', { sessionId: SID, url: `${HARNESS}?after-unblock=1&cb=${CB}` }); if (r.tabId) call('nbrowser_close_tab', { sessionId: SID, tabId: r.tabId }); return A(r.ok !== false, 'driving works again after unblock', JSON.stringify(r).slice(0, 140)); },

  // teardown (must be LAST live test)
  close_window: () => {
    const r = call('nbrowser_close_window', { sessionId: SID, includeSpawned: true });
    const own = call('nbrowser_owned', { sessionId: SID });
    const clean = (own.opened || []).length === 0 && (own.spawned || []).length === 0;
    return A(clean, 'window closed, zero litter', `still owned: ${JSON.stringify(own).slice(0, 140)}`);
  },
  owned: () => [true, 'exercised inside events_and_owned + close_window'],
};

// ─── coverage map: EVERY VERB_META verb decides its fate here ───────────────
export const COVERAGE = {
  status: 'live', readiness: 'live', ping: 'live', progress: 'live', scroll: 'live', input_unknown_type: 'guard', hover_raw_xy: 'live', apply_settings: 'skip:internal bridge-to-extension push, exercised by the settings test through nbrowser_settings', whoami: 'live', profiles: 'live',
  resolve_profiles: 'live', prewarm: 'live', dead_session_scoping: 'live', export_site_session: 'live', drop_handoff: 'live', export_readiness: 'live', export_saved_logins: 'guard', eval_on_browser_page: 'live', guard_profile_required: 'guard', open_minimized: 'live', chrome_status: 'live', describe: 'live', activity: 'live',
  os_windows: 'live', wake_profile: 'live', rescan: 'live', resync_sessions: 'live',
  open_window: 'live', pin_session: 'live', use_profile: 'live', list_windows: 'live',
  window_state: 'live', switch_window: 'live', navigate: 'live', reload: 'live',
  settings: 'live', back: 'live', forward: 'live', wait: 'live', wait_for: 'live', keepalive: 'live',
  eval: 'live', evaluate: 'live', screenshot: 'live', screenshot_full_res: 'live',
  errors: 'live', fetch_url: 'live', click: 'live', double_click: 'live',
  right_click: 'live', hover: 'live', type: 'live', press_key: 'live', select: 'live',
  set_date: 'live', input_dispatch: 'live', set_file_input: 'live', caption: 'live',
  cursor: 'live', focus: 'live', flash: 'live', flash_stop: 'live', flash_mode: 'live',
  taskbar: 'live', fullscreen: 'live', open_tab: 'live', list_tabs: 'live',
  switch_tab: 'live', close_tab: 'live', record_start: 'live', record_status: 'live',
  record_stop: 'live', record_list: 'live', download: 'live', download_wait: 'live',
  handle_dialog: 'live', login_state: 'live', login_form: 'live', credentials: 'live',
  totp: 'live', clear_cookies: 'live', add_extension_origin: 'live', owned: 'live',
  cleanup: 'live', close_window: 'live', profile_block: 'live', profile_unblock: 'live',
  open_os_window: 'live', state: 'live', demo: 'live',
  autologin: 'guard', close: 'guard', force_close: 'guard',
  events: 'live:events_and_owned',
  install_chrome: 'skip:starts a real Chrome install on the desktop — exercised manually on fresh boxes',
  register_totp: 'skip:writes a real TOTP seed into the vault — contract covered by totp read-path test',
  dev_reload: 'skip:restarts the SW + native host and is throttled 1/90s — disruptive to live sessions',
  uninstall: 'skip:the ab-2.0.35 cascade hook — deletes host registrations; tested at release time only',
};

function metaCount() {
  const s = readFileSync(join(root, 'bridge', 'server.js'), 'utf8');
  const i = s.indexOf('const VERB_META = {'); let d = 0, e = -1;
  for (let j = s.indexOf('{', i); j < s.length; j++) { if (s[j] === '{') d++; else if (s[j] === '}') { d--; if (!d) { e = j; break; } } }
  return [...s.slice(i, e + 1).matchAll(/^ {2}nbrowser_[a-z0-9_]+:\s*\{/gm)].length;
}

function pollUrl(pred, tries = 8, gapMs = 500) {
  let last = '';
  for (let i = 0; i < tries; i++) {
    const u = evalIn('location.search'); last = String(u.result);
    if (pred(last)) return { ok: true, last };
    call('nbrowser_wait', { sessionId: SID, ms: gapMs });
  }
  return { ok: false, last };
}

function discoverProfile() {
  const r = call('nbrowser_profiles', {});
  // Discovery failing = the target is unreachable. ABORT here rather than fall back to a guessed
  // profile: the 12:18 relay blip made discovery "succeed" with the hardcoded laptop profile against
  // ROG, which would have graded a full run against the wrong identity.
  if (TARGET_LOST || !r || !Array.isArray(r.profiles)) {
    console.log(`⚠️  RUN ABORTED: could not enumerate profiles on '${TARGET}' (${TARGET_LOST || 'nbrowser_profiles gave no list'}) - not guessing a profile.`);
    process.exit(2);
  }
  const ps = r.profiles;
  // Prefer a SIGNED-IN live profile over an id key (`chrome:p_xxxx`). An id key can belong to a browser
  // outside the default user-data dir (pup loads nbe into its own Chrome), which Layer 1 rightly refuses
  // to map to a disk profile when several candidates exist (laptop battery, 2026-09-20).
  const signed = (p) => p.live && p.email;
  const live = ps.find((p) => signed(p) && p.browser === 'chrome') || ps.find(signed)
    || ps.find((p) => p.live && p.browser === 'chrome') || ps.find((p) => p.live);
  if (live) return live.profile;
  const installed = ps.find((p) => p.extensionInstalled && p.browser === 'chrome') || ps.find((p) => p.extensionInstalled);
  if (installed) { call('nbrowser_wake_profile', { profile: installed.profile }); return installed.profile; }
  if (!ps.length) { console.log(`⚠️  RUN ABORTED: '${TARGET}' reports zero browser profiles.`); process.exit(2); }
  return ps[0].profile;
}

if (import.meta.url === `file://${process.argv[1]}`) {
  if (!PROFILE) { PROFILE = discoverProfile(); console.log(`profile auto-discovered: ${PROFILE}`); }
  console.log(`nb verb battery — target:${TARGET} profile:${PROFILE} harness:${HARNESS}\n`);
  // TEARDOWN-FIRST: a prior run (crashed, ctrl-C'd, or mis-argued) can leave the battery session
  // alive with a pinned profile; the profile_mismatch guard then correctly refuses everything.
  // Idempotency means every run starts from zero.
  call('nbrowser_close_window', { sessionId: SID, includeSpawned: true });
  call('nbrowser_close_window', { sessionId: SID + '-l1' });
  // Closing the window does NOT drop the session's profile PIN, and a pin left by a run that used a
  // different profile makes every later verb fail profile_mismatch (laptop, 2026-10-21). Re-pin to the
  // profile THIS run will drive, so a run is never poisoned by the one before it.
  for (const sid of [SID, SID + '-l1', SID + '-min', SID + '-dead', SID + '-cdpblock', SID + '-demo']) {   // NOT the profile_required probe: it must stay unpinned
    call('nbrowser_pin_session', { sessionId: sid, profile: PROFILE });
  }
  // WARM-UP: a daily run can land seconds after ab auto-respawned the bridge (self-update), before
  // any extension SW has re-dialed. Wake the profile and give it up to 30s - readiness cold-starts
  // are an ab lifecycle fact, not a verb regression.
  call('nbrowser_wake_profile', { profile: PROFILE });
  for (let i = 0; i < 10; i++) { const r = call('nbrowser_readiness'); if (r.ready === true) { console.log(`warm-up: ready after ${i} poll(s)\n`); break; } execFileSync('sleep', ['3']); }
  const order = ['status','readiness','ping','whoami','profiles','resolve_profiles','prewarm','guard_profile_required','open_minimized','chrome_status','describe','activity','os_windows','wake_profile','rescan','resync_sessions',
    'open_window','pin_session','use_profile','list_windows','window_state','switch_window',
    'navigate','reload','back','forward','wait','wait_for','keepalive',
    'eval','evaluate','screenshot','screenshot_full_res','errors','fetch_url',
    'click','double_click','right_click','hover','type','press_key','select','set_date','input_dispatch','set_file_input',
    'caption','cursor','focus','flash','flash_stop','flash_mode','taskbar','fullscreen',
    'open_tab','list_tabs','switch_tab','close_tab',
    'record_start','record_status','record_stop','record_list',
    'download','download_wait','login_state','login_form','credentials','totp','clear_cookies','add_extension_origin',
    'state','demo','open_os_window','guard_autologin','guard_record_fallback','guard_force_close','guard_close_all','profile_block','profile_unblock',
    'events_and_owned','handle_dialog','cleanup','owned','close_window','dead_session_scoping','export_site_session','drop_handoff','export_readiness','export_saved_logins','eval_on_browser_page'];
  for (const name of order) {
    if (TARGET_LOST) { record(name, 'skip', true, `not run - ${TARGET_LOST}`); continue; }
    const fn = T[name]; if (!fn) { record(name, '??', false, 'no test fn'); continue; }
    const q = Object.entries(COVERAGE).find(([v, m]) => (v === name) && String(m).startsWith('quarantine:'));
    try { const [ok, note] = fn(); const mode = q ? 'quarantine' : (name.startsWith('guard') ? 'guard' : 'live');
      record(name, mode, q ? true : ok, q && !ok ? `KNOWN BUG still present (${String(q[1]).slice(11)})` : (q && ok ? `KNOWN BUG NOT REPRODUCED - consider un-quarantining (${String(q[1]).slice(11)})` : note)); }
    catch (e) { record(name, q ? 'quarantine' : 'live', !!q, 'threw: ' + String(e.message).slice(0, 160)); }
  }
  for (const [v, mode] of Object.entries(COVERAGE)) if (mode.startsWith('skip:')) record(v, 'skip', true, mode.slice(5));
  if (TARGET_LOST) {
    console.log(`\n⚠️  RUN ABORTED: ${TARGET_LOST}. Results above are NOT a verdict on the bridge - re-run when the desktop is back (adom-bridge targets).`);
    writeFileSync(join(root, 'tools', 'verb-battery-report.json'), JSON.stringify({ at: new Date().toISOString(), target: TARGET, aborted: TARGET_LOST, results }, null, 1));
    process.exit(2);
  }
  const fails = results.filter((r) => !r.ok);
  const report = { at: new Date().toISOString(), target: TARGET, profile: PROFILE, pass: results.length - fails.length, fail: fails.length, results };
  writeFileSync(join(root, 'tools', 'verb-battery-report.json'), JSON.stringify(report, null, 1));
  console.log(`\n${report.pass}/${results.length} passed, ${fails.length} failed — report: tools/verb-battery-report.json`);
  process.exit(fails.length ? 1 : 0);
}