/** * design/125 — **SpecContract**:spec→Verifier 闭环的机器可读任务契约(make-real 两周实战纪律的 * 一等原语化)。本模块 = 纯 core 零件:契约类型 + 三值 oracle 执行器 + frozenPaths 校验/快照/恢复。 * 装配(policy 组合、stop 把关、outcome 发射)在 `orchestration/run-spec.ts`。 * * 🔴 **信任声明(fable-M3)**:SpecContract 是 **CODE-tier** 输入 —— `oracle[].cmd` 会被亲跑(execFile, * 任意命令)。它只能来自部署作者/可信代码路径;LLM 生成的 contract 直接喂 runSpec = 不受信输入获得 * 命令执行权(越红线),禁止。contract 里的 `spec` **文本**才是 DATA-tier(runSpec 恒围栏置入 objective)。 * * 🔴 **oracle 三值(fable-M6)**:每门结果 ∈ {green, red, error} —— 判据不满足 = red;基础设施故障 * (ENOENT/timeout/maxBuffer/parse 解析不出)= **error 非 red**。假红防线:error 绝不冒充"曾经红过" * (design/73 红线② 的机器化 —— `oracleHadRedRun` 只由真 red 观察置位)。 */ /** Declarative green criteria for one oracle gate. Absent ⇒ `exitCode === 0`. All declared criteria AND. */ export interface OracleGreenSpec { /** Expected exit code. Default 0 when no `parse` is declared; with `parse`, exit code is only * checked when explicitly set here (test runners exit non-zero on failures the counts already judge). */ exitCode?: number; /** Regex (source string) that MUST match the combined stdout+stderr. */ mustMatch?: string; /** Regex (source string) that MUST NOT match the combined stdout+stderr. */ mustNotMatch?: string; /** * Parsing criteria (fable-M5/B1 一体解): extract pass/fail counts from the output. Each regex's FIRST * capture group is the integer count (e.g. `"ℹ pass (\\d+)"`). Either regex failing to match = the gate * is **error** (解析不出 ≠ red — a runner that printed nothing proves nothing). */ parse?: { passRe: string; failRe: string; }; /** * Lower bound on the parsed pass count (requires `parse`). `"baseline"` = the pass+fail TOTAL this gate * observed at preflight — the anti-「测试消失式走捷径」 gate (the model leaves tests untouched but makes * them undiscovered; harness :114 实战门, fable-B1). Numeric default when absent: 1 (a zero-test green * is vacuous). */ minPass?: number | "baseline"; } /** One mechanical adjudicator: a command run via execFile (NO shell) + declarative green criteria. */ export interface OracleGateSpec { /** Stable gate name (baseline capture and per-gate reporting key on it). */ name: string; /** argv — `cmd[0]` is the executable, the rest are arguments. execFile, never a shell. */ cmd: string[]; /** Working directory, containment-checked against the runSpec root (same rule as frozenPaths). */ cwd?: string; /** Per-gate timeout. Default 60s. */ timeoutMs?: number; green?: OracleGreenSpec; } /** design/125 D1 — the machine-readable task contract (CODE-tier input; see module doc). */ export interface SpecContract { /** Task spec text (or a file reference resolved against the runSpec root). runSpec owns the objective * framing; the spec text itself is DATA-tier and is ALWAYS fenced before injection. */ spec: string | { path: string; }; /** * Read-only specification surface. **Validation (fable-M2)**: absolute paths and `..` segments are * rejected; paths resolve against the runSpec root with a containment check; policy matching uses the * RESOLVED path. Exact paths, no globs (v1 收窄). */ frozenPaths: string[]; /** Mechanical adjudicators (serializable, checkpointable, service-boundary-safe, feed-recordable). */ oracle: OracleGateSpec[]; /** * 双向验证: preflight runs the full oracle and REQUIRES red (proves the spec has substance), capturing * baseline counts for `minPass:"baseline"` (fable-B1: the red gate and the baseline are two faces of the * same preflight). Default **true** (the discipline is the default; opting out must be explicit). * `{ exempt }` documents WHY this contract legitimately starts green (e.g. pure-refactor spec). */ requireRedStart?: boolean | { exempt: string; }; /** design/73 aggregation key. Default: `spec:`. */ taskSignature?: string; } /** Three-value gate verdict — `error` = the adjudicator itself failed to run/parse (NEVER counts as red). */ export type OracleVerdict = "green" | "red" | "error"; export interface OracleGateResult { name: string; verdict: OracleVerdict; /** Process exit code (null when the process did not run/complete — spawn error or timeout kill). */ exitCode: number | null; /** Parsed counts when `green.parse` was declared and both regexes matched. */ pass?: number; fail?: number; /** Which criterion failed / what the infrastructure error was (first-party classification string). */ reason?: string; /** Tail of the combined stdout+stderr (UNTRUSTED — the model can influence it via the code under test; * fence with `delimitUntrusted` before any model-facing use). */ outputTail: string; } /** Per-gate preflight counts backing `minPass:"baseline"` (keyed by gate name). */ export type OracleBaseline = Record; export interface OracleRunReport { /** Worst-across-gates: any `error` ⇒ error (fail-loud dominates); else any `red` ⇒ red; else green. */ verdict: OracleVerdict; gates: OracleGateResult[]; } /** * Run every gate of the contract's oracle (sequentially — gates commonly share a working tree) and fold * the three-value verdicts: any `error` ⇒ `error` (fail-loud dominates; an unfalsifiable adjudication must * never pass silently), else any `red` ⇒ `red`, else `green`. * * `cwdRoot` is the runSpec root: each gate's `cwd` resolves against it with the same containment rule as * frozenPaths (a contract cannot point its adjudicator outside the workcopy). */ export declare function runOracle(gates: OracleGateSpec[], cwdRoot: string, opts?: { baseline?: OracleBaseline; signal?: AbortSignal; }): Promise; /** Extract the per-gate baseline counts from a (preflight) report — only gates whose parse succeeded. */ export declare function captureBaseline(report: OracleRunReport): OracleBaseline; /** A coded error (`config.*` / `spec.*` convention — same shape as goal.ts's reserved-name fail-fast). */ export declare function specError(code: string, message: string): Error & { code: string; }; /** * Validate and resolve `frozenPaths` against the runSpec root (fable-M2): absolute paths and `..` * segments are rejected outright (`spec.frozen_path_invalid`); the resolved path must stay inside the * root. Returns resolved absolute paths (the ONLY form policy matching and snapshots use). */ export declare function resolveFrozenPaths(rootDir: string, frozenPaths: string[]): string[]; /** * Byte-level snapshot of the frozen surface (fable-M1: a hash can only DETECT tampering — restoring needs * the CONTENT, and the snapshot is the restore source; no external seed directory dependency). Missing * file ⇒ `spec.frozen_missing` (a frozen path that doesn't exist can't be a specification). */ export declare function snapshotFrozenPaths(resolvedPaths: string[]): Map; /** * Restore any frozen file whose on-disk bytes differ from the snapshot (belt for `echo > test/x`-style * bash writes the Write/Edit deny policy cannot see — fable-MIN2). Returns the number of files restored. * * Symlink-safe (codex 实现审 B2): a tampered entry that is no longer a regular file (the model swapped * it for a symlink pointing outside the root) is REMOVED first — the snapshot bytes are then written to * a fresh regular file, never THROUGH a link. When `rootDir` is given, the parent directory's physical * path is containment-checked too (a symlinked PARENT dir would also carry the write outside); an entry * whose parent escapes is skipped (counted) — the terminal oracle then adjudicates the missing spec * files honestly (fail-safe direction) instead of runSpec writing outside its root. */ export declare function restoreFrozenPaths(snapshot: Map, rootDir?: string): number; //# sourceMappingURL=spec-contract.d.ts.map