From 852b3bb2322060bbbaf8f7d07ef70b2a1765c294 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 27 May 2026 17:10:44 -0700 Subject: [PATCH] [eric] refactor: trim the verifier and mcp comments to one line each per the updated convention --- e2e/mcp/electron-mcp.js | 17 +--------------- e2e/mcp/selftest.js | 11 ++-------- scripts/ci/lib/app-harness.js | 34 ++++++++----------------------- scripts/ci/selftest-gate.js | 16 ++------------- scripts/ci/verify-agent-turn.js | 20 +----------------- scripts/ci/verify-installer.js | 28 ++++--------------------- scripts/ci/verify-network.js | 23 ++------------------- scripts/ci/verify-packaged-app.js | 17 +--------------- scripts/ci/verify-resilience.js | 17 +--------------- scripts/ci/verify-signature.js | 14 +------------ 10 files changed, 23 insertions(+), 174 deletions(-) diff --git a/e2e/mcp/electron-mcp.js b/e2e/mcp/electron-mcp.js index b13d28ae..9e42ae5e 100644 --- a/e2e/mcp/electron-mcp.js +++ b/e2e/mcp/electron-mcp.js @@ -1,20 +1,5 @@ #!/usr/bin/env node -// A tiny MCP server that hands a Claude Code instance the "hand + eyes" to drive -// the REAL packaged OpenSwarm app: launch it, click, type, screenshot, read the -// live DOM/accessibility tree, run JS in the renderer, and tail backend.log. -// -// Why this exists: the deterministic scripts in scripts/ci/ cover boot/serve/ -// resilience/network/agent-turn (fast, free, the CI gate). This covers the part -// scripts can't express - actual GUI behavior - by letting a CC instance you talk -// to drive the app at Playwright (DOM) precision. The official @playwright/mcp is -// browser-only, so we wrap Playwright's Electron (_electron) API ourselves. -// -// Register it (repo-root .mcp.json) and any CC instance auto-connects: -// { "mcpServers": { "openswarm-gui": { "command": "node", -// "args": ["e2e/mcp/electron-mcp.js"] } } } -// -// The launched ElectronApplication + main Page are held across calls, so a CC -// session clicks through a single live app the way a person would. +// MCP server giving a Claude Code instance a Playwright hand on the real packaged app (launch/click/type/screenshot/eval/read-log); wraps Electron _electron since @playwright/mcp is browser-only. App held across calls. See e2e/mcp/README.md. 'use strict'; const fs = require('fs'); diff --git a/e2e/mcp/selftest.js b/e2e/mcp/selftest.js index d8f69c4b..ea19ab8e 100644 --- a/e2e/mcp/selftest.js +++ b/e2e/mcp/selftest.js @@ -1,11 +1,5 @@ #!/usr/bin/env node -// Proves the openswarm-gui MCP server actually works, end-to-end, the way a CC -// instance would use it: spawn the server over stdio, list its tools, then (unless -// --no-launch) launch the real app, screenshot it, read the log, and close. -// -// node e2e/mcp/selftest.js [--no-launch] -// -// Exit 0 = the hand works. Exit 1 = it doesn't (prints why). +// Proves the openswarm-gui MCP server works: spawn over stdio, list tools, then (unless --no-launch) launch the app, screenshot, assert the renderer painted, and close. 'use strict'; const path = require('path'); @@ -41,8 +35,7 @@ async function main() { if (!isImage) throw new Error('screenshot did not return a PNG'); process.stdout.write(`screenshot -> ${shot.content[0].data.length} base64 bytes\n`); - // A PNG of the right size could still be a BLANK window, so prove the renderer - // actually painted: assert the React root mounted children (the real render gate). + // A PNG of the right size could still be a blank window, so prove the renderer painted: assert #root mounted children. const root = await client.callTool({ name: 'eval', arguments: { expression: "document.getElementById('root').childElementCount" } }); const childCount = Number(root.content?.[0]?.text); if (!(childCount > 0)) throw new Error(`renderer #root has ${root.content?.[0]?.text} children (blank window, not a real paint)`); diff --git a/scripts/ci/lib/app-harness.js b/scripts/ci/lib/app-harness.js index c5c41acd..3a73a383 100644 --- a/scripts/ci/lib/app-harness.js +++ b/scripts/ci/lib/app-harness.js @@ -1,11 +1,5 @@ 'use strict'; -// Shared plumbing for the packaged-app verifiers in scripts/ci/. Every verifier -// needs the same things: find the built artifact for this OS, launch it, read the -// backend.log it writes, and kill it cleanly. Keeping that here means each verifier -// stays small and single-purpose (boot, resilience, signature, network). -// -// These helpers THROW on misuse and return data on success; the calling script -// owns the pass/fail print + exit code so the harness has no opinion on policy. +// Shared plumbing for the scripts/ci/ verifiers: locate the built artifact, launch it, read its backend.log, kill it cleanly. Helpers throw on misuse; callers own pass/fail. const fs = require('fs'); const os = require('os'); @@ -29,11 +23,9 @@ function packagedAppPath(explicit) { return found; } -// The on-disk binary the OS actually signs/scans: the .exe on win, the .app -// bundle dir on mac (codesign/spctl assess the bundle, not the inner MachO). +// The on-disk thing the OS signs/scans: the .exe on win, the .app bundle on mac. function signableTarget(appExecutable) { if (process.platform === 'darwin') { - // .../OpenSwarm.app/Contents/MacOS/OpenSwarm -> .../OpenSwarm.app const i = appExecutable.indexOf('.app'); return i === -1 ? appExecutable : appExecutable.slice(0, i + 4); } @@ -47,8 +39,7 @@ function backendLogPath() { return path.join(xdg, 'OpenSwarm', 'data', 'backend.log'); } -// The bearer token the shell writes before the HTTP bind; tests reuse it to call -// the same authenticated API the app itself uses. +// The bearer token the shell writes before bind; tests reuse it to call the authed API. function authTokenPath() { const dir = path.dirname(backendLogPath()); return path.join(dir, 'auth.token'); @@ -62,8 +53,7 @@ function readFileSafe(p) { try { return fs.readFileSync(p, 'utf8'); } catch { re function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } function spawnApp(appPath, extraArgs = []) { - // detached on posix so we can SIGKILL the whole process group (the app spawns - // python + 9router children); on win we reap by image name instead. + // detached on posix so we can SIGKILL the whole process group (python + 9router children); on win we reap by image name. return spawn(appPath, extraArgs, { detached: process.platform !== 'win32', stdio: 'ignore', cwd: path.dirname(appPath) }); } @@ -86,8 +76,7 @@ function healthCode(port, timeoutMs = 3000) { }); } -// Authenticated JSON call to the running backend, the same way the app calls it. -// Returns { status, json, text }; status 0 means the request never completed. +// Authenticated JSON call to the running backend; returns { status, json, text } (status 0 = never completed). function apiRequest(port, { method = 'GET', path = '/', token = '', body = null, timeoutMs = 30000 } = {}) { return new Promise((resolve) => { const data = body != null ? Buffer.from(JSON.stringify(body)) : null; @@ -106,9 +95,7 @@ function apiRequest(port, { method = 'GET', path = '/', token = '', body = null, }); } -// Find an already-running app to reuse (so we exercise the user's logged-in -// creds) by reading the token off disk and the port out of the last backend.log, -// then confirming it actually answers. Returns { port, token } or null. +// Reuse an already-running app (the user's logged-in creds): read the token + last logged port and confirm it answers. Returns { port, token } or null. async function attachToRunning() { const token = readFileSafe(authTokenPath()).trim(); const m = readFileSafe(backendLogPath()).match(/Backend ready on port (\d+)/g); @@ -133,11 +120,7 @@ function parsePerfMarks(log) { return marks; } -// Pure verdict on a backend.log: the log-based half of the boot check (provenance -// matches HEAD, the three perf marks exist, are ordered, and are not degenerate). -// Kept pure + exported so it can be mutation-tested (selftest-gate.js feeds it -// crafted broken logs and proves each guard fires) without launching the app. -// Returns { failures: string[], sha, marks }; empty failures == the log half passed. +// Pure, mutation-testable verdict on a backend.log (provenance == HEAD, perf marks present/ordered/non-degenerate); returns { failures, sha, marks }, empty failures == passed. function bootFailures({ log, headShort } = {}) { const failures = []; const sha = parseProvenanceSha(log || ''); @@ -156,8 +139,7 @@ function bootFailures({ log, headShort } = {}) { return { failures, sha, marks }; } -// Launch the app and poll its backend.log until it reports HTTP-ready (or time out). -// Returns { child, log, port }. Caller is responsible for killApp(child). +// Launch the app and poll backend.log until HTTP-ready (or time out); returns { child, log, port }. Caller calls killApp. async function launchAndWait({ appPath, timeoutMs = 180000, freshLog = true } = {}) { const logPath = backendLogPath(); if (freshLog) { diff --git a/scripts/ci/selftest-gate.js b/scripts/ci/selftest-gate.js index 4cc90569..9411cd11 100644 --- a/scripts/ci/selftest-gate.js +++ b/scripts/ci/selftest-gate.js @@ -1,16 +1,5 @@ #!/usr/bin/env node -// "Test the tests" - mutation testing of the gate's own logic. For each guard in -// the boot check, we feed a deliberately BROKEN backend.log and assert the guard -// fires (and that a good log passes). If breaking the input doesn't turn the gate -// red, the gate is theater; this script fails loudly when that happens. -// -// This covers the PURE, log-based assertions (provenance + perf). The live-process -// guards (signature --require-signed, wrong-token auth, verify-all aggregation, -// renderer paint) are fault-injected separately - see GATE_AUDIT.md. -// -// node scripts/ci/selftest-gate.js -// -// Exit 0 = every guard discriminates good from broken. Exit 1 = a guard is fake. +// Test-the-tests: feeds broken backend.logs to the boot check and asserts each guard fires (a good log passes); if a break stops going red, the gate is theater. Live-process guards: see GATE_AUDIT.md. 'use strict'; const h = require('./lib/app-harness'); @@ -43,8 +32,7 @@ check('degenerate all-zero marks -> caught', caught( '[provenance] OpenSwarm 1 sha=abc123def456 channel=stable\n[perf] app-launch t=0\n[perf] first-paint t=0\n[perf] backend-http-ready t=0', HEAD, /> 0|degenerate/)); -// And a stale build (old sha) must be caught even with all marks fine - the exact -// real-world case we already saw fire live. +// A stale build (old sha, all marks fine) must still be caught - the case we saw fire live. check('stale build (every mark fine, wrong sha) -> still caught', caught(GOOD.replace('abc123def456', 'deadbeef0000'), HEAD, /!= git HEAD/)); process.stdout.write('\nparse-function edge cases:\n'); diff --git a/scripts/ci/verify-agent-turn.js b/scripts/ci/verify-agent-turn.js index 865f234b..d1851d94 100644 --- a/scripts/ci/verify-agent-turn.js +++ b/scripts/ci/verify-agent-turn.js @@ -1,23 +1,5 @@ #!/usr/bin/env node -// Drives a REAL agent turn end-to-end against the packaged app, using whatever -// provider the app is already logged into on THIS machine (your subscription / -// 9router / API key) — no separate key, no second agent. It proves the full -// round-trip: session launch -> message -> the model actually generated a reply. -// -// Why this is gated: a real turn spends a sliver of your LLM quota. So it only -// runs when OPENSWARM_E2E_AGENT=1; otherwise it prints a skip and exits 0, which -// keeps CI from silently billing you. -// -// How it proves a real reply (not a fake pass): it launches with NO tools (so the -// agent can't trip an approval gate and hang) and a trivial prompt, then polls the -// session until status is terminal and asserts the model produced output tokens -// (tokens.output > 0) with no error. Output tokens can only come from a live -// provider answering — they can't be faked by the harness. -// -// OPENSWARM_E2E_AGENT=1 node scripts/ci/verify-agent-turn.js [--app ] [--prompt "..."] -// -// Note: the [perf] first-agent-response mark is emitted by the RENDERER, so it is -// asserted in the GUI walkthrough, not here (this path is API-driven by design). +// Drives a REAL agent turn on the app's own login (gated by OPENSWARM_E2E_AGENT=1; spends quota, else skips). Launches tool-free (no approval-gate hang) and asserts terminal status + tokens.output>0, a live reply the harness cant fake. 'use strict'; const h = require('./lib/app-harness'); diff --git a/scripts/ci/verify-installer.js b/scripts/ci/verify-installer.js index c85ba3d0..6c0d91b3 100644 --- a/scripts/ci/verify-installer.js +++ b/scripts/ci/verify-installer.js @@ -1,22 +1,5 @@ #!/usr/bin/env node -// Verifies the Windows NSIS installer (OpenSwarm-Setup-x64.exe). Two modes, -// because the installer is oneClick + per-user to a FIXED dir -// (%LOCALAPPDATA%\Programs\OpenSwarm), so a blind install/uninstall on a machine -// that already has OpenSwarm would DESTROY the user's real install. -// -// default (safe, observational): validate the Setup.exe artifact, and if an -// install is present, assert its invariants (dir layout, uninstaller registry -// entry whose target actually exists, shortcuts). Touches nothing. -// -// --destructive: the real cycle - install /S, assert invariants, (optionally -// launch + verify-all), uninstall /S, assert removal AND that user data -// survived (deleteAppDataOnUninstall:false). REFUSES if an install already -// exists unless --force, so it only runs where there is nothing to clobber -// (a clean machine / CI runner). This is what the CI installer job runs. -// -// node scripts/ci/verify-installer.js [--setup ] [--destructive] [--force] -// -// Exit 0 = invariants hold. Exit 1 = a real installer/install problem. +// Verifies the Windows NSIS installer: default safely observes an existing install (dir, an uninstaller that exists, shortcuts); --destructive runs install->verify->uninstall but refuses to clobber an existing install unless --force (clean machine / CI). 'use strict'; const fs = require('fs'); @@ -47,8 +30,7 @@ function defaultSetup() { return exists(p) ? p : null; } -// The shipped installer must be a real, complete PE - not a 0-byte stub or a -// truncated upload (a real CI failure mode). +// The shipped installer must be a real, complete PE (catch a 0-byte stub or truncated upload). function checkSetupArtifact(setup) { process.stdout.write(`Setup.exe: ${setup}\n`); if (!exists(setup)) { bad(`Setup.exe missing at ${setup}`); return; } @@ -84,8 +66,7 @@ function findShortcuts() { return found; } -// Assert the on-disk + registry state a correct install produces. Used both to -// observe an existing install (safe) and to verify a fresh one (destructive). +// Assert the on-disk + registry state a correct install produces (used by both observe and destructive). function assertInstalled() { if (!exists(INSTALL_DIR)) { bad(`install dir missing: ${INSTALL_DIR}`); return; } ok(`install dir present: ${INSTALL_DIR}`); @@ -97,8 +78,7 @@ function assertInstalled() { else { ok(`uninstall registry entry: ${reg.split('|')[0]}`); const up = uninstallerPathFromReg(reg); - // A registered uninstaller whose target does not exist is an orphaned entry - - // a real, user-visible "can't uninstall" bug. Assert the target actually exists. + // A registered uninstaller whose target is missing is an orphaned "can't uninstall" bug, so assert it exists. if (!up) bad('uninstall entry has no parseable uninstaller path'); else if (!exists(up)) bad(`uninstaller registered but missing on disk: ${up}`); else ok(`uninstaller exists: ${up}`); diff --git a/scripts/ci/verify-network.js b/scripts/ci/verify-network.js index b40aca82..49fa9ca5 100644 --- a/scripts/ci/verify-network.js +++ b/scripts/ci/verify-network.js @@ -1,22 +1,5 @@ #!/usr/bin/env node -// Exercises the network/auth plumbing the app depends on, using the SAME bearer -// token and ports the running app uses (no mocks). Splits into two tiers: -// -// LOCAL (always hard-asserted, deterministic): -// - auth.token exists on disk (the load-bearing pre-bind handshake, auth.py) -// - an authed endpoint rejects with no token (401/403) and accepts with it -// (proves the bearer wiring, not just that a port is open) -// - the bundled 9router subprocess is listening on :20128 (LLM routing + the -// OAuth authorize/exchange path both go through it) -// -// EXTERNAL (best-effort; --strict makes them hard): -// - api.openswarm.com reachable (the fly-hosted proxy OAuth + sign-in use) -// - the GitHub Releases feed reachable (what the auto-updater reads) -// -// Completing a real OAuth flow is inherently interactive (a browser), so it lives -// in the GUI walkthrough; here we prove its DEPENDENCIES are reachable. -// -// node scripts/ci/verify-network.js [--app ] [--strict] [--timeout-ms 120000] +// Exercises auth/network with the app's real bearer token: no-token and wrong-token rejected (401), real token 200, 9router on :20128. External reachability is best-effort unless --strict. 'use strict'; const fs = require('fs'); @@ -84,9 +67,7 @@ async function main() { const authedPath = '/api/settings/default-system-prompt'; const noTok = await httpStatus(port, authedPath, {}); const withTok = await httpStatus(port, authedPath, { Authorization: `Bearer ${token}` }); - // Wrong-token MUST be rejected too. Without this, a backend that accepts ANY - // Authorization header would pass no-token=401 + with-token=200 while auth is - // actually broken. This probe is what makes the 200 mean "validated". + // Wrong-token must also be rejected: else a backend accepting ANY Authorization header passes no-token+real-token while auth is broken. This makes the 200 mean "validated". const badTok = await httpStatus(port, authedPath, { Authorization: 'Bearer not-a-real-token-deadbeefcafe' }); if (![401, 403].includes(noTok)) failures.push(`authed endpoint returned ${noTok} WITHOUT token (expected 401/403)`); if ([401, 403, 0].includes(withTok)) failures.push(`authed endpoint returned ${withTok} WITH the real token (expected it honored)`); diff --git a/scripts/ci/verify-packaged-app.js b/scripts/ci/verify-packaged-app.js index 7a051757..77fff1ce 100644 --- a/scripts/ci/verify-packaged-app.js +++ b/scripts/ci/verify-packaged-app.js @@ -1,20 +1,5 @@ #!/usr/bin/env node -// Deterministic "does the packaged app actually boot and serve" check — a plain -// script, no browser/Electron automation (which is flaky: single-instance locks, -// target-closed races). It launches the REAL built exe/app, waits for the backend, -// and reads the same backend.log the shipped app writes to confirm the boot: -// -// - [provenance] line present and its sha == git rev-parse HEAD (right build) -// - [perf] app-launch < first-paint < backend-http-ready (UI painted, ordered) -// - the backend answers /api/health/check with 200 (it actually serves) -// -// first-paint coming from the log means we prove the renderer painted WITHOUT -// scraping the DOM. Reserve Playwright for genuine GUI-click regressions; this -// covers "did the artifact boot and serve" far more robustly. -// -// node scripts/ci/verify-packaged-app.js [--app ] [--timeout-ms 180000] -// -// Exit 0 = all good. Exit 1 = something didn't boot/serve/match (prints why). +// Launches the built app and reads backend.log: provenance sha == git HEAD, perf marks ordered (proves first paint), health 200. No browser automation; Playwright is for GUI clicks. 'use strict'; const h = require('./lib/app-harness'); diff --git a/scripts/ci/verify-resilience.js b/scripts/ci/verify-resilience.js index f19e2dcc..4faa881b 100644 --- a/scripts/ci/verify-resilience.js +++ b/scripts/ci/verify-resilience.js @@ -1,20 +1,5 @@ #!/usr/bin/env node -// Proves the packaged app survives the two machine-variance cases that most often -// cause "worked on mine, broke on theirs" — both already handled in main.js, but -// never verified: -// -// --locked-port: something else already holds the preferred backend port. -// We bind 8324.. on 127.0.0.1, launch, and assert the app -// still comes up on a DIFFERENT port and serves 200 -// (pickBackendPort fallback, main.js:551). -// --multi-instance: the user double-clicks / a deep link relaunches the app. -// We launch one instance, then a second; the second must exit -// cleanly (single-instance lock at main.js:117) while the -// first keeps serving. -// -// node scripts/ci/verify-resilience.js [--app ] [--locked-port] [--multi-instance] -// -// With no check flag, runs both. Exit 0 = all ran passed; exit 1 = a failure. +// Proves the app survives a taken preferred port (--locked-port falls back, still serves 200) and a second launch (--multi-instance exits cleanly, first keeps serving). No flag runs both. 'use strict'; const net = require('net'); diff --git a/scripts/ci/verify-signature.js b/scripts/ci/verify-signature.js index 8e2fb4ce..d58638c7 100644 --- a/scripts/ci/verify-signature.js +++ b/scripts/ci/verify-signature.js @@ -1,17 +1,5 @@ #!/usr/bin/env node -// Reports — and, with --require-signed, ENFORCES — the code-signing state of a -// built artifact. Local dev builds are intentionally unsigned -// (CSC_IDENTITY_AUTO_DISCOVERY=false), so by default this only REPORTS so you -// always know what you're holding. The release workflows pass --require-signed -// AFTER Azure/Apple have signed, turning it into the SmartScreen/Gatekeeper gate: -// -// Windows: Get-AuthenticodeSignature .Status == Valid (Authenticode -> no SmartScreen block) -// macOS: codesign --verify --deep --strict AND spctl --assess == accepted (Gatekeeper) -// plus a stapled notarization ticket (stapler validate) -// -// node scripts/ci/verify-signature.js [--target ] [--require-signed] -// -// Exit 0 = reported ok (or signed when required). Exit 1 = required but not signed. +// Reports code-signing state; --require-signed fails unless signed (release CI gate, post-sign). Local builds are unsigned by design. Win: Authenticode Valid; mac: codesign + spctl + staple. 'use strict'; const { execSync } = require('child_process');