@mostra/verify 0.0.0-placeholder → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-DCmzgY2w.js","names":["html","select"],"sources":["../src/recorder.ts","../src/observe.ts","../src/network.ts","../src/smoke.ts","../src/runner.ts"],"sourcesContent":["/// <reference lib=\"dom\" />\n/**\n * The recorder every frame of a verify render starts with (Playwright's init script): it keeps the\n * page's own console errors and warnings, uncaught errors and unhandled rejections, each with the\n * diagnostic codes along its `cause` chain, so the runner can tell the artifact's faults from those\n * verify's synthetic refusal caused (D138 item 2). An unhandled rejection whose reason is that\n * refusal is dropped, as the browser would never meet it for a viewer.\n *\n * It runs in the page, so it is one self-contained function: nothing from this module's scope.\n */\n\n/** One thing the page reported. */\nexport interface RecordedEntry {\n /** Its place in the page's order of entries, from 0. */\n readonly seq: number;\n /**\n * `error` and `warn` (the console), `uncaught` (an error event), `rejection`, or `caught`\n * (an error a React error boundary caught, reported by the page's root).\n */\n readonly kind: \"error\" | \"warn\" | \"uncaught\" | \"rejection\" | \"caught\";\n /** The message, or the error's name and message, as the page gave it. */\n readonly text: string;\n /** The `code`s of the value and its `cause` chain: `MostraError`s carry one. */\n readonly codes: readonly string[];\n /** Set on a rejection the recorder kept from the browser: verify's own refusal. */\n readonly dropped?: true;\n}\n\n/** The global the recorder keeps its entries on. */\nexport const RECORDER_GLOBAL = \"__mostraVerifyLog\";\n\n/** The recorder, as Playwright installs it in every frame before the page's scripts run. */\nexport function recorder(): void {\n const scope = globalThis as unknown as Record<string, unknown>;\n if (scope[\"__mostraVerifyLog\"]) return;\n const entries: unknown[] = [];\n let seq = 0;\n const codesOf = (value: unknown): string[] => {\n const codes: string[] = [];\n let current = value as { code?: unknown; cause?: unknown } | null | undefined;\n for (let depth = 0; depth < 8 && current && typeof current === \"object\"; depth += 1) {\n if (typeof current.code === \"string\") codes.push(current.code);\n current = current.cause as typeof current;\n }\n return codes;\n };\n const describe = (value: unknown): string => {\n if (value instanceof Error) return value.stack ?? `${value.name}: ${value.message}`;\n if (typeof value === \"string\") return value;\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n };\n const record = (entry: Record<string, unknown>): void => {\n if (entries.length < 500) entries.push({ seq: ++seq, ...entry });\n };\n for (const level of [\"error\", \"warn\"] as const) {\n const original = console[level].bind(console);\n console[level] = (...args: unknown[]) => {\n record({\n kind: level,\n text: args.map(describe).join(\" \").slice(0, 2000),\n codes: args.flatMap(codesOf),\n });\n original(...args);\n };\n }\n addEventListener(\"error\", (event) => {\n const error = (event as ErrorEvent).error as unknown;\n record({\n kind: \"uncaught\",\n text: (error === undefined ? (event as ErrorEvent).message : describe(error)).slice(0, 2000),\n codes: codesOf(error),\n });\n });\n addEventListener(\"unhandledrejection\", (event) => {\n const reason = (event as PromiseRejectionEvent).reason as unknown;\n const codes = codesOf(reason);\n const dropped = codes.includes(\"MOSTRA_VERIFY_ACTION_SKIPPED\");\n if (dropped) event.preventDefault();\n record({\n kind: \"rejection\",\n text: describe(reason).slice(0, 2000),\n codes,\n ...(dropped ? { dropped: true } : {}),\n });\n });\n // A page in the verify target reports what its error boundaries caught here (VERIFY_REPORT in\n // @mostra/protocol; the recorder runs in the page, so the name is written out).\n Object.defineProperty(scope, \"__mostraVerifyReport\", {\n value: (error: unknown) =>\n record({ kind: \"caught\", text: describe(error).slice(0, 2000), codes: codesOf(error) }),\n });\n Object.defineProperty(scope, \"__mostraVerifyLog\", { value: entries });\n}\n","/// <reference lib=\"dom\" />\n/**\n * How the runner observes a rendered page: the quiescence wait, the recorder's entries, and the\n * judgement of what the page reported (D72, D138 items 2 and 3).\n */\nimport { createDiagnostic, type Diagnostic, VERIFY_PROBE } from \"@mostra/protocol\";\nimport type { Frame } from \"playwright-core\";\nimport type { NetworkWatch } from \"./network.ts\";\nimport { RECORDER_GLOBAL, type RecordedEntry } from \"./recorder.ts\";\n\n/**\n * Waits until the frame's document has had no mutation for 200 ms, no request is in flight (the\n * page's event stream aside) and the client runtime waits on no server work (calls, runs, first\n * live values: the probe's `pending`, whose answers arrive on the event stream), at most `maxMs`,\n * or `serverMaxMs` while server work is still pending. A quiescence wait, never a fixed sleep: it\n * returns as soon as the page is still (EVAL-01b; Opus review finding 1).\n */\nexport async function settle(\n frame: Frame,\n network: NetworkWatch,\n deadline: number,\n maxMs = 3000,\n serverMaxMs = 10_000,\n): Promise<boolean> {\n const start = Date.now();\n const end = (pending: boolean): number =>\n Math.min(start + (pending ? Math.max(maxMs, serverMaxMs) : maxMs), deadline);\n try {\n await frame.evaluate(() => {\n const scope = globalThis as unknown as { __mostraQuiet?: () => number };\n if (scope.__mostraQuiet) return;\n let last = performance.now();\n new MutationObserver(() => {\n last = performance.now();\n }).observe(document, {\n subtree: true,\n childList: true,\n attributes: true,\n characterData: true,\n });\n scope.__mostraQuiet = () => performance.now() - last;\n });\n for (;;) {\n const state = await frame.evaluate((probe) => {\n const scope = globalThis as unknown as Record<string, unknown>;\n const quiet = (scope[\"__mostraQuiet\"] as () => number)();\n const pending = (scope[probe] as { pending?: () => number } | undefined)?.pending?.() ?? 0;\n return { quiet, pending };\n }, VERIFY_PROBE);\n if (state.quiet >= 200 && network.inflight() === 0 && state.pending === 0) return true;\n if (Date.now() >= end(state.pending > 0)) return false;\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n } catch {\n // The frame navigated or closed: nothing more to wait for.\n }\n return false;\n}\n\n/** Folds identical findings into one with a count. */\nexport function dedupe(findings: readonly Diagnostic[]): Diagnostic[] {\n const seen = new Map<string, Diagnostic>();\n for (const finding of findings) {\n const key = `${finding.code}\\u0000${finding.message}`;\n const known = seen.get(key);\n if (known) seen.set(key, { ...known, count: (known.count ?? 1) + (finding.count ?? 1) });\n else seen.set(key, finding);\n }\n return [...seen.values()];\n}\n\n/** The recorder's entries in a frame from `from` on. */\nexport async function recorded(frame: Frame, from = 0): Promise<RecordedEntry[]> {\n try {\n return (await frame.evaluate(\n ([name, start]) =>\n (\n ((globalThis as unknown as Record<string, unknown>)[name] as unknown[] | undefined) ?? []\n ).slice(start),\n [RECORDER_GLOBAL, from] as const,\n )) as RecordedEntry[];\n } catch {\n return [];\n }\n}\n\n/** Where a finding happened, for its message. */\nexport type Moment =\n | { readonly kind: \"load\" }\n | { readonly kind: \"interaction\"; readonly control: string };\n\nconst momentText = (moment: Moment): string =>\n moment.kind === \"load\" ? \"while the page loaded\" : `after the smoke test used ${moment.control}`;\n\n/**\n * Judges what the page reported in one moment (D138 items 2 and 3): its own faults are errors,\n * console warnings hints; a fault whose `cause` chain is verify's refusal, and any console error\n * in a moment when verify refused an action, is a hint naming the action; a missing secret is\n * the environment's, a warning.\n */\nexport function judge(\n entries: readonly RecordedEntry[],\n moment: Moment,\n skipped: readonly string[],\n viewport: string,\n): Diagnostic[] {\n const findings: Diagnostic[] = [];\n const location = { viewport };\n for (const entry of entries) {\n if (entry.dropped) continue;\n const text = entry.text.split(\"\\n\").slice(0, 6).join(\"\\n\");\n const ownSkip = entry.codes.includes(\"MOSTRA_VERIFY_ACTION_SKIPPED\");\n const action = skipped.length > 0 ? skipped.join(\", \") : \"an action\";\n // By its cause chain always; by the window (a console error after a click that produced a\n // skip) only during an interaction, never for the whole load (Opus review finding 3).\n const inWindow = moment.kind === \"interaction\" && entry.kind === \"error\" && skipped.length > 0;\n if (ownSkip || inWindow) {\n findings.push(\n createDiagnostic(\"MOSTRA_VERIFY_SKIP_CONSEQUENCE\", {\n message: `After verify skipped ${action} ${momentText(moment)}, the page reported: ${text}`,\n location,\n }),\n );\n continue;\n }\n if (entry.codes.includes(\"MOSTRA_SECRET_MISSING\")) {\n findings.push(\n createDiagnostic(\"MOSTRA_VERIFY_SECRET_MISSING\", {\n message: `Server code needed a secret ${momentText(moment)}: ${text}`,\n location,\n }),\n );\n continue;\n }\n if (entry.kind === \"warn\") {\n findings.push(\n createDiagnostic(\"MOSTRA_CONSOLE_WARNING\", {\n message: `The page logged a warning ${momentText(moment)}: ${text}`,\n location,\n }),\n );\n } else if (entry.kind === \"error\") {\n findings.push(\n createDiagnostic(\"MOSTRA_CONSOLE_ERROR\", {\n message: `The page logged an error ${momentText(moment)}: ${text}`,\n location,\n }),\n );\n } else {\n findings.push(\n createDiagnostic(\"MOSTRA_RUNTIME_PAGE_ERROR\", {\n message: `${entry.kind === \"rejection\" ? \"A promise rejected with nothing to handle it\" : entry.kind === \"caught\" ? \"An error boundary caught an error\" : \"An error was thrown and nothing caught it\"} ${momentText(moment)}: ${text}`,\n location,\n }),\n );\n }\n }\n return findings;\n}\n","/**\n * What a verify render's page does on the network (D138 item 3): requests in flight (for the\n * quiescence wait), failed requests for resources the artifact's frame loaded, the sandbox's\n * refusals of actions that didn't opt into `dryRun()`, and the field writes the page made.\n *\n * A failed request counts when the artifact's frame loaded it: its document, scripts, styles,\n * images, fonts and fetches. Requests the browser makes on its own (`/favicon.ico`) and Mostra's\n * protocol (`/_mostra/…`, whose answers the client runtime handles) don't. Chrome's own\n * \"Failed to load resource\" console line is never counted: the request's finding stands for it.\n */\nimport { createDiagnostic, type Diagnostic } from \"@mostra/protocol\";\nimport type { Frame, Page, Request, Response } from \"playwright-core\";\n\n/** The network of one render. */\nexport interface NetworkWatch {\n /** Requests in flight, the page's event stream aside. */\n inflight(): number;\n /** Server functions the sandbox refused since mark `from` (see {@link NetworkWatch.mark}). */\n skippedSince(from: number): string[];\n /** A mark for {@link NetworkWatch.skippedSince} and {@link NetworkWatch.writesSince}. */\n mark(): number;\n /** Field ops the gateway applied since a mark. */\n writesSince(from: number): number;\n /**\n * Requests the artifact's frame made since a mark, its event stream aside: Mostra's protocol\n * (fields, calls, runs, signals, submits) and the page's own fetches alike, each an effect.\n */\n requestsSince(from: number): number;\n /** The failed-request findings so far. */\n findings(): Diagnostic[];\n}\n\n/** Whether a request is the page's event stream, which stays open. */\nconst isStream = (url: URL): boolean => /\\/_mostra\\/v\\/[^/]+\\/events(\\/poll)?$/.test(url.pathname);\n\n/** Watches a page's network; `frame` finds the artifact's frame. */\nexport function watchNetwork(\n page: Page,\n frame: () => Frame | undefined,\n viewport: string,\n): NetworkWatch {\n const open = new Set<Request>();\n /** Events in order: skips, writes and protocol requests, each at its position. */\n const log: (\n | { kind: \"skip\"; fn: string }\n | { kind: \"write\"; ops: number }\n | { kind: \"protocol\" }\n | { kind: \"request\" }\n )[] = [];\n const failures = new Map<string, Diagnostic>();\n /** Protocol answers still being read: they count as in flight until their body is in. */\n let reading = 0;\n\n const fromArtifact = (request: Request): boolean => {\n try {\n const own = frame();\n return own !== undefined && request.frame() === own;\n } catch {\n return false; // A service worker's request has no frame.\n }\n };\n\n const counted = (url: URL, artifactOrigin: string | undefined): boolean => {\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n if (url.origin === artifactOrigin && url.pathname.startsWith(\"/_mostra/\")) return false;\n return url.pathname !== \"/favicon.ico\";\n };\n\n const fail = (request: Request, why: string): void => {\n if (!fromArtifact(request)) return;\n const url = new URL(request.url());\n let artifactOrigin: string | undefined;\n try {\n artifactOrigin = new URL(frame()?.url() ?? \"\").origin;\n } catch {\n artifactOrigin = undefined;\n }\n if (!counted(url, artifactOrigin)) return;\n const own = url.origin === artifactOrigin;\n const shown = own ? url.pathname : `${url.origin}${url.pathname}`;\n const key = `${request.method()} ${shown} ${why}`;\n const known = failures.get(key);\n if (known) {\n failures.set(key, { ...known, count: (known.count ?? 1) + 1 });\n return;\n }\n failures.set(\n key,\n createDiagnostic(own ? \"MOSTRA_NETWORK_REQUEST_FAILED\" : \"MOSTRA_NETWORK_EXTERNAL_FAILED\", {\n message: `${request.resourceType()} ${request.method()} ${shown} ${why}.`,\n location: { viewport },\n }),\n );\n };\n\n page.on(\"request\", (request) => {\n const url = new URL(request.url());\n if (isStream(url)) return;\n open.add(request);\n // Any request the page's own code makes is something a control did (GPT review finding 4).\n if (fromArtifact(request) && !url.pathname.startsWith(\"/_mostra/\"))\n log.push({ kind: \"request\" });\n });\n const done = (request: Request): void => {\n open.delete(request);\n };\n page.on(\"requestfinished\", done);\n page.on(\"requestfailed\", (request) => {\n done(request);\n const text = request.failure()?.errorText ?? \"failed\";\n // A request the page itself cancelled (an aborted fetch, a navigation away) didn't fail.\n if (/ERR_ABORTED|NS_BINDING_ABORTED|cancelled/i.test(text)) return;\n fail(request, `failed (${text})`);\n });\n page.on(\"response\", (response: Response) => {\n const request = response.request();\n const url = new URL(request.url());\n const status = response.status();\n if (url.pathname.includes(\"/_mostra/v/\")) {\n reading += 1;\n void onProtocol(request, url, response).finally(() => {\n reading -= 1;\n });\n return;\n }\n if (status >= 400) fail(request, `answered ${status}`);\n });\n\n const onProtocol = async (request: Request, url: URL, response: Response): Promise<void> => {\n if (request.method() !== \"POST\" || isStream(url)) return;\n const route = /\\/_mostra\\/v\\/[^/]+\\/(fields|call\\/.+|runs|signal|submit)$/.exec(\n url.pathname,\n )?.[1];\n if (!route) return;\n log.push({ kind: \"protocol\" });\n if (route === \"fields\" && response.ok()) {\n const body = (await response.json().catch(() => ({}))) as { applied?: unknown[] };\n log.push({ kind: \"write\", ops: Array.isArray(body.applied) ? body.applied.length : 1 });\n return;\n }\n if (response.status() !== 409 || !(route === \"runs\" || route.startsWith(\"call/\"))) return;\n const body = (await response.json().catch(() => ({}))) as { code?: unknown };\n if (body.code !== \"MOSTRA_VERIFY_ACTION_SKIPPED\") return;\n const fn = route.startsWith(\"call/\")\n ? decodeURIComponent(route.slice(\"call/\".length))\n : String((request.postDataJSON() as { fnId?: unknown } | null)?.fnId ?? \"an action\");\n log.push({ kind: \"skip\", fn });\n };\n\n return {\n inflight: () => open.size + reading,\n mark: () => log.length,\n skippedSince: (from) => [\n ...new Set(log.slice(from).flatMap((entry) => (entry.kind === \"skip\" ? [entry.fn] : []))),\n ],\n writesSince: (from) =>\n log.slice(from).reduce((sum, entry) => sum + (entry.kind === \"write\" ? entry.ops : 0), 0),\n requestsSince: (from) => log.slice(from).filter((entry) => entry.kind !== \"write\").length,\n findings: () => [...failures.values()],\n };\n}\n","/// <reference lib=\"dom\" />\n/**\n * The smoke test (ROUND-3 §9, D55, D72, D138): uses every enabled control of the rendered page\n * once, within 200 interactions or 60 s, and reports what it observed.\n *\n * - Buttons, tabs, menus and other triggers are clicked; tooltip and hover-card triggers hovered;\n * context-menu triggers right-clicked. Disabled controls and already-active ones (the selected\n * tab, the current page) are left alone, as EVAL-01's calibration showed they rightly do nothing.\n * - Inputs, text areas, selects, checkboxes and radios are filled or toggled with values that fit\n * their type, then blurred so the page saves; one whose value saves nothing is unbound (a hint).\n * - Links to other origins, `mailto:`, downloads and links that open a new tab aren't followed.\n * - A control with no observable effect (no change to the page, no request, no saved field, no\n * navigation) is a warning that says what was observed, never an error (D72).\n * - Actions that didn't opt into `dryRun()` are refused by the sandbox; faults that follow from\n * the refusal are attributed to it (see `judge`).\n */\nimport { createDiagnostic, type Diagnostic } from \"@mostra/protocol\";\nimport type { Frame, Locator, Page } from \"playwright-core\";\nimport type { NetworkWatch } from \"./network.ts\";\nimport { judge, recorded, settle } from \"./observe.ts\";\n\n/** One control the smoke test found. */\ninterface Control {\n readonly index: number;\n readonly tag: string;\n readonly role: string;\n readonly type: string;\n readonly name: string;\n /** How it is used. */\n readonly use: \"click\" | \"hover\" | \"context\" | \"fill\" | \"toggle\" | \"select\" | \"skip\";\n}\n\n/** Options for {@link runSmoke}. */\nexport interface SmokeOptions {\n readonly page: Page;\n readonly frame: Frame;\n readonly network: NetworkWatch;\n readonly viewport: string;\n /** The recorder's entries already judged. */\n readonly from: number;\n readonly maxInteractions: number;\n readonly deadline: number;\n}\n\n/** What the smoke test found. */\nexport interface SmokeOutcome {\n readonly findings: Diagnostic[];\n readonly interactions: number;\n readonly stateChanges: number;\n readonly budget: boolean;\n}\n\nconst MARK = \"data-mostra-smoke\";\n/** How many levels of revealed controls the smoke test follows: a dialog, then its menu. */\nconst REVEAL_DEPTH = 2;\n\n/**\n * Finds the page's controls not marked yet and marks each with its index, numbering from `start`\n * (runs in the frame): the page's controls at first, then those an interaction revealed.\n */\nfunction findControls([mark, start]: readonly [string, number]): Control[] {\n const selector = [\n \"button\",\n \"[role=button]\",\n \"a[href]\",\n \"input:not([type=hidden])\",\n \"textarea\",\n \"select\",\n \"summary\",\n \"[role=tab]\",\n \"[role=checkbox]\",\n \"[role=switch]\",\n \"[role=radio]\",\n \"[role=menuitem]\",\n \"[role=combobox]\",\n \"[role=option]\",\n \"[aria-haspopup]\",\n '[data-slot$=\"-trigger\"]',\n '[contenteditable=\"true\"]',\n '[contenteditable=\"\"]',\n ].join(\",\");\n const controls: Control[] = [];\n const seen = new Set<Element>();\n for (const element of document.querySelectorAll(selector)) {\n if (seen.has(element) || controls.length >= 400 || element.hasAttribute(mark)) continue;\n seen.add(element);\n const html = element as HTMLElement;\n const tag = element.tagName.toLowerCase();\n const type = (element.getAttribute(\"type\") ?? \"\").toLowerCase();\n const role = element.getAttribute(\"role\") ?? \"\";\n const slot = element.getAttribute(\"data-slot\") ?? \"\";\n const name = (\n element.getAttribute(\"aria-label\") ??\n ((html as HTMLInputElement).labels?.[0]?.textContent || undefined) ??\n element.getAttribute(\"placeholder\") ??\n element.getAttribute(\"title\") ??\n html.innerText ??\n \"\"\n )\n .trim()\n .replace(/\\s+/g, \" \")\n .slice(0, 60);\n let use: Control[\"use\"] = \"click\";\n if (tag === \"a\") {\n const anchor = element as HTMLAnchorElement;\n let url: URL | undefined;\n try {\n url = new URL(anchor.href, location.href);\n } catch {\n url = undefined;\n }\n const leaves =\n !url ||\n url.origin !== location.origin ||\n anchor.target === \"_blank\" ||\n anchor.hasAttribute(\"download\");\n if (leaves) use = \"skip\";\n } else if (slot === \"tooltip-trigger\" || slot === \"hover-card-trigger\") use = \"hover\";\n else if (slot === \"context-menu-trigger\") use = \"context\";\n else if (tag === \"select\") use = \"select\";\n else if (tag === \"textarea\" || html.isContentEditable) use = \"fill\";\n else if (tag === \"input\") {\n if (type === \"checkbox\" || type === \"radio\") use = \"toggle\";\n else if ([\"submit\", \"button\", \"reset\", \"image\"].includes(type)) use = \"click\";\n else if (type === \"file\") use = \"skip\";\n else use = \"fill\";\n }\n const index = start + controls.length;\n element.setAttribute(mark, String(index));\n controls.push({ index, tag, role, type, name, use });\n }\n return controls;\n}\n\n/** Whether a control is disabled or already active, read live (runs in the frame). */\nfunction stateOf(element: Element): { readonly disabled: boolean; readonly active: boolean } {\n const html = element as HTMLButtonElement;\n const disabled =\n html.disabled === true ||\n element.getAttribute(\"aria-disabled\") === \"true\" ||\n element.closest(\"fieldset[disabled]\") !== null;\n const current = element.getAttribute(\"aria-current\");\n const active =\n element.getAttribute(\"aria-selected\") === \"true\" ||\n (current !== null && current !== \"false\") ||\n element.getAttribute(\"data-state\") === \"active\" ||\n element.hasAttribute(\"data-active\") ||\n element.hasAttribute(\"data-selected\");\n return { disabled, active };\n}\n\n/** The attributes that show a control's state, as one string (runs in the frame). */\nfunction attributesOf(element: Element): string {\n return [\n \"aria-expanded\",\n \"aria-checked\",\n \"aria-selected\",\n \"aria-pressed\",\n \"data-state\",\n \"data-checked\",\n \"data-open\",\n \"data-popup-open\",\n \"data-pressed\",\n \"data-panel-open\",\n \"open\",\n ]\n .map((name) => `${name}=${element.getAttribute(name)}`)\n .join(\"|\");\n}\n\n/** A value that fits an input's type. */\nfunction valueFor(type: string): string {\n const values: Record<string, string> = {\n email: \"verify@example.com\",\n number: \"7\",\n range: \"7\",\n date: \"2026-01-15\",\n time: \"12:30\",\n \"datetime-local\": \"2026-01-15T12:30\",\n month: \"2026-01\",\n week: \"2026-W03\",\n url: \"https://example.com/\",\n tel: \"5550100\",\n color: \"#336699\",\n password: \"verify-pass\",\n };\n return values[type] ?? \"Verify\";\n}\n\nconst describe = (control: Control): string => {\n const kind =\n control.role || (control.tag === \"input\" ? `${control.type || \"text\"} input` : control.tag);\n return control.name ? `${kind} \"${control.name}\"` : kind;\n};\n\n/** Counts the frame document's mutations from now on (runs in the frame). */\nfunction countMutations(): void {\n const scope = globalThis as unknown as { __mostraSmoke?: { count: number } };\n if (scope.__mostraSmoke) return;\n const state = { count: 0 };\n scope.__mostraSmoke = state;\n new MutationObserver((records) => {\n state.count += records.length;\n }).observe(document, { subtree: true, childList: true, attributes: true, characterData: true });\n}\n\nconst mutations = (frame: Frame): Promise<number> =>\n frame\n .evaluate(\n () =>\n (globalThis as unknown as { __mostraSmoke?: { count: number } }).__mostraSmoke?.count ?? -1,\n )\n .catch(() => -1);\n\n/** What one use of a control showed. */\ninterface Use {\n readonly used: boolean;\n readonly changed: boolean;\n readonly writes: number;\n readonly navigated: boolean;\n readonly newDocument: boolean;\n readonly mark: number;\n}\n\n/** Runs the smoke test on a rendered page. */\nexport async function runSmoke(options: SmokeOptions): Promise<SmokeOutcome> {\n const { page, frame, network, viewport } = options;\n const findings: Diagnostic[] = [];\n const startMark = network.mark();\n let seen = options.from;\n let interactions = 0;\n let budget = false;\n let stopped = false;\n await frame.evaluate(countMutations).catch(() => undefined);\n const controls = await frame\n .evaluate(findControls, [MARK, 0] as const)\n .catch(() => [] as Control[]);\n const usable = controls.filter((control) => control.use !== \"skip\");\n let next = controls.length;\n\n /**\n * Uses a control once and judges what the page reported meanwhile. Controls it revealed (a\n * dialog's buttons, a menu's items, a tab's panel) are used too, before anything is dismissed,\n * down to {@link REVEAL_DEPTH} levels (GPT review finding 3).\n */\n const exercise = async (control: Control, depth = 0): Promise<Use> => {\n const none: Use = {\n used: false,\n changed: false,\n writes: 0,\n navigated: false,\n newDocument: false,\n mark: network.mark(),\n };\n if (frame.isDetached()) return none;\n const target: Locator = frame.locator(`[${MARK}=\"${control.index}\"]`);\n if (!(await target.isVisible().catch(() => false))) return none;\n const state = await target.evaluate(stateOf).catch(() => ({ disabled: true, active: false }));\n if (state.disabled || state.active) return none;\n const mark = network.mark();\n const beforeMutations = await mutations(frame);\n const beforeAttributes = await target.evaluate(attributesOf).catch(() => \"\");\n const beforeUrl = frame.url();\n try {\n const timeout = Math.max(500, Math.min(2000, options.deadline - Date.now()));\n if (control.use === \"hover\") await target.hover({ timeout });\n else if (control.use === \"context\") await target.click({ button: \"right\", timeout });\n else if (control.use === \"fill\") {\n await target.fill(valueFor(control.type), { timeout });\n await target.press(\"Tab\", { timeout });\n } else if (control.use === \"toggle\") {\n await target.click({ timeout });\n await target.press(\"Tab\", { timeout }).catch(() => undefined);\n } else if (control.use === \"select\") {\n const option = await target.evaluate((element) => {\n const select = element as HTMLSelectElement;\n return [...select.options].find((entry) => !entry.selected && !entry.disabled)?.value;\n });\n if (option === undefined) return none;\n await target.selectOption(option, { timeout });\n await target.press(\"Tab\", { timeout }).catch(() => undefined);\n } else await target.click({ timeout });\n } catch {\n return none; // Covered, moving or gone: not a control the smoke test could use.\n }\n interactions += 1;\n await settle(frame, network, options.deadline, 2000, 5000);\n const navigated = frame.url() !== beforeUrl;\n const afterMutations = await mutations(frame);\n const newDocument = afterMutations === -1;\n const afterAttributes = newDocument\n ? beforeAttributes\n : await target.evaluate(attributesOf).catch(() => beforeAttributes);\n // Whatever the page reported meanwhile (an error) is an effect too: judged as itself.\n const reported = (await recorded(frame, seen)).length > 0;\n const changed =\n navigated ||\n newDocument ||\n reported ||\n network.requestsSince(mark) > 0 ||\n afterAttributes !== beforeAttributes ||\n (afterMutations > beforeMutations && beforeMutations >= 0);\n const moment = { kind: \"interaction\" as const, control: describe(control) };\n if (newDocument) {\n // The frame loaded a new document: its recorder started again, and the controls found on\n // the old one are gone.\n findings.push(...judge(await recorded(frame), moment, network.skippedSince(mark), viewport));\n stopped = true;\n } else {\n const entries = await recorded(frame, seen);\n seen += entries.length;\n findings.push(...judge(entries, moment, network.skippedSince(mark), viewport));\n if (depth < REVEAL_DEPTH) await exerciseRevealed(depth);\n if (depth === 0) {\n await page.keyboard.press(\"Escape\").catch(() => undefined);\n await page.mouse.move(0, 0).catch(() => undefined);\n }\n }\n return { used: true, changed, writes: network.writesSince(mark), navigated, newDocument, mark };\n };\n\n const within = (): boolean =>\n !stopped && interactions < options.maxInteractions && Date.now() < options.deadline;\n\n /**\n * Uses the controls the last interaction revealed. They are judged for what the page reports\n * and for inputs that save nothing, never for having no effect: a second try would need their\n * container opened again, and silence is the conservative answer (D72).\n */\n const exerciseRevealed = async (depth: number): Promise<void> => {\n if (!within() || frame.isDetached()) return;\n const revealed = await frame\n .evaluate(findControls, [MARK, next] as const)\n .catch(() => [] as Control[]);\n next += revealed.length;\n for (const control of revealed) {\n if (!within()) break;\n if (control.use === \"skip\" || control.tag === \"a\") continue;\n const outcome = await exercise(control, depth + 1);\n if (!outcome.used) continue;\n unbound(control, outcome);\n }\n };\n\n /** Reports an input the smoke test changed whose value saved nothing (a hint). */\n const unbound = (control: Control, outcome: Use): void => {\n if (control.use !== \"fill\" && control.use !== \"toggle\" && control.use !== \"select\") return;\n if (outcome.writes > 0 || outcome.navigated || outcome.newDocument) return;\n findings.push(\n createDiagnostic(\"MOSTRA_FIELD_UNBOUND\", {\n message: `The smoke test changed ${describe(control)} and the page saved nothing: the input isn't bound to a field.`,\n location: { viewport, selector: `[${MARK}=\"${control.index}\"]` },\n }),\n );\n };\n /** Controls that changed nothing the first time: tried again once the others were used. */\n const again: Control[] = [];\n let used = 0;\n // Links go last: following one may load a new document, which ends the test.\n const links = usable.filter((control) => control.tag === \"a\");\n for (const control of usable.filter((entry) => entry.tag !== \"a\")) {\n if (!within()) break;\n used += 1;\n const outcome = await exercise(control);\n if (!outcome.used) continue;\n if (control.use === \"fill\" || control.use === \"toggle\" || control.use === \"select\") {\n unbound(control, outcome);\n } else if (!outcome.changed) again.push(control);\n }\n // A control can rightly do nothing the first time: the option already chosen, the filter\n // already applied. After the others changed the page, it gets a second try; only one that\n // changes nothing either time is reported (D72: observations, never errors).\n for (const control of again) {\n if (!within()) break;\n const outcome = await exercise(control);\n if (!outcome.used || outcome.changed) continue;\n findings.push(\n createDiagnostic(\"MOSTRA_SMOKE_NO_EFFECT\", {\n message: `Observed: ${control.use === \"hover\" ? \"hovering over\" : \"clicking\"} ${describe(control)} twice changed nothing verify could see (no change to the page, no request, no saved field, no navigation).`,\n location: { viewport, selector: `[${MARK}=\"${control.index}\"]` },\n }),\n );\n }\n // A link to the page already shown rightly changes nothing, so links aren't judged for it.\n for (const control of links) {\n if (!within()) break;\n used += 1;\n await exercise(control);\n }\n budget = !stopped && used < usable.length;\n if (budget) {\n findings.push(\n createDiagnostic(\"MOSTRA_SMOKE_BUDGET\", {\n message: `The smoke test stopped after ${interactions} interactions, at its budget of ${options.maxInteractions} interactions or 60 s; ${usable.length - used} controls weren't used.`,\n location: { viewport },\n }),\n );\n }\n return { findings, interactions, stateChanges: network.writesSince(startMark), budget };\n}\n","/// <reference lib=\"dom\" />\n/**\n * The verify runner (ARCHITECTURE §1, ROUND-3 §9, D138): one warm headless Chromium that renders\n * capability links the supervisor's sandbox minted, each in a fresh browser context, and reports\n * what the page did. It decides what a page's behavior means for the verdict (D72): definite\n * failures are errors, observations warnings or hints, and faults that follow from verify's own\n * refusal of an action are attributed to it. The supervisor decides everything else.\n */\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { join } from \"node:path\";\nimport {\n type CompareRequest,\n type CompareResult,\n createDiagnostic,\n type Diagnostic,\n type ProbedField,\n type RenderRequest,\n type RenderResult,\n RpcError,\n VERIFY_PROBE,\n} from \"@mostra/protocol\";\nimport pixelmatch from \"pixelmatch\";\nimport {\n type Browser,\n type BrowserContext,\n chromium,\n type Frame,\n type Page,\n} from \"playwright-core\";\nimport { PNG } from \"pngjs\";\nimport { watchNetwork } from \"./network.ts\";\nimport { dedupe, judge, recorded, settle } from \"./observe.ts\";\nimport { recorder } from \"./recorder.ts\";\nimport { runSmoke } from \"./smoke.ts\";\n\nconst require = createRequire(import.meta.url);\n\n/** How long the runner keeps its browser without work (ROUND-3 §9): 10 minutes. */\nexport const RUNNER_IDLE_MS: number = 10 * 60 * 1000;\n\n/** The longest a page may take to report `mostra/ready` before it is judged never ready. */\nconst READY_WAIT_MS = 15_000;\n/**\n * How long after its deadline a render is abandoned: its browser context is closed, which ends a\n * page that blocks its own main thread without touching other runs' pages (Opus review finding 5).\n */\nconst RENDER_ABANDON_MS = 1_000;\n/** The time the smoke test leaves before the run's deadline, for the regression stage after it. */\nconst SMOKE_RESERVE_MS = 5_000;\n\n/** Options for {@link createRunner}. */\nexport interface RunnerOptions {\n /** Called when the runner has been idle for `idleMs`: the process exits then. */\n readonly onIdle?: () => void;\n /** How long without work counts as idle; {@link RUNNER_IDLE_MS} by default. */\n readonly idleMs?: number;\n}\n\n/** The runner of one process. */\nexport interface Runner {\n /** Renders one capability link in a fresh browser context and reports what the page did. */\n render(request: RenderRequest): Promise<RenderResult>;\n /** Compares two screenshots and writes the difference. */\n compare(request: CompareRequest): Promise<CompareResult>;\n /** Closes the browser. */\n close(): Promise<void>;\n}\n\n/** Whether a launch failed because the browser isn't installed. */\nfunction browserMissing(error: unknown): boolean {\n return /Executable doesn't exist|browserType\\.launch: .*(not found|ENOENT)/i.test(\n String((error as Error)?.message ?? error),\n );\n}\n\n/** Creates the runner; its browser starts on the first render. */\nexport function createRunner(options: RunnerOptions = {}): Runner {\n let browser: Promise<Browser> | undefined;\n let idleTimer: ReturnType<typeof setTimeout> | undefined;\n let busy = 0;\n let axeSource: string | undefined;\n\n const touch = (): void => {\n if (idleTimer) clearTimeout(idleTimer);\n idleTimer = undefined;\n if (busy > 0 || !options.onIdle) return;\n idleTimer = setTimeout(() => options.onIdle?.(), options.idleMs ?? RUNNER_IDLE_MS);\n idleTimer.unref?.();\n };\n\n const launch = (): Promise<Browser> => {\n browser ??= chromium.launch({ headless: true }).catch((error: unknown) => {\n browser = undefined;\n if (browserMissing(error)) {\n throw new RpcError(\n createDiagnostic(\"MOSTRA_ENV_BROWSER_MISSING\", {\n message:\n \"verify's browser, Playwright's Chromium headless shell, isn't installed for this runtime.\",\n }),\n );\n }\n throw error;\n });\n return browser;\n };\n\n const work = async <T>(task: () => Promise<T>): Promise<T> => {\n busy += 1;\n touch();\n try {\n return await task();\n } finally {\n busy -= 1;\n touch();\n }\n };\n\n return {\n render: (request) =>\n work(async () => {\n axeSource ??= await readFile(require.resolve(\"axe-core/axe.min.js\"), \"utf8\");\n return renderBounded(await launch(), request, axeSource);\n }),\n compare: (request) => work(() => compareImages(request)),\n async close() {\n if (idleTimer) clearTimeout(idleTimer);\n const open = browser;\n browser = undefined;\n if (open) await (await open.catch(() => undefined))?.close();\n },\n };\n}\n\n/** The artifact's frame: the shell's only child frame. */\nfunction artifactFrame(page: Page): Frame | undefined {\n return page.frames().find((frame) => frame.parentFrame() === page.mainFrame());\n}\n\n/** What the frame shows: whether any element has a visible box of nonzero area (D138 item 3). */\nfunction showsSomething(): boolean {\n // Visible through its ancestors too: `checkVisibility` with these options is false when the\n // element has no box (an ancestor is `display: none`), is `visibility: hidden`, or it or any\n // ancestor has opacity 0 (CSSOM View; GPT review finding 5: a wrapper at opacity 0 hides\n // children whose own opacity is 1).\n const visible = (element: Element): boolean => {\n const rect = element.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return false;\n return element.checkVisibility({ opacityProperty: true, visibilityProperty: true });\n };\n for (const element of document.querySelectorAll(\"img, svg, canvas, video\")) {\n if (visible(element)) return true;\n }\n if (!document.body) return false;\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const node = walker.currentNode;\n if (!node.textContent?.trim() || !node.parentElement || !visible(node.parentElement)) continue;\n const range = document.createRange();\n range.selectNodeContents(node);\n const rect = range.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) return true;\n }\n return false;\n}\n\n/** Whether a PNG is one uniform color. */\nexport function uniform(png: Buffer): boolean {\n const image = PNG.sync.read(png);\n const { data } = image;\n for (let offset = 4; offset < data.length; offset += 4) {\n if (\n data[offset] !== data[0] ||\n data[offset + 1] !== data[1] ||\n data[offset + 2] !== data[2] ||\n data[offset + 3] !== data[3]\n )\n return false;\n }\n return true;\n}\n\n/**\n * Waits for `work`, and gives up on it `graceMs` past `deadline`: `abandon()` is called (for a\n * render, closing its context, which ends a page whose code blocks its main thread) and `late` is\n * the answer, also when `work` then fails because of the abandon. A failure before that is\n * `work`'s own and is thrown (Opus review finding 5).\n */\nexport async function bounded<T>(\n work: Promise<T>,\n options: {\n readonly deadline: number;\n readonly late: T;\n readonly abandon: () => Promise<unknown> | undefined;\n readonly graceMs?: number;\n },\n): Promise<T> {\n let abandoning = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const abandoned = new Promise<T>((done) => {\n timer = setTimeout(\n () => {\n abandoning = true;\n const closing = Promise.resolve(options.abandon()).catch(() => undefined);\n void Promise.race([closing, new Promise((wait) => setTimeout(wait, 2000))]).then(() =>\n done(options.late),\n );\n },\n Math.max(0, options.deadline - Date.now()) + (options.graceMs ?? RENDER_ABANDON_MS),\n );\n });\n const guarded = work.catch((error: unknown) => {\n if (abandoning) return options.late;\n throw error;\n });\n try {\n return await Promise.race([guarded, abandoned]);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Renders one capability link, and abandons it when it is still running past its deadline: its\n * context is closed (the page's renderer with it), and the render reports it ran out of time.\n */\nasync function renderBounded(\n browser: Browser,\n request: RenderRequest,\n axeSource: string,\n): Promise<RenderResult> {\n const created: { context?: BrowserContext } = {};\n return bounded(renderPage(browser, request, axeSource, created), {\n deadline: request.deadline,\n late: { ready: false, findings: [], timedOut: true, timingsMs: {} },\n abandon: () => created.context?.close(),\n });\n}\n\n/** Renders one capability link and reports what the page did. */\nasync function renderPage(\n browser: Browser,\n request: RenderRequest,\n axeSource: string,\n created: { context?: BrowserContext },\n): Promise<RenderResult> {\n const started = Date.now();\n const timings: Record<string, number> = {};\n const findings: Diagnostic[] = [];\n const viewport = request.viewport.name;\n const location = { viewport };\n let timedOut = false;\n const left = (): number => request.deadline - Date.now();\n const context: BrowserContext = await browser.newContext({\n viewport: { width: request.viewport.width, height: request.viewport.height },\n deviceScaleFactor: 1,\n reducedMotion: \"reduce\",\n });\n created.context = context;\n try {\n // The same instant for every render of a run (D138 item 4): `Date` is fixed, timers run.\n await context.clock.setFixedTime(request.clockMs);\n await context.addInitScript(recorder);\n const page = await context.newPage();\n // A popup (window.open, target=_blank) is closed at once: verify follows no link out.\n context.on(\"page\", (other) => {\n if (other !== page) void other.close().catch(() => undefined);\n });\n const network = watchNetwork(page, () => artifactFrame(page), viewport);\n\n await page.goto(request.url, { waitUntil: \"commit\", timeout: Math.max(1000, left()) });\n let ready = false;\n try {\n await page.waitForFunction(\n () => {\n const state = document.documentElement.dataset[\"shell\"];\n return state === \"ready\" || state === \"error\";\n },\n undefined,\n { timeout: Math.max(1, Math.min(READY_WAIT_MS, left())) },\n );\n ready = (await page.evaluate(() => document.documentElement.dataset[\"shell\"])) === \"ready\";\n } catch {\n ready = false;\n timedOut = left() <= 0;\n }\n timings[\"ready\"] = Date.now() - started;\n const frame = artifactFrame(page);\n if (!ready || !frame) {\n const shellError = await page\n .evaluate(() => document.documentElement.dataset[\"shellError\"] ?? \"\")\n .catch(() => \"\");\n findings.push(\n createDiagnostic(\"MOSTRA_RUNTIME_NOT_READY\", {\n message: `The page didn't report it was ready${shellError ? ` (the shell reported ${shellError})` : \"\"}.`,\n location,\n }),\n );\n if (frame)\n findings.push(\n ...judge(await recorded(frame), { kind: \"load\" }, network.skippedSince(0), viewport),\n );\n findings.push(...network.findings());\n return { ready: false, findings: dedupe(findings), timedOut, timingsMs: timings };\n }\n\n await frame.evaluate(() => document.fonts.ready.then(() => true)).catch(() => false);\n await settle(frame, network, request.deadline);\n timings[\"settled\"] = Date.now() - started;\n\n let screenshot: RenderResult[\"screenshot\"];\n let shot: Buffer | undefined;\n if (request.collect.screenshot) {\n const path = join(request.outDir, `${request.name}.png`);\n shot = await page.screenshot({ path, animations: \"disabled\", caret: \"hide\" });\n screenshot = { path, width: request.viewport.width, height: request.viewport.height };\n timings[\"screenshot\"] = Date.now() - started;\n }\n // Blank only when nothing has a visible box and the picture is one color (D138 item 3).\n const shows = await frame.evaluate(showsSomething).catch(() => true);\n if (!shows && shot && uniform(shot)) {\n findings.push(\n createDiagnostic(\"MOSTRA_RUNTIME_BLANK\", {\n message:\n \"The page reported ready but shows nothing: no visible text, image, SVG, canvas or video, and its screenshot is one color.\",\n location,\n }),\n );\n }\n\n let aria: RenderResult[\"aria\"];\n if (request.collect.aria) {\n const text = await frame\n .locator(\"body\")\n .ariaSnapshot({ timeout: Math.max(1000, Math.min(10_000, left())) })\n .catch(() => {\n if (left() <= 0) timedOut = true;\n return \"\";\n });\n // The supervisor writes it, through the instance's redactor (Opus review finding 8).\n const path = join(request.outDir, `${request.name}.aria.yml`);\n aria = { path, text };\n timings[\"aria\"] = Date.now() - started;\n }\n\n let fields: readonly ProbedField[] | undefined;\n if (request.collect.fields) {\n fields = await frame\n .evaluate(\n (name) =>\n ((globalThis as unknown as Record<string, { fields?: () => unknown } | undefined>)[\n name\n ]?.fields?.() ?? []) as ProbedField[],\n VERIFY_PROBE,\n )\n .catch(() => []);\n }\n\n if (request.collect.axe && left() <= 0) timedOut = true;\n if (request.collect.axe && left() > 0) {\n findings.push(...(await runAxe(frame, axeSource, viewport)));\n timings[\"axe\"] = Date.now() - started;\n }\n\n // Faults while loading, judged before the smoke test adds its own.\n const loadEntries = await recorded(frame);\n findings.push(...judge(loadEntries, { kind: \"load\" }, network.skippedSince(0), viewport));\n\n let smoke: RenderResult[\"smoke\"];\n if (request.collect.smoke && left() > 0) {\n const outcome = await runSmoke({\n page,\n frame,\n network,\n viewport,\n from: loadEntries.length,\n maxInteractions: request.collect.smoke.maxInteractions,\n // The smoke test stops between interactions at its budget, which leaves the regression\n // stage its time: stopping there is the budget's hint, never a timeout (Opus review\n // finding 6).\n deadline: Math.min(\n request.deadline - SMOKE_RESERVE_MS,\n Date.now() + request.collect.smoke.maxMs,\n ),\n });\n findings.push(...outcome.findings);\n smoke = {\n interactions: outcome.interactions,\n stateChanges: outcome.stateChanges,\n skipped: network.skippedSince(0),\n budget: outcome.budget,\n };\n timings[\"smoke\"] = Date.now() - started;\n }\n findings.push(...network.findings());\n return {\n ready: true,\n findings: dedupe(findings),\n ...(screenshot ? { screenshot } : {}),\n ...(aria ? { aria } : {}),\n ...(fields ? { fields } : {}),\n ...(smoke ? { smoke } : {}),\n timedOut,\n timingsMs: timings,\n };\n } finally {\n await context.close().catch(() => undefined);\n }\n}\n\ninterface AxeResult {\n readonly violations: readonly {\n readonly id: string;\n readonly impact?: string | null;\n readonly help: string;\n readonly nodes: readonly { readonly target: readonly unknown[] }[];\n }[];\n}\n\n/**\n * axe-core's violations in the frame (axe's `incomplete` results aren't reported): critical and\n * serious are warnings, moderate and minor hints (D138 item 3). Injected unmodified.\n */\nasync function runAxe(frame: Frame, source: string, viewport: string): Promise<Diagnostic[]> {\n try {\n await frame.evaluate(source);\n const result = (await frame.evaluate(() =>\n (\n globalThis as unknown as {\n axe: { run(context: unknown, options: unknown): Promise<unknown> };\n }\n ).axe.run(document, { resultTypes: [\"violations\"] }),\n )) as AxeResult;\n return result.violations.map((violation) => {\n const targets = violation.nodes.slice(0, 3).map((node) => node.target.map(String).join(\" \"));\n const serious = violation.impact === \"critical\" || violation.impact === \"serious\";\n return createDiagnostic(serious ? \"MOSTRA_A11Y_SERIOUS\" : \"MOSTRA_A11Y_MINOR\", {\n message: `axe ${violation.id} (${violation.impact ?? \"unrated\"}): ${violation.help}. Found at ${targets.join(\", \")}${violation.nodes.length > 3 ? \" and more\" : \"\"}.`,\n location: { viewport, ...(targets[0] ? { selector: targets[0] } : {}) },\n count: Math.max(1, violation.nodes.length),\n });\n });\n } catch {\n return [];\n }\n}\n\n/** Compares two screenshots with pixelmatch's perceptual threshold and writes the difference. */\nexport async function compareImages(request: CompareRequest): Promise<CompareResult> {\n const a = PNG.sync.read(await readFile(request.a));\n const b = PNG.sync.read(await readFile(request.b));\n if (a.width !== b.width || a.height !== b.height) return { ratio: 1, sizeChanged: true };\n const diff = new PNG({ width: a.width, height: a.height });\n const changed = pixelmatch(a.data, b.data, diff.data, a.width, a.height, { threshold: 0.1 });\n await writeFile(request.diff, PNG.sync.write(diff));\n return { ratio: changed / (a.width * a.height), sizeChanged: false };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,60 @@
1
1
  {
2
2
  "name": "@mostra/verify",
3
- "version": "0.0.0-placeholder",
4
- "description": "Placeholder for Mostra's first release, which publishes through trusted publishing.",
3
+ "version": "0.1.0",
4
+ "description": "The verify runner: the child process that renders artifact pages in a warm headless Chromium for `mostra verify`.",
5
5
  "license": "Apache-2.0",
6
- "repository": { "type": "git", "url": "git+https://github.com/mostra-dev/mostra.git" }
7
- }
6
+ "author": "The Mostra Authors",
7
+ "homepage": "https://github.com/mostra-dev/mostra/tree/main/packages/verify#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/mostra-dev/mostra.git",
11
+ "directory": "packages/verify"
12
+ },
13
+ "bugs": "https://github.com/mostra-dev/mostra/issues",
14
+ "type": "module",
15
+ "sideEffects": false,
16
+ "exports": {
17
+ ".": {
18
+ "@mostra/source": "./src/index.ts",
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "src",
27
+ "!src/**/*.test.ts"
28
+ ],
29
+ "engines": {
30
+ "node": ">=24"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "provenance": true
35
+ },
36
+ "dependencies": {
37
+ "@mostra/protocol": "0.1.0",
38
+ "axe-core": "4.14.0",
39
+ "pixelmatch": "8.0.0",
40
+ "playwright-core": "1.64.0",
41
+ "pngjs": "7.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@mostra/tools": "0.0.0",
45
+ "@types/node": "24.19.1",
46
+ "tsdown": "0.23.0",
47
+ "typescript": "7.0.2",
48
+ "vitest": "5.0.3",
49
+ "@types/pngjs": "6.0.5"
50
+ },
51
+ "bin": {
52
+ "mostra-verify": "./dist/main.js"
53
+ },
54
+ "scripts": {
55
+ "build": "tsdown",
56
+ "typecheck": "tsc -b",
57
+ "test": "vitest run",
58
+ "api": "mostra-tools api"
59
+ }
60
+ }
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `@mostra/verify`: the verify runner, the child process the supervisor starts to render artifact
3
+ * pages in a warm headless Chromium (ARCHITECTURE §1, D138). Its entry is `mostra-verify`
4
+ * (`dist/main.js`); the supervisor talks to it over IPC with `VERIFY_RUNNER_METHODS` from
5
+ * `@mostra/protocol`. This entry exposes the runner itself, for tests and other hosts of it.
6
+ */
7
+
8
+ export { ariaSummary } from "@mostra/protocol";
9
+ export { dedupe, judge, type Moment } from "./observe.ts";
10
+ export { type RecordedEntry, recorder } from "./recorder.ts";
11
+ export {
12
+ bounded,
13
+ compareImages,
14
+ createRunner,
15
+ RUNNER_IDLE_MS,
16
+ type Runner,
17
+ type RunnerOptions,
18
+ uniform,
19
+ } from "./runner.ts";
package/src/main.ts ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The verify runner child's entry (`mostra-verify`, ARCHITECTURE §1): the supervisor's process
4
+ * manager starts it, in its own process group, with a JSON-RPC channel over Node IPC. It serves
5
+ * `verify/render` and `verify/compare`, keeps one warm Chromium for them, and exits after 10 idle
6
+ * minutes or when its supervisor goes away, taking the browser with it (FAILURE_MODEL §2).
7
+ */
8
+ import {
9
+ type CompareRequest,
10
+ createDiagnostic,
11
+ type RenderRequest,
12
+ RpcError,
13
+ RpcPeer,
14
+ VERIFY_RUNNER_METHODS,
15
+ } from "@mostra/protocol";
16
+ import { createRunner } from "./runner.ts";
17
+
18
+ const send = process.send?.bind(process);
19
+ if (!send) {
20
+ process.stderr.write("mostra-verify runs only as a child of a Mostra supervisor.\n");
21
+ process.exit(2);
22
+ }
23
+
24
+ /** Ends this child and its process group (the browser with it). */
25
+ function exitWithGroup(code: number): never {
26
+ try {
27
+ process.kill(-process.pid, "SIGTERM");
28
+ } catch {
29
+ // The group is already gone.
30
+ }
31
+ process.exit(code);
32
+ }
33
+
34
+ const idle = Number(process.env["MOSTRA_TEST_VERIFY_IDLE_MS"]);
35
+ const runner = createRunner({
36
+ ...(idle > 0 ? { idleMs: idle } : {}),
37
+ onIdle: () => {
38
+ void runner.close().finally(() => exitWithGroup(0));
39
+ },
40
+ });
41
+
42
+ const peer = new RpcPeer({
43
+ send: (message) => {
44
+ send(message);
45
+ },
46
+ onRequest: async (method, params) => {
47
+ if (method === VERIFY_RUNNER_METHODS.render) {
48
+ return runner.render(params as unknown as RenderRequest);
49
+ }
50
+ if (method === VERIFY_RUNNER_METHODS.compare) {
51
+ return runner.compare(params as unknown as CompareRequest);
52
+ }
53
+ throw new RpcError(
54
+ createDiagnostic("MOSTRA_HOST_METHOD_UNKNOWN", {
55
+ message: `The verify runner serves no method ${method}.`,
56
+ }),
57
+ );
58
+ },
59
+ });
60
+ process.on("message", (message) => peer.receive(message));
61
+ process.on("disconnect", () => {
62
+ peer.close();
63
+ void runner.close().finally(() => exitWithGroup(0));
64
+ });
package/src/network.ts ADDED
@@ -0,0 +1,161 @@
1
+ /**
2
+ * What a verify render's page does on the network (D138 item 3): requests in flight (for the
3
+ * quiescence wait), failed requests for resources the artifact's frame loaded, the sandbox's
4
+ * refusals of actions that didn't opt into `dryRun()`, and the field writes the page made.
5
+ *
6
+ * A failed request counts when the artifact's frame loaded it: its document, scripts, styles,
7
+ * images, fonts and fetches. Requests the browser makes on its own (`/favicon.ico`) and Mostra's
8
+ * protocol (`/_mostra/…`, whose answers the client runtime handles) don't. Chrome's own
9
+ * "Failed to load resource" console line is never counted: the request's finding stands for it.
10
+ */
11
+ import { createDiagnostic, type Diagnostic } from "@mostra/protocol";
12
+ import type { Frame, Page, Request, Response } from "playwright-core";
13
+
14
+ /** The network of one render. */
15
+ export interface NetworkWatch {
16
+ /** Requests in flight, the page's event stream aside. */
17
+ inflight(): number;
18
+ /** Server functions the sandbox refused since mark `from` (see {@link NetworkWatch.mark}). */
19
+ skippedSince(from: number): string[];
20
+ /** A mark for {@link NetworkWatch.skippedSince} and {@link NetworkWatch.writesSince}. */
21
+ mark(): number;
22
+ /** Field ops the gateway applied since a mark. */
23
+ writesSince(from: number): number;
24
+ /**
25
+ * Requests the artifact's frame made since a mark, its event stream aside: Mostra's protocol
26
+ * (fields, calls, runs, signals, submits) and the page's own fetches alike, each an effect.
27
+ */
28
+ requestsSince(from: number): number;
29
+ /** The failed-request findings so far. */
30
+ findings(): Diagnostic[];
31
+ }
32
+
33
+ /** Whether a request is the page's event stream, which stays open. */
34
+ const isStream = (url: URL): boolean => /\/_mostra\/v\/[^/]+\/events(\/poll)?$/.test(url.pathname);
35
+
36
+ /** Watches a page's network; `frame` finds the artifact's frame. */
37
+ export function watchNetwork(
38
+ page: Page,
39
+ frame: () => Frame | undefined,
40
+ viewport: string,
41
+ ): NetworkWatch {
42
+ const open = new Set<Request>();
43
+ /** Events in order: skips, writes and protocol requests, each at its position. */
44
+ const log: (
45
+ | { kind: "skip"; fn: string }
46
+ | { kind: "write"; ops: number }
47
+ | { kind: "protocol" }
48
+ | { kind: "request" }
49
+ )[] = [];
50
+ const failures = new Map<string, Diagnostic>();
51
+ /** Protocol answers still being read: they count as in flight until their body is in. */
52
+ let reading = 0;
53
+
54
+ const fromArtifact = (request: Request): boolean => {
55
+ try {
56
+ const own = frame();
57
+ return own !== undefined && request.frame() === own;
58
+ } catch {
59
+ return false; // A service worker's request has no frame.
60
+ }
61
+ };
62
+
63
+ const counted = (url: URL, artifactOrigin: string | undefined): boolean => {
64
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
65
+ if (url.origin === artifactOrigin && url.pathname.startsWith("/_mostra/")) return false;
66
+ return url.pathname !== "/favicon.ico";
67
+ };
68
+
69
+ const fail = (request: Request, why: string): void => {
70
+ if (!fromArtifact(request)) return;
71
+ const url = new URL(request.url());
72
+ let artifactOrigin: string | undefined;
73
+ try {
74
+ artifactOrigin = new URL(frame()?.url() ?? "").origin;
75
+ } catch {
76
+ artifactOrigin = undefined;
77
+ }
78
+ if (!counted(url, artifactOrigin)) return;
79
+ const own = url.origin === artifactOrigin;
80
+ const shown = own ? url.pathname : `${url.origin}${url.pathname}`;
81
+ const key = `${request.method()} ${shown} ${why}`;
82
+ const known = failures.get(key);
83
+ if (known) {
84
+ failures.set(key, { ...known, count: (known.count ?? 1) + 1 });
85
+ return;
86
+ }
87
+ failures.set(
88
+ key,
89
+ createDiagnostic(own ? "MOSTRA_NETWORK_REQUEST_FAILED" : "MOSTRA_NETWORK_EXTERNAL_FAILED", {
90
+ message: `${request.resourceType()} ${request.method()} ${shown} ${why}.`,
91
+ location: { viewport },
92
+ }),
93
+ );
94
+ };
95
+
96
+ page.on("request", (request) => {
97
+ const url = new URL(request.url());
98
+ if (isStream(url)) return;
99
+ open.add(request);
100
+ // Any request the page's own code makes is something a control did (GPT review finding 4).
101
+ if (fromArtifact(request) && !url.pathname.startsWith("/_mostra/"))
102
+ log.push({ kind: "request" });
103
+ });
104
+ const done = (request: Request): void => {
105
+ open.delete(request);
106
+ };
107
+ page.on("requestfinished", done);
108
+ page.on("requestfailed", (request) => {
109
+ done(request);
110
+ const text = request.failure()?.errorText ?? "failed";
111
+ // A request the page itself cancelled (an aborted fetch, a navigation away) didn't fail.
112
+ if (/ERR_ABORTED|NS_BINDING_ABORTED|cancelled/i.test(text)) return;
113
+ fail(request, `failed (${text})`);
114
+ });
115
+ page.on("response", (response: Response) => {
116
+ const request = response.request();
117
+ const url = new URL(request.url());
118
+ const status = response.status();
119
+ if (url.pathname.includes("/_mostra/v/")) {
120
+ reading += 1;
121
+ void onProtocol(request, url, response).finally(() => {
122
+ reading -= 1;
123
+ });
124
+ return;
125
+ }
126
+ if (status >= 400) fail(request, `answered ${status}`);
127
+ });
128
+
129
+ const onProtocol = async (request: Request, url: URL, response: Response): Promise<void> => {
130
+ if (request.method() !== "POST" || isStream(url)) return;
131
+ const route = /\/_mostra\/v\/[^/]+\/(fields|call\/.+|runs|signal|submit)$/.exec(
132
+ url.pathname,
133
+ )?.[1];
134
+ if (!route) return;
135
+ log.push({ kind: "protocol" });
136
+ if (route === "fields" && response.ok()) {
137
+ const body = (await response.json().catch(() => ({}))) as { applied?: unknown[] };
138
+ log.push({ kind: "write", ops: Array.isArray(body.applied) ? body.applied.length : 1 });
139
+ return;
140
+ }
141
+ if (response.status() !== 409 || !(route === "runs" || route.startsWith("call/"))) return;
142
+ const body = (await response.json().catch(() => ({}))) as { code?: unknown };
143
+ if (body.code !== "MOSTRA_VERIFY_ACTION_SKIPPED") return;
144
+ const fn = route.startsWith("call/")
145
+ ? decodeURIComponent(route.slice("call/".length))
146
+ : String((request.postDataJSON() as { fnId?: unknown } | null)?.fnId ?? "an action");
147
+ log.push({ kind: "skip", fn });
148
+ };
149
+
150
+ return {
151
+ inflight: () => open.size + reading,
152
+ mark: () => log.length,
153
+ skippedSince: (from) => [
154
+ ...new Set(log.slice(from).flatMap((entry) => (entry.kind === "skip" ? [entry.fn] : []))),
155
+ ],
156
+ writesSince: (from) =>
157
+ log.slice(from).reduce((sum, entry) => sum + (entry.kind === "write" ? entry.ops : 0), 0),
158
+ requestsSince: (from) => log.slice(from).filter((entry) => entry.kind !== "write").length,
159
+ findings: () => [...failures.values()],
160
+ };
161
+ }
package/src/observe.ts ADDED
@@ -0,0 +1,159 @@
1
+ /// <reference lib="dom" />
2
+ /**
3
+ * How the runner observes a rendered page: the quiescence wait, the recorder's entries, and the
4
+ * judgement of what the page reported (D72, D138 items 2 and 3).
5
+ */
6
+ import { createDiagnostic, type Diagnostic, VERIFY_PROBE } from "@mostra/protocol";
7
+ import type { Frame } from "playwright-core";
8
+ import type { NetworkWatch } from "./network.ts";
9
+ import { RECORDER_GLOBAL, type RecordedEntry } from "./recorder.ts";
10
+
11
+ /**
12
+ * Waits until the frame's document has had no mutation for 200 ms, no request is in flight (the
13
+ * page's event stream aside) and the client runtime waits on no server work (calls, runs, first
14
+ * live values: the probe's `pending`, whose answers arrive on the event stream), at most `maxMs`,
15
+ * or `serverMaxMs` while server work is still pending. A quiescence wait, never a fixed sleep: it
16
+ * returns as soon as the page is still (EVAL-01b; Opus review finding 1).
17
+ */
18
+ export async function settle(
19
+ frame: Frame,
20
+ network: NetworkWatch,
21
+ deadline: number,
22
+ maxMs = 3000,
23
+ serverMaxMs = 10_000,
24
+ ): Promise<boolean> {
25
+ const start = Date.now();
26
+ const end = (pending: boolean): number =>
27
+ Math.min(start + (pending ? Math.max(maxMs, serverMaxMs) : maxMs), deadline);
28
+ try {
29
+ await frame.evaluate(() => {
30
+ const scope = globalThis as unknown as { __mostraQuiet?: () => number };
31
+ if (scope.__mostraQuiet) return;
32
+ let last = performance.now();
33
+ new MutationObserver(() => {
34
+ last = performance.now();
35
+ }).observe(document, {
36
+ subtree: true,
37
+ childList: true,
38
+ attributes: true,
39
+ characterData: true,
40
+ });
41
+ scope.__mostraQuiet = () => performance.now() - last;
42
+ });
43
+ for (;;) {
44
+ const state = await frame.evaluate((probe) => {
45
+ const scope = globalThis as unknown as Record<string, unknown>;
46
+ const quiet = (scope["__mostraQuiet"] as () => number)();
47
+ const pending = (scope[probe] as { pending?: () => number } | undefined)?.pending?.() ?? 0;
48
+ return { quiet, pending };
49
+ }, VERIFY_PROBE);
50
+ if (state.quiet >= 200 && network.inflight() === 0 && state.pending === 0) return true;
51
+ if (Date.now() >= end(state.pending > 0)) return false;
52
+ await new Promise((resolve) => setTimeout(resolve, 50));
53
+ }
54
+ } catch {
55
+ // The frame navigated or closed: nothing more to wait for.
56
+ }
57
+ return false;
58
+ }
59
+
60
+ /** Folds identical findings into one with a count. */
61
+ export function dedupe(findings: readonly Diagnostic[]): Diagnostic[] {
62
+ const seen = new Map<string, Diagnostic>();
63
+ for (const finding of findings) {
64
+ const key = `${finding.code}\u0000${finding.message}`;
65
+ const known = seen.get(key);
66
+ if (known) seen.set(key, { ...known, count: (known.count ?? 1) + (finding.count ?? 1) });
67
+ else seen.set(key, finding);
68
+ }
69
+ return [...seen.values()];
70
+ }
71
+
72
+ /** The recorder's entries in a frame from `from` on. */
73
+ export async function recorded(frame: Frame, from = 0): Promise<RecordedEntry[]> {
74
+ try {
75
+ return (await frame.evaluate(
76
+ ([name, start]) =>
77
+ (
78
+ ((globalThis as unknown as Record<string, unknown>)[name] as unknown[] | undefined) ?? []
79
+ ).slice(start),
80
+ [RECORDER_GLOBAL, from] as const,
81
+ )) as RecordedEntry[];
82
+ } catch {
83
+ return [];
84
+ }
85
+ }
86
+
87
+ /** Where a finding happened, for its message. */
88
+ export type Moment =
89
+ | { readonly kind: "load" }
90
+ | { readonly kind: "interaction"; readonly control: string };
91
+
92
+ const momentText = (moment: Moment): string =>
93
+ moment.kind === "load" ? "while the page loaded" : `after the smoke test used ${moment.control}`;
94
+
95
+ /**
96
+ * Judges what the page reported in one moment (D138 items 2 and 3): its own faults are errors,
97
+ * console warnings hints; a fault whose `cause` chain is verify's refusal, and any console error
98
+ * in a moment when verify refused an action, is a hint naming the action; a missing secret is
99
+ * the environment's, a warning.
100
+ */
101
+ export function judge(
102
+ entries: readonly RecordedEntry[],
103
+ moment: Moment,
104
+ skipped: readonly string[],
105
+ viewport: string,
106
+ ): Diagnostic[] {
107
+ const findings: Diagnostic[] = [];
108
+ const location = { viewport };
109
+ for (const entry of entries) {
110
+ if (entry.dropped) continue;
111
+ const text = entry.text.split("\n").slice(0, 6).join("\n");
112
+ const ownSkip = entry.codes.includes("MOSTRA_VERIFY_ACTION_SKIPPED");
113
+ const action = skipped.length > 0 ? skipped.join(", ") : "an action";
114
+ // By its cause chain always; by the window (a console error after a click that produced a
115
+ // skip) only during an interaction, never for the whole load (Opus review finding 3).
116
+ const inWindow = moment.kind === "interaction" && entry.kind === "error" && skipped.length > 0;
117
+ if (ownSkip || inWindow) {
118
+ findings.push(
119
+ createDiagnostic("MOSTRA_VERIFY_SKIP_CONSEQUENCE", {
120
+ message: `After verify skipped ${action} ${momentText(moment)}, the page reported: ${text}`,
121
+ location,
122
+ }),
123
+ );
124
+ continue;
125
+ }
126
+ if (entry.codes.includes("MOSTRA_SECRET_MISSING")) {
127
+ findings.push(
128
+ createDiagnostic("MOSTRA_VERIFY_SECRET_MISSING", {
129
+ message: `Server code needed a secret ${momentText(moment)}: ${text}`,
130
+ location,
131
+ }),
132
+ );
133
+ continue;
134
+ }
135
+ if (entry.kind === "warn") {
136
+ findings.push(
137
+ createDiagnostic("MOSTRA_CONSOLE_WARNING", {
138
+ message: `The page logged a warning ${momentText(moment)}: ${text}`,
139
+ location,
140
+ }),
141
+ );
142
+ } else if (entry.kind === "error") {
143
+ findings.push(
144
+ createDiagnostic("MOSTRA_CONSOLE_ERROR", {
145
+ message: `The page logged an error ${momentText(moment)}: ${text}`,
146
+ location,
147
+ }),
148
+ );
149
+ } else {
150
+ findings.push(
151
+ createDiagnostic("MOSTRA_RUNTIME_PAGE_ERROR", {
152
+ message: `${entry.kind === "rejection" ? "A promise rejected with nothing to handle it" : entry.kind === "caught" ? "An error boundary caught an error" : "An error was thrown and nothing caught it"} ${momentText(moment)}: ${text}`,
153
+ location,
154
+ }),
155
+ );
156
+ }
157
+ }
158
+ return findings;
159
+ }