/** * L2 mechanical verification gate (design/54) — the "verifiable outputs" exit oracle. * * Runs a TRUSTED, spec-derived suite of commands (build / type-check / test) in an {@link ExecutionEnv} * and gates on the **real exit codes**: pass = every step exited 0. This is the mechanical half of * verifiable outputs (design/54 §2); the de-correlated LLM-judge half is {@link runWithVerification} * (L3). It catches what tests cover; service[33] measured L2 alone misses 50-100% of semantic/edge * defects, which is why L3 runs on what passes L2. * * **It is a thin exit oracle, not a framework** — no model, no Runner, no agents dependency. It just runs * commands and reports their exit truthfully, choosing the streaming `execStream` on a remote env (long * builds, read-timeout, output cap) or the buffered `exec` on a base env. Which commands to run, and how * to compose L2 with L3 / per-module sub-gates, is the caller's (profile's) job (design/54 §4). * * 🔴 SECURITY (design/53 §2.B, threat BUG8 — "L2 can be gamed"): `steps` MUST be a trusted, spec-derived * suite — NOT test files an **untrusted worker** authored (a worker can write `assert(true)` / happy-path * tests that pass while the code is broken, pushing all pressure onto an uncalibrated L3). This helper does * not and cannot know which commands are trustworthy; the caller owns that. The gate's guarantee is narrow: * "did these exact commands all exit 0 in this env" — nothing about whether the commands are meaningful, * whether the env is isolated (design/53 §E = deployment), or the semantics of the change (that is L3). */ import type { ExecutionEnv } from "../internal/harness.js"; /** One command in an L2 suite. */ export interface ExecStep { /** Shell command to run (e.g. `"npm test"`, `"tsc --noEmit"`, `"git diff"`). */ command: string; /** Human label for reporting. Default: the command string. */ label?: string; /** Working directory; relative paths resolve against the env root. Default: the env's cwd. */ cwd?: string; /** Extra environment variables for this command. */ env?: Record; /** Per-step timeout (seconds). Default: none. */ timeoutSec?: number; } /** The outcome of a single {@link ExecStep}. */ export interface ExecStepResult { label: string; command: string; /** The real exit code, or `null` when the command never produced one (transport error / timeout / abort). */ exitCode: number | null; stdout: string; stderr: string; /** `exitCode === 0`. A `null` exit is **never** `ok` (we can't confirm success, so we don't pass it). */ ok: boolean; /** Set when the step failed to RUN (an `ExecutionError`/`RemoteExecutionError` code) — distinct from a non-zero exit. */ errorCode?: string; /** True when captured output was truncated at {@link ExecGateOptions.maxOutputBytes}. */ truncated?: boolean; } /** The L2 verdict over a whole suite. */ export interface ExecGateResult { /** True iff there was ≥1 step AND every step ran and exited 0. An EMPTY suite is `false` (nothing was verified — not a vacuous pass). */ passed: boolean; /** Per-step detail, in order. With `stopOnFailure` (default), steps after the first failure are absent. */ steps: ExecStepResult[]; } /** Options for {@link runExecGate}. */ export interface ExecGateOptions { /** Stop at the first failed/non-zero step (default `true` — a cheap CI-style short-circuit). */ stopOnFailure?: boolean; /** Cap captured output **per step** in UTF-8 bytes (default 64 KiB) so a `find /` can't flood memory. */ maxOutputBytes?: number; /** Idle read timeout (ms) for the streaming (remote) path; ignored on the buffered path. */ readTimeoutMs?: number; /** Abort the whole gate (also forwarded to each command). */ signal?: AbortSignal; } /** * Run an L2 mechanical gate: execute `steps` in `env` and gate on real exit codes. See the file header for * the security contract — `steps` must be a trusted, spec-derived suite, not worker-authored tests. */ export declare function runExecGate(env: ExecutionEnv, steps: ExecStep[], options?: ExecGateOptions): Promise; //# sourceMappingURL=exec-gate.d.ts.map