✔ loadConfig: missing file throws ConfigError (2.069893ms)
✔ loadConfig: invalid JSON throws ConfigError (0.438417ms)
✔ loadConfig: missing dataRoot throws ConfigError (0.327993ms)
✔ loadConfig: relative dataRoot throws ConfigError (0.325869ms)
✔ loadConfig: valid config returns dataRoot (1.031496ms)
✔ findNewestSession: picks the newest by mtime among two files (0.731378ms)
✔ findNewestSession: finds files in nested subdirectories (0.449258ms)
✔ findNewestSession: returns null for a missing dir (0.190751ms)
✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (1.108679ms)
✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (2.038489ms)
✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.855495ms)
✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (2.03661ms)
✔ deriveState: full state table (0.313836ms)
✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.601595ms)
✔ rule: newest entry is a tool result with no assistant text after it is working (0.531506ms)
✔ rule: a finished turn (text-only assistant message, stopReason stop) is waiting (0.405072ms)
✔ task: the first user message of the session, from text blocks (0.491929ms)
✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.381223ms)
✔ task: no user message in the log means null (shown as unknown), never a guess (0.472373ms)
✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.567966ms)
✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.643836ms)
✔ scan: the written record carries task, workspace and activeProject (0.745408ms)
✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.812363ms)
✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.549512ms)
✔ registration: a record whose pid is gone is stale; derived values win, sources say derived, registered stays with alive false; a pid the probe cannot decide is not stale; pidAlive itself (1.429642ms)
✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.623553ms)
✔ loadRegistrations: a missing seatsDir gives empty lists (0.275783ms)
✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (1.147462ms)
✔ matchRegistration: matches by sessionsDir, and by realpath through a symlink; sessionsDir null never matches; same seat name with a different sessionsDir does not match (fleet vs repo darkwing) (1.76852ms)
✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (1.131616ms)
✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.313115ms)
✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (1.127154ms)
✔ scanAgent: ageSeconds is computed from the injected now (0.302815ms)
✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.199626ms)
✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.381178ms)
✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.405132ms)
✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (1.289574ms)
✔ scan: relative boardDir throws ConfigError (0.150536ms)
✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (89.928423ms)
✔ CLI: missing config exits 2 with a refused: message (80.253972ms)
✔ CLI: unknown command exits 2 (79.841572ms)
✔ CLI: unknown --liveness value exits 2 (90.403332ms)
✔ panesRunPi: true when any trimmed line equals 'pi' (0.356649ms)
✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.162436ms)
✔ tmuxIsAlive: a pane running pi is alive (0.297307ms)
✔ tmuxIsAlive: session exists but pi has exited is not alive (0.124004ms)
✔ tmuxIsAlive: no such tmux session is not alive (1.002756ms)
✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.141834ms)
✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.188087ms)
✔ parsePanes: one pane per line, command and optional tab-separated path (0.141459ms)
✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.129298ms)
✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.172234ms)
✔ loadSeen: missing file returns {} (1.594824ms)
✔ loadSeen: invalid JSON throws ConfigError (2.10826ms)
✔ loadSeen: a JSON array throws ConfigError (0.343731ms)
✔ loadSeen: a non-string value throws ConfigError (4.345296ms)
✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (3.563497ms)
✔ markSeen: seen false deletes the key (1.941261ms)
✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.477522ms)
✔ markSeen: project containing '/' throws ConfigError (0.231097ms)
✔ markSeen: non-boolean seen throws ConfigError (0.202643ms)
✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (2.88064ms)
✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.617591ms)
✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (1.806863ms)
✔ scanAgent: an error-state session with a matching mark is seen (2.149805ms)
✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (1.045558ms)
✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.490992ms)
✔ isLoopbackHost: recognizes loopback hosts (2.380812ms)
✔ isLoopbackHost: rejects non-loopback hosts (6.201413ms)
✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (4.882151ms)
✔ startServer: serves page, healthz, and a rescanning /api/board (48.899289ms)
✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (11.47682ms)
✔ startServer: /api/board returns 500 JSON with an error field when scan throws (4.702469ms)
✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (78.932321ms)
✔ CLI: serve rejects a non-numeric --port with exit 2 (82.056496ms)
✔ CLI: scan still works after the async cli refactor (93.42089ms)
✔ CLI: live serve prints its URL and answers /healthz (134.584373ms)
✔ page.html: esc() escapes every HTML-significant character (1.293554ms)
✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (19.569156ms)
✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (5.81507ms)
✔ POST /api/seen with invalid JSON returns 400 (8.413609ms)
✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (6.922079ms)
✔ POST /api/seen with a missing agent returns 400 (6.492325ms)
✔ POST /api/board returns 405; PUT /api/seen returns 405 (6.785092ms)
✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (93.27849ms)
✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.666541ms)
✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (1.860189ms)
✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.458153ms)
✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.301181ms)
✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.522645ms)
✔ page.html: task and active project cells show their source via sourceTag(); the detail has a Registered row via registeredText(); SOURCE_LABEL maps registration to registered; every dynamic value in sourceTag/fromSource/registeredText is escaped (1.596776ms)
✔ POST /api/reply: runs agent-send.sh with -s from the registration, -S <host>:control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams (66.023811ms)
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (47.085295ms)
✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (44.906851ms)
✔ POST /api/reply: refusals before the tool runs: empty or blank or long text 400, unknown row 404, no registration 409, stale registration 409, no tmux session 409, bad JSON 400; the tool is never called (30.336458ms)
✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (6.232881ms)
✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.196143ms)
✔ page.html: the reply box appears only where canReply() holds (live registration with a tmux session), the detail has a Reply row, the submit posts JSON to /api/reply, receipts and drafts survive a refresh, and every receipt value is escaped (0.713094ms)
✔ real helper GET HTTP 200 preserves exit 0 without credentials (16.87011ms)
✔ real helper POST HTTP 201 preserves exit 0 without credentials (19.904021ms)
✔ real helper GET HTTP 403 preserves exit 1 without credentials (14.960278ms)
✔ fixture git subjects only, follow-ups and three session kinds (150.605163ms)
✔ text and JSON carry same numbers, open and truncated title (323.809392ms)
✔ missing credentials exit 2, no-issues never calls API and shows unknown (256.457428ms)
✔ empty range gives no rows and zero totals (141.564883ms)
✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (116.133329ms)
✔ close-only issue included, even median, missing metadata stays unknown (88.374838ms)
✔ unique commits but per-issue links count multiple tags once each (87.716176ms)
✔ page cap refuses rather than silently undercounting (101.467302ms)
✔ bad API payload not JSON refuses (91.969036ms)
✔ bad API payload {} refuses (86.830577ms)
✔ bad API payload [{"number":1}] refuses (77.291166ms)
✔ partial or malformed session log refuses with location, not content (91.720579ms)
✔ no sessions is an empty table; symlink source refuses (138.05013ms)
✔ reads only refactor even when another branch is checked out (85.265578ms)
✔ invalid dates, reverse dates and duplicate options refuse (46.116248ms)
✔ preamble parsing and issue number boundaries (0.357602ms)
✔ no closed issues with human messages means undefined ratio, not invented zero (0.191973ms)
✔ pure resolution selects current default or explicit enrolled account (3.452427ms)
✔ scope is explicit, bounded and never inferred (3.224534ms)
✔ fork pin is preserved against default change, override, missing account and revocation (2.07011ms)
✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (1.785801ms)
✔ only explicit synthetic credential forms and internal fixture stores admitted (12.853623ms)
✔ two concurrent workspaces of the same agent publish distinct complete private generations (89.218237ms)
✔ same execution ID is exclusively claimed and cannot overwrite a published generation (51.919745ms)
✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (69.110981ms)
✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (165.301085ms)
✔ credential lock contention refuses without duplicate side effects (34.503814ms)
✔ symlinked pre-existing final target is refused and never followed (76.344676ms)
✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.585699ms)
✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (90.149481ms)
✔ expired credentials refresh under transaction and subsequent generation reuses rotation (229.155792ms)
✔ refresh failure retains prior generation and store state (80.103824ms)
✔ refresh timeout retains prior generation and store state (166.055716ms)
✔ refresh malformed retains prior generation and store state (68.191722ms)
✔ concurrent refresh on same account refuses contention while unrelated account proceeds (218.21407ms)
✔ invalid refresh options refuse before burning claim (39.334705ms)
✔ fixed fake process rotates both OAuth fields without mutating caller input (51.399762ms)
✔ concurrent isolated processes preserve separate provider credentials (55.271858ms)
✔ fake failure is refused with fixed diagnostics (33.787807ms)
✔ fake malformed is refused with fixed diagnostics (47.268272ms)
✔ fake timeout is refused with fixed diagnostics (108.39385ms)
✔ fake unchanged is refused with fixed diagnostics (64.498115ms)
✔ caller executable/environment injection is rejected before spawning (0.483922ms)
✔ valid fixture tree validates and lists without secrets (112.29331ms)
✔ unknown-field refuses (0.679922ms)
✔ invalid-id refuses uppercase and traversal shapes (0.462347ms)
✔ plain-http baseUrl requires allowInsecureTransport (0.517778ms)
✔ native provider rejects allowInsecureTransport (0.232811ms)
✔ unsupported credential type and kind refuse (0.267028ms)
✔ account provider-path mismatch refuses (1.053032ms)
✔ profile account refs must be provider/account shaped (0.410258ms)
✔ seat selection accepts fork pin field, validates account refs (0.385189ms)
✔ harness manifest id must equal executable (gate 1) (0.471681ms)
✔ CLI validate: duplicate provider id across files refuses (49.292022ms)
✔ CLI validate: missing referenced provider/account refuse (56.231526ms)
✔ CLI validate: broken JSON refuses without secret echo (44.514594ms)
✔ CLI usage errors exit 2 (111.897184ms)
✔ credential.json sibling presence does not break validation and is never read (168.781797ms)
✔ D1 missing, empty and structurally empty roots refuse, no list projection (275.749732ms)
✔ D1 required directory auth cannot be absent (124.619472ms)
✔ D1 required directory auth/providers cannot be absent (136.518861ms)
✔ D1 required directory auth/accounts cannot be absent (117.238735ms)
✔ D1 required directory auth/settings cannot be absent (119.706456ms)
✔ D1 required directory harnesses cannot be absent (97.054141ms)
✔ D1 root file and unreadable metadata refuse (137.663685ms)
✔ D2 no symlink traversal at auth/providers/openai-codex.json (57.104514ms)
✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (58.8982ms)
✔ D2 no symlink traversal at auth/providers (69.919373ms)
✔ D2 no symlink traversal at auth (66.585499ms)
✔ D2 root and ancestor symlinks and lexical traversal refuse (182.602198ms)
✔ private filesystem modes enforced for root (56.988873ms)
✔ private filesystem modes enforced for auth (54.402276ms)
✔ private filesystem modes enforced for auth/providers/openai-codex.json (55.196762ms)
✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (62.434238ms)
✔ D3 numeric version 1 only across all record kinds (1.213381ms)
✔ D4 nested unknown keys and missing per-kind required fields refuse (51.243059ms)
✔ D5 unenrolled default refuses even when account exists (61.905203ms)
✔ D6 provider/account credential type must match (61.029479ms)
✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.495449ms)
✔ D8 URLs reject embedded credentials and unsupported protocols without echo (164.103475ms)
✔ D9 malformed JSON diagnostics contain no content excerpt (61.131821ms)
✔ D10 missing metadata is missing-path, not invalid-json (61.521407ms)
✔ D10 library returns no partial entries on any invalid record (70.423693ms)
✔ null/scalar/array metadata refuses without stack or echo (216.403521ms)
✔ credential sibling is never opened, even when an unreadable symlink (34.349719ms)
✔ oversized metadata refuses before parsing (58.959915ms)
✔ resolveSeat: by name under --repo resolves the repo layout (2.244578ms)
✔ resolveSeat: by path resolves the fleet layout (0.655672ms)
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (1.497292ms)
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (1.278671ms)
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (1.478842ms)
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.781997ms)
✔ updateTask: changes task and updatedAt only, and refuses appropriately (2.864265ms)
✔ CLI launch: registers, execs the fake launch script, and passes args through (40.186244ms)
✔ CLI launch: --harness lands in the record (62.351088ms)
✔ CLI launch: the launch script's own exit code passes through (52.770779ms)
✔ CLI launch: relaunching a seat rewrites the one registration record (103.782698ms)
✔ CLI launch: omitting --task records an empty string, not null (68.271979ms)
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (201.947039ms)
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (249.955376ms)
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.580992ms)
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (1340.984357ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (1624.334009ms)
✔ loopback host and board origin fail closed (7.093521ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (97.215606ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (36.079167ms)
✔ unreachable board reports URL; CLI rejects unsupported options (389.105161ms)
ℹ tests 208
ℹ suites 0
ℹ pass 208
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2604.12737
