/** * The proof of a passing run, and the function that checks it. * * A proof is a claim about *content*: this exact recipe and these exact scanner files passed a * full, unscoped, live validation. It is content-addressed — hash the authored bytes, nothing * else — so a copied package is still proven (same content, same claim) and a one-character edit * is not. There is no expiry: content that has not changed does not become less proven by sitting * still, and a TTL would only train people to re-run validation ritually rather than when it * means something. * * `verifyProof` is deliberately pure observation: it reports, it never enforces and never throws. * The deploy verb owns the decision; this module owns the fact. It also never parses the recipe — * the record carries the file list and the scanner roots, so verification is a re-hash and a * comparison. A recipe that no longer parses necessarily hashes differently than the one that * passed, which is the answer, not an error. * * Per-file hashes are stored so a refusal can say *what* changed. "The package differs" sends an * agent diffing blindly; "scan.py changed, scoring.py is new" is an answer. */ import type { Finding } from "./types.js"; import type { ResolvedWallet } from "./wallet.js"; /** Lives at the package root, beside the recipe. Excluded from its own fingerprint. */ export declare const PROOF_FILENAME = ".senpi-proof.json"; export interface ScannerObservation { scanner: string; reads_ok: number; reads_failed: number; signals_emitted: number; } export interface ProofRecord { record_version: 1; /** Hash over the sorted per-file map — the package's identity. */ fingerprint: string; /** Relative path → sha256, so a mismatch can name the file. */ files: Record; /** What was hashed, so verification can re-walk without parsing the recipe. */ recipe: string; scanner_roots: string[]; runtime_version: string; verdict: "PASS"; wallet?: { address: string; source: string; book: string; }; observations: ScannerObservation[]; created_at: string; } export type VerifyReason = "no_proof" | "content_changed" | "runtime_version_changed"; export interface VerifyResult { ok: boolean; reason?: VerifyReason; /** * Which named instance the verdict is about, as the caller named it — present exactly when a * multi-instance verification failed, absent for a flat package and on every pass. * * Carried structurally because the alternatives for a caller that needs it are all worse: parsing * it back out of {@link VerifyResult.finding}'s prose, or re-verifying instance by instance, which * would multiply the proof-verification metric on a refusal. Verification stops at the first * failure, so this names THAT one and says nothing about the instances after it. */ instance?: string; /** Relative paths that differ from the record: edited, added, or removed. */ changedFiles?: string[]; /** Present exactly when `ok` is false — ready for the deploy verb to surface unmodified. */ finding?: Finding; record?: ProofRecord; } /** Hash the authored content: the recipe as written, every scanner file verbatim. */ export declare function fingerprintPackage(recipePath: string, scannerRoots: string[]): { fingerprint: string; files: Record; }; export interface WriteProofInputs { recipePath: string; scannerRoots: string[]; wallet?: ResolvedWallet; observations: ScannerObservation[]; } /** * Record a passing run. Atomic — a crash mid-write must not leave a half-proof that * `verifyProof` would read as corrupt-and-therefore-absent *after* a genuine pass. * * Returns the finding to attach when the write fails: the run still passed — the package is no * less runnable because a disk was full — but the deploy gate will not find a proof, and whoever * operates the environment is the one who can do something about that. Post-verdict by design: * it must not flip a pass, even under --strict. */ export declare function writeProof(inputs: WriteProofInputs): { recorded: boolean; finding?: Finding; }; /** * Which instances a multi-instance package expects to be proven. * * A proof lives beside the recipe it is a claim about, so a package with `swing/runtime.yaml` and * `scalp/runtime.yaml` has two proofs and none at its root. The caller names the instances because * only the caller knows them — they are declared in `strategy.yaml`, which is the deploy manifest * and deliberately not something the runtime reads. Inferring them by looking for proofs on disk * would pass a package whose third instance was never validated at all. */ export interface VerifyOptions { /** Instance directory names, relative to `packageDir`. Omit for a flat single-recipe package. */ instances?: string[]; } /** * Is this package's content the content that passed? * * Pure observation for the deploy verb to act on. Every failure reason has the same remedy — * `openclaw senpi validate ` — stated on the finding, so the caller can surface it unmodified * and an agent can close the loop without knowing anything about proofs. */ export declare function verifyProof(packageDir: string, options?: VerifyOptions): VerifyResult; //# sourceMappingURL=proof.d.ts.map