import { type CritiqueProvider } from "./critique.js"; import { type QaRunJob, type QaRunResult } from "./qa-run-contracts.js"; import { type QaSnapshotStoreOptions } from "./qa-snapshot.js"; /** Set on the runner's own environment before the host's critique provider is * loaded for the judge stage, unless the job permits metered critique: the * provider must stay on subscription-backed headless harnesses and surface * exhaustion instead of falling back to a metered API. */ export declare const QA_RUN_HEADLESS_ONLY_ENV = "HARNERY_CRITIQUE_HEADLESS_ONLY"; /** Result document written into the run's output directory. */ export declare const QA_RUN_RESULT_FILENAME = "page-qa-result.json"; /** Pointer document written into the parent output directory after every * run, naming the newest run's directory and verdict. Consumers resolve the * current result through this pointer instead of guessing at loose files. */ export declare const QA_RUN_LATEST_FILENAME = "latest.json"; export interface QaRunLatestPointerInput { run_id: string; dir: string; completed_at: string; verdict: QaRunResult["verdict"]; } /** * Publish the parent directory's latest-result pointer without allowing an * older completion to replace a newer one. Both runner and manual evidence * use this writer so every producer preserves the same ordering invariant. */ export declare function writeLatestPointer(outParent: string, input: QaRunLatestPointerInput): string; /** Live status document beside the result (QaRunStatusDocument): written at * start, every stage boundary, and on a heartbeat timer, so a disconnected * client can tell a running job from a dead one without guessing. */ export declare const QA_RUN_STATUS_FILENAME = "run-status.json"; /** The effective validated job, written into the run directory so a * reconnecting client can re-derive the job digest (`qa-verify --job`). */ export declare const QA_RUN_JOB_FILENAME = "job.json"; export interface QaRunExecOptions { /** Hard per-command timeout in milliseconds. */ timeoutMs: number; /** Complete child environment (already layered by the runner). */ env: NodeJS.ProcessEnv; } export interface QaRunExecResult { /** Process exit code; null when the command never completed normally. */ exitCode: number | null; stdout: string; stderr: string; /** Spawn failure / timeout / signal description. Presence means the * command's outcome cannot be trusted. */ error?: string; } /** Injectable child-process executor. `argv[0]` is the executable. */ export type QaRunExec = (argv: string[], options: QaRunExecOptions) => Promise; /** Grace between the timeout's SIGTERM and the follow-up SIGKILL. A child * that catches SIGTERM (Bun installs a handler by default) gets this long to * exit before the kill is made non-negotiable. */ export declare const QA_RUN_KILL_GRACE_MS = 5000; /** Default executor: spawn with argv arrays only (never a shell string), * closed stdin, bounded output buffers, and the policy timeout. * * Timeout enforcement is escalated and group-wide, via `spawn` rather than * `execFile` for two live-verified reasons. First, a child that catches * SIGTERM while awaiting its own grandchildren turns a single polite kill * into an unbounded wait (a critique command outlived its 120s cap by 10x). * Second, `execFile` resolves only when the child's stdio closes, and an * orphaned grandchild inheriting the pipe keeps it open after the child is * dead — so even a delivered kill did not settle the call. The child is * therefore spawned detached into its own process group; at the deadline the * whole group gets SIGTERM, then SIGKILL after a grace, and the result * settles on exit with whatever output drained, never waiting on a pipe an * orphan still holds. A timed-out command reports an error even if the child * then exits 0 — a result produced after the deadline cannot be trusted. */ export declare const defaultQaRunExec: QaRunExec; export interface QaRunMatrixOptions { /** A validated job (see validateQaRunJob — the runner trusts its shape). */ job: QaRunJob; /** PARENT directory for run output. Every invocation creates its own * `run-/` beneath it for artifacts and the result document, and * maintains `latest.json` in the parent — a reused parent can therefore * never present an older run's result as the current one. */ outParent: string; /** argv prefix that reaches the host CLI's browse command, e.g. * `[process.execPath, cliScript, "browse"]`. */ browseArgv: string[]; /** Injectable executor (tests). Default: execFile via defaultQaRunExec. */ exec?: QaRunExec; /** Extra child-environment overrides layered over process.env. */ childEnv?: NodeJS.ProcessEnv; /** Progress callback for human-facing per-stage lines. */ onLog?: (message: string) => void; /** Run ID override (tests). Default: crypto.randomUUID(). */ runId?: string; /** Working-tree revision probe supplied by the caller (the CLI probes git * once). Ignored when the job itself pins tested_revision. */ revisionProbe?: { tested_revision?: string; worktree_dirty?: boolean; }; /** Machine-wide admission gate. When present the runner acquires a slot * before any browser work, records the wait as wall_time_ms.queue (never * part of total), and finalizes an incomplete result with an "admission" * blocker when acquisition fails — the evidence trail survives a full * queue. The returned function releases the slot; the runner calls it at * finalize, and a crashed runner's slot is reclaimed by dead-PID pruning. */ admission?: { resource: string; acquire: (onWait: (message: string) => void) => Promise<() => void>; /** Snapshot of the other holders of the resource, sampled alongside host * pressure so an incomplete run names what it was competing with. */ holders?: () => Array<{ label: string; pid: number; }>; }; /** Host-injected vision call for the judge stage. The judge runs in this * process over the pack on disk, after every capture browser has closed. */ critiqueProvider?: CritiqueProvider; /** Lazy alternative to `critiqueProvider`; called once, after the * headless-only environment has been applied. */ critiqueProviderLoader?: () => Promise; /** QA snapshot store override (tests, host-managed cache locations). */ snapshotStore?: QaSnapshotStoreOptions; /** Renders the command a reader can run to judge the pack later; shown in * the pack's review.md when the judge did not run. */ reviewPackJudgeCommand?: (packDir: string) => string; /** True when the run writes into the managed artifact store: the pack's * manifest then says `managed: true` and the expiry sweep may delete it * once `policy.review_pack_retention_minutes` (default 90) have passed * since the judge finished. A pack under an explicit out dir stays * unmanaged and is never deleted automatically. */ reviewPackManaged?: boolean; } /** * Execute the whole QA matrix for one validated job and return the result * (also written to `/page-qa-result.json`). Stages: plan → * deterministic gates (bounded pool) → interactions (serial) → capture (each * context rendered once into the run's page review pack through the same * pool, browser closed) → critique (one in-process pool of vision calls over * every tile of every context, no browser open) → snapshot (persisted from * the pack's files in signoff). The verdict is computeVerdict over * everything recorded — fail-closed. */ export declare function runQaMatrix(options: QaRunMatrixOptions): Promise; //# sourceMappingURL=qa-run.d.ts.map