/** * Acceptance gate runner — executes typed `AcceptanceGate` items from a * task's `acceptance` array and returns structured `AcceptanceGateResult[]`. * * Supported gate kinds: * - `test` — spawn a command, assert exit code / stdout * - `file` — assert file properties (exists, bytes, content) * - `command` — spawn any CLI, assert exit code / stdout / stderr * - `lint` — run biome/eslint/tsc/prettier/rustc/clippy, assert clean * - `http` — fetch URL, assert status + optional body * - `manual` — always returns `skipped` (requires explicit human verdict) * * Design constraints: * - Each gate is self-contained (no cross-gate state). * - Gates run sequentially under ONE captured operation deadline. The caller admits it * with {@link typedGateAdmissionMs}: the ADR-061 tool deadline of every executing gate * plus {@link TYPED_GATE_BOOKKEEPING_MS} for the surrounding bookkeeping (T12516). * - Per-gate timeouts can only tighten the captured deadline, never renew it. * - Passing process gates can be served from the ADR-061 evidence cache * (`options.cache`, see `gate-result-cache.ts`); failures are never cached (T12621). * - Structured test-count evidence and HTTP service startup require separate capabilities. * - Results are observations; this module does not persist completion authority. * * @epic T760 * @task T781 */ import type { AcceptanceGate, AcceptanceGateResult, AcRow, DataAccessor, LintGate, Task } from '@cleocode/contracts'; import type { AcceptanceGateCacheInputs, AcceptanceGateInvocation, AcceptanceGateRunOptions, AcceptanceGateVerificationReceipt } from '@cleocode/contracts/acceptance-gate'; import type { OperationExecutionContext } from '@cleocode/contracts/jobs'; /** * Shared budget for the bookkeeping around typed gates: task and criterion * reads, input snapshots, result binding and the persisting transaction. * * It is the same two seconds the maintenance paths use, and it is ADDED to the * tool deadlines rather than bounding them. Before T12516 it was the whole * lifetime of a typed verification, so any gate whose tool ran longer than two * seconds — every real test suite — stopped with `E_OPERATION_DEADLINE`. * * @task T12516 */ export declare const TYPED_GATE_BOOKKEEPING_MS = 2000; /** * Captured gate-runner options from the canonical shared contract. * @remarks Nested invocations retain their original operation authority and deadline. * @example * ```typescript * const options: RunGatesOptions = { projectRoot: '/project' }; * ``` */ export type RunGatesOptions = AcceptanceGateRunOptions; /** * Resolve the wall-clock deadline for one gate's tool execution. * * Precedence: the gate's own `timeoutMs`; then the legacy blanket * `CLEO_GATE_TIMEOUT_MS`; then the ADR-061 tool deadline for the gate kind, * resolved by {@link resolveSpawnTimeoutMs} — `CLEO_TOOL_TIMEOUT_`, else * 1,800,000 ms for `test` and 300,000 ms for every other kind. * * @param gate - Gate whose tool deadline is needed. * @param env - Environment to read the overrides from. * @returns Deadline in milliseconds. * @throws When a declared or configured deadline is not a positive safe integer. * @example * ```typescript * resolveGateTimeoutMs({ kind: 'test', description: 'suite', command: 'npm', args: ['test'], expect: 'exit0' }); // 1_800_000 * ``` * @task T12516 */ export declare function resolveGateTimeoutMs(gate: AcceptanceGate, env?: NodeJS.ProcessEnv): number; /** * Wall-clock lifetime to admit for running a set of typed gates. * * The sum of every tool-executing gate's ADR-061 deadline plus * {@link TYPED_GATE_BOOKKEEPING_MS}. Callers that own the operation lifetime * create it with this budget so the two-second shared budget covers the * bookkeeping only and never bounds a tool (T12516). * * @param gates - Gates that will run. * @param env - Environment to read deadline overrides from. * @returns Budget in milliseconds, clamped to the safe-integer range. * @throws When a gate deadline override is invalid (see {@link resolveGateTimeoutMs}). * @example * ```typescript * const execution = createOperationExecutionContext(identity, { budgetMs: typedGateAdmissionMs(gates) }); * ``` * @task T12516 */ export declare function typedGateAdmissionMs(gates: readonly AcceptanceGate[], env?: NodeJS.ProcessEnv): number; /** * Prefix of the `errorMessage` a gate carries when `cache: 'only'` refused to * execute it. The verifier matches it to refuse the write with * `E_GATE_NOT_CACHED` (a gate the write links) or to record it as not run. * @task T12621 * @task T13521 */ export declare const GATE_NOT_CACHED_PREFIX = "typed gate not cached:"; /** * Prefix of the `errorMessage` a gate carries when `cache: 'only'` found an * entry that failed authentication. The verifier refuses the write with * `E_GATE_CACHE_INVALID`: a forged or tampered pass is never recorded. * @task T12621 */ export declare const GATE_CACHE_INVALID_PREFIX = "typed gate cache entry invalid:"; /** * Compute a gate's cache inputs digest the same way the attesting verifier does. * @param task - Task that owns the gate; its `files` are verifier inputs. * @param gate - Gate whose invocation and inputs are captured. * @param execution - Admitted lifetime whose project root scopes the capture. * @param env - Environment the gate will launch with. * @returns Digest to pass as `cacheInputsHash` to {@link runGates}. * @example * ```typescript * const cacheInputsHash = await captureGateInputsHash(task, gate, execution); * ``` * @task T12621 */ export declare function captureGateInputsHash(task: Task, gate: AcceptanceGate, execution: OperationExecutionContext, env?: NodeJS.ProcessEnv): Promise; /** * {@link captureGateInputsHash} with the parts behind the digest, which a * stored pass keeps so a later miss can say what moved (T13693). * * @param task - Task that owns the gate; its `files` are verifier inputs. * @param gate - Gate whose invocation and inputs are captured. * @param execution - Admitted lifetime whose project root scopes the capture. * @param env - Environment the gate will launch with. * @returns The digest and its parts. * @task T13693 */ export declare function captureGateInputs(task: Task, gate: AcceptanceGate, execution: OperationExecutionContext, env?: NodeJS.ProcessEnv): Promise<{ hash: string; inputs: AcceptanceGateCacheInputs; }>; /** * The tree a typed gate runs in and is fingerprinted against. * * `projectRoot` is the CLEO store root, which for a git worktree is the MAIN * checkout (all worktrees share one `.cleo/`). Evidence tools already run in the * caller's own worktree (gh#1220); typed gates ran in the store root, so from a * worktree `cleo done` measured the task's code with its tools and the main * checkout with its typed gates (T12625 review). The caller's git toplevel is * used only when it is a linked worktree OF THIS project — any other cwd * (an unrelated repo, a test harness, a declared child repo in a multi-repo * root) keeps `projectRoot`, so typed-gate paths written against the CLEO root * keep resolving exactly as before. * * Known limit (documented, accepted): "linked worktree of this project" is read * from the `.git` gitlink, so a plain directory carrying a forged gitlink that * points into this project's `.git/worktrees/` is treated as a worktree. That * grants nothing a copy of the checkout would not: its typed results are only * carried to another tree by content (tracked inputs must hash identically and * the recorded HEAD must be merged there). * * T13649: the evidence declaration (`CLEO_EVIDENCE_GIT_ROOT`, `GIT_WORK_TREE` * or `evidence.gitRoot`) is consulted first, as `resolveEvidenceExecutionRoot` * does for evidence tools. Without it, a run whose cwd had been moved to the * store root keyed the gate cache to the primary checkout's HEAD even when the * caller named its worktree. * * T14058: the tree comes from {@link resolveEvidenceTree}, the resolver * change-set binding uses, so typed gates and a bound test-run never measure * different checkouts: the invocation's linked worktree of the declared * repository wins, and with `taskId` the task's `task/` worktree is chosen * from the main checkout. Only this project's checkout or one of its linked * worktrees is accepted; any other candidate is skipped. * * @param projectRoot - CLEO store root (the typed gate's authority). * @param cwd - Invocation directory. Defaults to `process.cwd()`. * @param env - Where the evidence declaration is read. Defaults to `process.env`. * @param taskId - Task whose `task/` worktree may be the tree; null skips that tier. * @returns The realpath of the tree typed gates execute in. * @task T12625 * @task T13649 * @task T14058 */ export declare function resolveTypedGateRoot(projectRoot: string, cwd?: string, // CWD-OK: the invocation tree is the subject (gh#1220) env?: NodeJS.ProcessEnv, taskId?: string | null): string; /** * Execute all typed `AcceptanceGate` entries and return results. * * Free-text strings in the acceptance array MUST be filtered by the caller * before invoking this function. Only `AcceptanceGate` objects are accepted. * * @param gates - Typed gate objects (strings pre-filtered by caller). * @param options - Execution options. * @returns Ordered `AcceptanceGateResult[]`, one per gate. * @throws If explicit project authority conflicts, metadata is invalid, or admission options are invalid. * @remarks Results retain the original gate requirement ID. Process outcomes include target, * transport and cleanup observations; they do not by themselves authorize task completion. * Declared operation bytes reserve the per-gate capture ceiling before admission. Filesystem * calls and regular-expression CPU work are cooperatively checked, not synchronously preempted. * Canonical attachment retrieval validates its declared size but is not a streaming read API. * @example * ```typescript * const results = await runGates([{ kind: 'command', description: 'check', cmd: 'node', args: ['check.mjs'] }], { projectRoot: '/project' }); * ``` * * @epic T760 * @task T781 */ export declare function runGates(gates: AcceptanceGate[], options?: RunGatesOptions): Promise; /** * A typed result was verified in another tree of this project and cannot be * accepted from the completing tree. Carries the exact re-verify command. * * @task T12625 */ export declare class TypedRevalidationError extends Error { /** The command that re-verifies the task's typed gates in the completing tree. */ readonly fix: string; constructor(message: string, fix: string); } /** * Run task requirements with their canonical AC and bounded input snapshots. * @param task - Captured task, including its complete mixed acceptance array. * @param criteria - Current normalized acceptance rows from the same task snapshot. * @param options - Existing explicitly admitted verification lifetime and process limits. * @returns Results bound to original mixed-array indexes, identities and input bytes. * @remarks This function never writes a result. The canonical verifier must compare * task/criterion freshness again and persist results plus receipt in one transaction. * Untracked harnesses are included; undeclared runtime dependencies are not inferred. * @example * ```typescript * const results = await runTaskGates(task, rows, { projectRoot, execution }); * ``` */ export declare function runTaskGates(task: Task, criteria: readonly AcRow[], options: RunGatesOptions): Promise; /** * Build the existing audit detail payload for one validated typed-result batch. * @param results - Exact result array that is persisted alongside this receipt. * @param passed - Overall verification outcome at the time of recording. * @returns The canonical receipt detail shape with deterministic result-byte hash. * @throws Error when results are absent, unbound, duplicated or belong to different batches. * @remarks This serializer establishes shape and byte identity, not execution authority. * The caller must persist the receipt and results in the same owning transaction. * @example * ```ts * const details = createTaskGateReceipt(results, verification.passed); * await transaction.appendLog({ action: 'gate.verify.typed', taskId, details }); * ``` */ export declare function createTaskGateReceipt(results: readonly AcceptanceGateResult[], passed: boolean): AcceptanceGateVerificationReceipt; /** * Why a stored typed result no longer stands for `gate` on `task`, or null * when its result, kind, REQ-ID and binding (task, owner, criterion id, * criterion text hash, gate definition hash) all match. The one predicate * `cleo complete`'s revalidation and `cleo done --plan` share (T13502), so * the plan never reads as a pass what completion refuses. * * @param result - The stored typed result for the gate's index. * @param task - The task (its id). * @param gate - The gate at that index now. * @param row - The AC row at that index now. * @param owner - The project identity and root completing; omitted, the owner is not compared. * @param requirePassing - True when only a pass may stand (completion, planning). * @returns The reason, worded to follow "Typed requirement ", or null. * @task T13502 */ export declare function typedResultStaleReason(result: AcceptanceGateResult, task: Pick, gate: AcceptanceGate, row: Pick, owner?: { projectId: string; projectRoot: string; }, requirePassing?: boolean): string | null; /** * Revalidate persisted typed results against current canonical task and input state. * @param task - Current task read under the caller's transaction ownership. * @param criteria - Current normalized AC rows from that transaction. * @param results - Authentic stored results, never caller-submitted verdicts. * @param options - Current captured lifetime bounding revalidation work. * @param requirePassing - True for completion; false only when recording actual unmet verification outcomes. * @returns Resolves when bound inputs are current and the requested verdict policy holds. * @remarks Historical unbound results and generic text-AC evidence cannot substitute. * Filesystem checks are cooperative and cannot make external file writes atomic with SQLite. * A result verified in the completing tree whose inputs moved is refused with a * {@link TypedRevalidationError} naming what moved and the re-bind command (T14063). * @example * ```typescript * await revalidateTaskGateResults(task, rows, task.verification?.gateResults ?? [], { execution }); * ``` */ export declare function revalidateTaskGateResults(task: Task, criteria: readonly AcRow[], results: readonly AcceptanceGateResult[], options: RunGatesOptions, requirePassing?: boolean): Promise; /** Why a stored typed pass would not stand at completion, and how to re-bind it (T14063). */ export interface TypedResultDrift { /** The refusal `cleo complete` would raise, after "Typed requirement ". */ reason: string; /** Command that re-binds the result, when one is known. */ fix?: string; } /** * Run completion's input revalidation over every stored typed PASS without * refusing (T14063), so `cleo done --plan` never reports a pass that * `cleo complete` would refuse. * * @param task - The task as stored. * @param criteria - Its normalized AC rows. * @param options - Execution lifetime, store root and environment, as completion passes them. * @param cwd - Directory `cleo complete` would run in; it selects the tree. * @returns The refusal per acceptance index; an index absent from the map stands. * @example * ```typescript * const drift = await typedResultDrift(task, rows, { execution, projectRoot }, runFrom); * ``` * @task T14063 */ export declare function typedResultDrift(task: Task, criteria: readonly AcRow[], options: RunGatesOptions, cwd?: string): Promise>; /** * Validate completion authority from current typed inputs and the canonical verification receipt. * @param task - Task snapshot owned by the caller's write transaction. * @param criteria - Normalized criteria from that same transaction. * @param options - Captured original execution lifetime and project scope. * @param accessor - Canonical receipt reader bound to the same project store. * @param policy - Project evidence policy; `allowCachedGates: false` refuses cached passes (T12621). * @returns Resolves only when every hard typed requirement has current authentic proof. * @throws When typed representations, results, inputs or canonical receipts disagree. * @remarks Callers retain transaction ownership and must check their execution context before * committing. Advisory gates retain their existing semantics; generic evidence cannot replace * hard typed proof. Filesystem hashing is cooperative, not atomic with external file writes. * @example * ```typescript * await validateTaskGateCompletion(task, rows, { execution }, accessor); * ``` */ export declare function validateTaskGateCompletion(task: Task, criteria: readonly AcRow[], options: RunGatesOptions, accessor: Pick, policy?: { allowCachedGates?: boolean; }): Promise; /** * Resolve the actual executable inputs once using the same rules as process * launch. Exported so `cleo goal draft` spells a gate's command exactly as it * runs (T13793). * * @param gate - The gate. * @param root - Project root its `cwd` resolves against. * @param env - Environment the gate's `env` overlays. * @returns The invocation, or undefined for a gate that runs no process. */ export declare function executableInvocation(gate: AcceptanceGate, root: string, env: NodeJS.ProcessEnv): AcceptanceGateInvocation | undefined; /** * Whether a test command's output reports a failure, for `expect: 'pass'`: * a `FAIL`, `failing` or `Error:` token, after removing summaries that report * zero failures (T13511 — `node --test` prints `ℹ fail 0` on success, which * the bare pattern read as a failure) and passing-test lines, whose test names * routinely say "failure" (T13752). A non-zero count still matches. * Only consulted when the run emitted no structured report. * * @param stdout - The command's captured stdout. * @returns True when the output reports a failure. * @example * ```ts * outputReportsFailure('ℹ pass 3\nℹ fail 0'); // false * outputReportsFailure('ℹ pass 2\nℹ fail 1'); // true * ``` * @task T13511 * @task T13752 */ export declare function outputReportsFailure(stdout: string): boolean; /** * Tool-specific executable, default arguments and failure pattern of a typed * lint gate. A gate fails on a non-zero exit, and with `expect: 'clean'` also * when the output matches `errorPattern`. Shared with `cleo goal draft` (T13793). */ export declare const LINT_TOOL_DEFAULTS: Record; /** * Filter a mixed acceptance array to only typed `AcceptanceGate` objects. * Free-text strings are silently dropped with their original index preserved * via the `index` field of each result. * * @param items - Mixed `(string | AcceptanceGate)[]` from `task.acceptance`. * @returns Typed gates with their original indices. */ export declare function extractTypedGates(items: (string | AcceptanceGate)[]): Array<{ gate: AcceptanceGate; originalIndex: number; }>; //# sourceMappingURL=gate-runner.d.ts.map