/** * Orchestrator-side worker re-verification gate (T1589 / T1586). * * Closes lie #4 from HONEST-HANDOFF-2026-04-28.md: predecessor orchestrators * trusted subagent self-reports without re-running gates. This module * re-validates a worker's claim BEFORE the orchestrator accepts completion. * * Project-agnostic: uses canonical `tool:test` resolution per ADR-061 so * pnpm/npm/cargo/pytest/go all work identically. Git operations go through * the standard `git` CLI. The audit log lives at the project's * `.cleo/audit/worker-mismatch.jsonl` (matches `force-bypass.jsonl` / * `contract-violations.jsonl` conventions). * * Wire-in: `packages/core/src/sentient/tick.ts` calls {@link reVerifyWorkerReport} * after `spawnResult.exitCode === 0` and before `writeSuccessReceipt`. A * rejection downgrades the success path to the failure-receipt path so the * task is not silently marked complete on false-success worker output. * * @task T1589 * @epic T1586 * @adr ADR-051 (evidence-based gate ritual) * @adr ADR-061 (project-agnostic tool resolution) */ /** * Self-reported outcome from a subagent worker. The orchestrator MUST NOT * trust any field here without re-verification through {@link reVerifyWorkerReport}. * * @task T1589 */ export interface WorkerReport { /** Task ID the worker claims to have completed. */ taskId: string; /** Worker's claimed success / failure outcome. */ selfReportSuccess: boolean; /** * Evidence atoms the worker captured (CLI `--evidence` syntax, e.g. * `tool:test`, `commit:;files:a.ts,b.ts`). Used for cross-checking; * re-verify always runs `tool:test` regardless of what the worker claimed. */ evidenceAtoms: string[]; /** * Files the worker claims it touched, relative to project root. Compared * against `git status --porcelain` since the last commit on the working * branch. */ touchedFiles: string[]; } /** * One mismatch between the worker's self-report and the re-verified ground * truth. Lives inside {@link WorkerMismatchAuditEntry.mismatches}. * * @task T1589 */ export interface WorkerMismatch { /** Which dimension failed: tests, files, or evidence. */ kind: 'tests' | 'files' | 'evidence'; /** What the worker claimed. */ claimed: string; /** What re-verification observed. */ actual: string; /** Short human-readable reason. */ reason: string; } /** * Append-only audit row written to `.cleo/audit/worker-mismatch.jsonl` when * {@link reVerifyWorkerReport} rejects. Each line is standalone JSON (same * convention as `force-bypass.jsonl`). * * @task T1589 */ export interface WorkerMismatchAuditEntry { /** ISO-8601 timestamp the mismatch was detected. */ timestamp: string; /** Task the worker claimed to have completed. */ taskId: string; /** Worker's claimed success boolean (echoed for audit). */ claimedSuccess: boolean; /** Files the worker claimed it touched. */ claimedFiles: string[]; /** Files git reports as modified (porcelain --short). */ actualFiles: string[]; /** Per-dimension mismatch records. */ mismatches: WorkerMismatch[]; } /** * Result of {@link reVerifyWorkerReport}. The orchestrator MUST treat * `accepted: false` as a failure and route the task into the retry/backoff * path (e.g. `writeFailureReceipt` in the sentient tick loop). * * @task T1589 */ export interface ReVerifyResult { /** True only when every re-verified dimension matches the worker's claim. */ accepted: boolean; /** Human-readable mismatch summaries (one per failed dimension). */ mismatches: string[]; /** Audit row written when `accepted === false`; `null` on acceptance. */ auditEntry: WorkerMismatchAuditEntry | null; } /** Project-relative audit log path. Mirrors `.cleo/audit/force-bypass.jsonl`. */ export declare const WORKER_MISMATCH_AUDIT_FILE = ".cleo/audit/worker-mismatch.jsonl"; /** * Options for {@link reVerifyWorkerReport}. All fields except `projectRoot` * are injection seams used by unit tests to avoid spawning real subprocesses. * * @task T1589 */ export interface ReVerifyOptions { /** Absolute path to the project root (where `.cleo/` and `.git/` live). */ projectRoot: string; /** * Override the default `tool:test` runner. Returns `{ ok: true }` when the * project test command exits 0, `{ ok: false, reason }` otherwise. Tests * inject a stub here; production calls {@link defaultRunProjectTests}. */ runProjectTests?: (projectRoot: string) => Promise; /** * Override the default `git status --porcelain` reader. Tests inject a * stub. Production calls {@link defaultListChangedFiles}. */ listChangedFiles?: (projectRoot: string) => Promise; } /** Outcome of running the project's canonical test command. */ export interface TestRunResult { ok: boolean; reason?: string; } /** * Default `tool:test` runner. Reuses {@link validateAtom} so the same * project-context.json resolution + ADR-061 evidence cache applies. * * @task T1589 * @adr ADR-061 */ export declare function defaultRunProjectTests(projectRoot: string): Promise; /** * Default git-status reader. Returns the list of paths reported by * `git status --porcelain` since the last commit. Used to fact-check the * worker's `touchedFiles` claim. * * @task T1589 */ export declare function defaultListChangedFiles(projectRoot: string): Promise; /** * Re-verify a subagent worker's self-report against ground truth. * * Performs three independent checks and rejects on any hard-evidence * mismatch: * * 1. **Test status** — runs `tool:test` (project-resolved per ADR-061) and * compares the exit code against the worker's `selfReportSuccess` claim. * Worker says success but tests fail → reject. * 2. **Touched files** — compares `touchedFiles` against `git status * --porcelain`. Sets must match exactly (order-independent). Counts * matter: worker says 3 but git shows 5 → reject. * 3. **Evidence atoms** — sanity-check that the worker actually captured * evidence (non-empty list when claiming success). Does NOT re-validate * every atom (that's `revalidateEvidence`'s job at `cleo complete`). * * On rejection, writes one append-only line to `.cleo/audit/worker-mismatch.jsonl` * with the full claimed-vs-actual diff. The audit write is best-effort; an * error there does not change the rejection verdict. * * @param report - The worker's self-report (untrusted input). * @param options - Project root + injectable test/git seams. * @returns Acceptance verdict + machine-readable mismatch detail. * * @task T1589 * @epic T1586 * * @example * ```ts * const result = await reVerifyWorkerReport( * { taskId: 'T123', selfReportSuccess: true, * evidenceAtoms: ['tool:test'], touchedFiles: ['src/a.ts'] }, * { projectRoot: '/path/to/project' }, * ); * if (!result.accepted) throw new Error(result.mismatches.join('; ')); * ``` */ export declare function reVerifyWorkerReport(report: WorkerReport, options: ReVerifyOptions): Promise; /** * Append one {@link WorkerMismatchAuditEntry} to `.cleo/audit/worker-mismatch.jsonl`. * * Errors are swallowed: an audit-write failure must never change the * rejection verdict (matches `appendOwnerOverrideAudit` / * `appendContractViolation`). * * @internal */ export declare function appendWorkerMismatchAudit(projectRoot: string, entry: WorkerMismatchAuditEntry): void; //# sourceMappingURL=worker-verify.d.ts.map