/** * Producer-side instrumentation: turn a **nucleus-tool-proxy trace** into a * signed capability-boundary in-bounds attestation receipt. * * The trace is the real output of the tool-proxy's authorization layer: a * declared boundary (a SPIFFE principal + the task-shield allowlist, i.e. * `TaskWitness`'s allow-set) plus one record per observed tool call mirroring * the `verdict_sink` "tool_call" span fields (`actor`, `operation`, * `verdict`, `subject`, `deny_reason`). See * `crates/nucleus-policy/examples/emit_toolproxy_trace.rs` for the emitter. * * This module maps that export into the {@link Receipt} schema and signs it. * The receipt's verdict is still recomputed independently by the verifier — * the producer never gets to assert in-bounds; it only records what happened. */ import { type KeyObject } from "node:crypto"; import { type CapabilityBoundary, type TraceEvent, type Receipt } from "./attestation.js"; /** A single observed tool call, mirroring the tool-proxy `verdict_sink` span. */ export interface ToolProxyVerdictRecord { /** SPIFFE id of the actor that made the call (`nucleus.actor`). */ actor: string; /** Operation/tool invoked (`nucleus.operation`), canonical serde name. */ operation: string; /** The proxy's own decision (`nucleus.verdict`). */ verdict: "allow" | "deny" | "error"; /** The call's target/argument (`nucleus.subject`), if present. */ subject?: string; /** Proxy deny reason (`nucleus.deny_reason`), if any. */ deny_reason?: string; /** Human-readable label, if the runtime recorded one (mutable, untrusted). */ displayName?: string; } /** The full trace export the emitter produces. */ export interface ToolProxyExport { boundary: { principal: string; allowedTools: string[]; }; records: ToolProxyVerdictRecord[]; /** Optional task-shield hash (provenance; not part of the receipt). */ taskHashHex?: string; } /** A disagreement between the proxy's decision and the recomputed verdict. */ export interface CrossCheckIssue { seq: number; operation: string; proxyVerdict: string; recomputedInBounds: boolean; note: string; } export interface AttestResult { receipt: Receipt; /** * Sanity cross-check: the proxy's per-call decision should agree with the * receipt's recomputed in-bounds finding (a proxy `deny` of an out-of-policy * op should land out-of-bounds, an `allow` of an in-policy op in-bounds). A * non-empty list means the trace and the declared boundary disagree — worth * surfacing, though the receipt's recomputed verdict remains authoritative. */ crossCheck: CrossCheckIssue[]; } /** Build the declared {@link CapabilityBoundary} from a trace export. */ export declare function boundaryFromExport(exp: ToolProxyExport): CapabilityBoundary; /** Map the proxy's verdict records into receipt {@link TraceEvent}s. */ export declare function traceEventsFromRecords(records: ToolProxyVerdictRecord[]): TraceEvent[]; /** * Attest a tool-proxy run: build the boundary + events from the export and * sign the receipt. Also runs a cross-check that the proxy's own decisions * agree with the recomputed in-bounds verdict. */ export declare function attestToolProxyRun(exp: ToolProxyExport, privateKey: KeyObject, opts?: { signedAt?: string; }): AttestResult; /** Parse a trace export from JSON text. Accepts the emitter's pretty output. */ export declare function parseExport(json: string): ToolProxyExport;