/** * Loader and validator for `.commandmate/verify.yaml`. * * Canonical spec: docs/design/verification-config.md * Phase 0 reference implementation: .claude/skills/cmate-verify/scripts/verify-run.sh * * Server-only: reads from disk, so import this from API routes / CLI only. */ export interface VerifyGate { id: string; command: string; timeoutSec: number; /** * Name of a machine-wide lock this gate must hold while it runs (Issue #1771). * * Declares "only one of these may run on this machine at a time", which a * gate owning a fixed port, database or emulator needs and which command and * timeout cannot express. Two parallel worktrees running such a gate at once * make the second fail on the resource, and `GATE e2e FAIL exit=1` reads * exactly like the change being broken. * * Optional rather than `string | null`: contracts store their gate list as * JSON in `tasks.contract_json` and are read back with `JSON.parse`, never * re-validated, so every row written before this field existed simply has no * key — and `undefined` is the honest reading of that. */ mutex?: string; /** * Re-run this gate once, in the same tree, when its command exits non-zero * (Issue #1772). `1` opts in; `0` (the default) is the behaviour every gate * had before this field existed. * * Only 0 and 1 are accepted. A gate allowed three or four attempts stops * being a gate: enough re-runs turn any red into a green, and the value of * this field is precisely that it cannot. One retry answers the one question * worth asking — "does this reproduce in the same tree?" — and nothing more. * * Optional for the same reason {@link VerifyGate.mutex} is: contracts store * their gate list as JSON in `tasks.contract_json` and read it back with * `JSON.parse` without re-validating, so a row written before this field * existed simply has no key. */ retryOnFail?: number; /** * Whether a FLAKY outcome — failed, then passed on the retry — counts as a * pass (Issue #1772). Default false: the gate does not get weaker by opting * into a retry, it only gets a name for what happened. * * Declared per gate rather than per repository because it is a statement * about *this* gate's failure mode. `unit` failing on a random UUID that * happens to contain a forbidden substring is noise; `e2e` failing once and * passing once is usually a real race in the product. An `options`-level flag * would force those two into one answer, and would also be declarable for * gates that never opted into a retry, where it can never fire. * * `true` without `retryOnFail: 1` is a config error rather than a no-op: no * retry means no FLAKY, so the declaration could never take effect. */ flakyIsPass?: boolean; } export interface VerifyOptions { /** null when unset; the caller resolves the repository default branch. */ baseRef: string | null; skipInPrimaryCheckout: boolean; maxLogTailBytes: number; /** * Whether `work-evidence` demands a COMMIT rather than any change at all * (Issue #1628, D-4). Default false — the gate's job has always been "is * there work here to verify", and a dirty tree answers that. * * Turn it on in a repository whose delegations end in a commit (the task * contract preamble tells agents "未 commit の作業は未完了とみなされる", and * without this the gate happily passes `commits=0 uncommitted=1`, so * `RESULT passed` proved nothing about the commit). Repository-wide by * design: it is a property of how the repository is worked, not of one run. */ requireCommit: boolean; /** * Whether the built-in `env-clean` gate judges this repository's runs * (Issue #1740). Default false — every contract written before the gate * existed must keep its verdict, and a gate that starts failing runs the day * it lands is a gate that gets switched off again. * * Turning it on has a second effect: baselines are only recorded (at task * creation) while it is on, because a snapshot of the machine is a side effect * and the off state must have none. A run without a baseline reports UNKNOWN, * never a pass. */ requireEnvClean: boolean; } export interface VerifyConfig { version: 1; gates: VerifyGate[]; options: VerifyOptions; } /** * Built-in gate that answers "is there any work here to verify?". * * Declared here rather than in the runner so modules that only reason *about* * gate selection (task contracts) can name it without importing the engine. */ export declare const WORK_EVIDENCE_GATE_ID = "work-evidence"; /** * Built-in gate that reconciles the changed files against the contract's * `scope.allow` / `scope.deny` (#1546). Declared alongside work-evidence for the * same reason: `lib/tasks/contract-message.ts` has to name it while resolving a * contract's gate list, and must not pull the engine in to do so. */ export declare const SCOPE_GATE_ID = "scope"; /** * Built-in gate that reconciles the *machine* against the snapshot taken when * the task was created (#1740): listening CommandMate servers, `mcbd-*` tmux * sessions, `$HOME` and `~/.commandmate`. `scope` answers what a delegation * changed inside the repository; this answers what it changed outside one. */ export declare const ENV_CLEAN_GATE_ID = "env-clean"; /** Gate IDs reserved for built-in gates; using one in `gates` is a config error. */ export declare const RESERVED_GATE_IDS: readonly ["work-evidence", "scope", "env-clean"]; export declare const VERIFY_CONFIG_RELATIVE_PATH = ".commandmate/verify.yaml"; /** * Gate ids a default full run executes, in the order the runner records them * (Issue #2061). * * The Verification pane needs a denominator while a run is in flight — gate * rows are created as each gate starts, so "3 gates recorded" is progress, not * a total. Declared here rather than in the pane because the composition is the * runner's: `work-evidence`, then `scope`, then `env-clean` only when a * declaration switched it on, then verify.yaml's own gates in file order. * * A *default* run, which is what the pane's "run verification" button starts. * A run narrowed with `--gates` / `gateIds` executes a subset of this. * * @param contractGates gate definitions the worktree's execution contract * carries (#1791). They run after verify.yaml's, exactly as * `declaredGates()` orders them, and leaving them out understated both * the pane's progress denominator and — since #2063 made the list * selectable — which gates an operator is allowed to re-run: a run whose * only failure was a contract gate offered nothing to re-run at all. */ export declare function defaultPlannedGateIds(config: VerifyConfig, contractGates?: readonly VerifyGate[]): string[]; export declare const DEFAULT_TIMEOUT_SEC = 600; export declare const DEFAULT_MAX_LOG_TAIL_BYTES = 8192; /** * Shape a gate id may take, wherever a gate is declared. * * Exported because the task contract declares gates too (#1791) and names them * in `verify.gates`. A second copy of this expression would let the two * declaration sites drift into accepting different ids, and a contract gate the * runner refuses to resolve is a completion criterion nothing can evaluate. */ export declare const GATE_ID_PATTERN: RegExp; /** * Shape a `mutex` name may take (Issue #1771). * * Wider than {@link GATE_ID_PATTERN} because a mutex names a *resource*, not a * gate: two repositories that both bind port 60303 should be able to agree on * `port.60303` and be serialized against each other. Narrow enough that the * name is safe as a path segment — no separator, no whitespace, nothing a shell * re-interprets — because both runners turn it into * `~/.commandmate/locks/.lock`. */ export declare const GATE_MUTEX_PATTERN: RegExp; /** Keeps the lock directory name inside every filesystem's limit. */ export declare const MAX_GATE_MUTEX_LENGTH = 64; /** * Largest `retryOnFail` a gate may declare (Issue #1772). * * Deliberately 1, and deliberately a named constant rather than a literal in * one `if`: the ceiling is the feature. A gate that may re-run until it passes * reports the machine's luck, not the work. */ export declare const MAX_RETRY_ON_FAIL = 1; export declare class VerifyConfigError extends Error { readonly issues: string[]; constructor(issues: string[]); } /** * Validate a list of `{ id, command, timeoutSec }` entries. * * Shared with the task contract's `verify.gateDefinitions` (#1791), which * declares gates in the same shape and must be held to the same constraints — * id pattern, reserved ids, duplicates, integer timeout in range. Calling this * is what makes "the same constraints" a fact rather than a comment: a contract * gate accepted here is one the runner can execute and the report can name. * * Deliberately does *not* decide whether the list may be absent or empty. That * is the caller's rule: verify.yaml requires at least one gate, a contract that * declares none is the normal case. * * @param at path prefix for issue messages (`gates`, `verify.gateDefinitions`) * @returns the accepted entries; an entry that produced an issue is dropped so * the result never carries a value that failed validation */ export declare function validateGateEntries(value: unknown, at: string, issues: string[]): VerifyGate[]; /** * Read and validate `/.commandmate/verify.yaml`. * * @returns the validated config, or null when the file does not exist * @throws VerifyConfigError with every violation collected in `issues` */ export declare function loadVerifyConfig(repoPath: string): VerifyConfig | null; //# sourceMappingURL=verify-config.d.ts.map