import { mkdirSync, writeFileSync } from 'node:fs' import { join } from 'node:path' import { agentTraceEnabled } from '../../../consts.ts' import { reclaimHomeDirPath } from '../../../paths.ts' import { type ProofResult, tailLogs, warnsAndErrors, } from '../../../proof/attestor.ts' function claimsDir(): string { return join(reclaimHomeDirPath(), 'claims') } function proofRunsDir(): string { return join(reclaimHomeDirPath(), 'proof-runs') } /** Persist everything we know about this proof attempt — input provider, * sentParams the SDK saw, full captured logs, and the final result — to * a file the dev can `cat` directly. This is the source of truth when * the LLM's summary disagrees with reality. */ function writeProofRunDump(payload: { provider: unknown hasSecretParamsRef: boolean attestorUrl?: string result: Record }): string | undefined { try { mkdirSync(proofRunsDir(), { recursive: true }) const path = join(proofRunsDir(), `${Date.now()}.json`) writeFileSync(path, JSON.stringify(payload, null, 2), 'utf8') return path } catch{ // Persistence is best-effort; never fail the proof for it. return undefined } } /** * Walk a claim object for display: * - drop any `request` field at any depth (it's huge and only useful * for low-level debugging — the on-disk claim still has it) * - convert byte-like values (Uint8Array, or arrays where every entry * is a number in [0,255]) to base64 strings so they render as * readable text instead of `{"0":12,"1":34,...}` * * The on-disk file at claimPath is the raw, unprettified claim — this * function only affects what we return in the MCP tool response. */ function prettifyClaim(value: unknown): unknown { if(value === null || value === undefined) { return value } if(value instanceof Uint8Array) { return Buffer.from(value).toString('base64') } if(Array.isArray(value)) { if(isLikelyByteArray(value)) { return Buffer.from(value as number[]).toString('base64') } return value.map(prettifyClaim) } if(typeof value === 'object') { const out: Record = {} for(const [k, v] of Object.entries(value as Record)) { if(k === 'request') { continue } out[k] = prettifyClaim(v) } return out } return value } function isLikelyByteArray(v: unknown[]): boolean { if(v.length === 0) { return false } for(const x of v) { if(typeof x !== 'number' || !Number.isInteger(x) || x < 0 || x > 255) { return false } } return true } /** * The MCP's rich `run_proof` view: persist the full claim to disk, return * identifier + extractedParameters + a prettified claim (request dropped, byte * arrays as base64) + trimmed logs, and — under `RECLAIM_AGENT_TRACE=1` — a * forensic run dump. This is the developer-facing shape; the cloud backend * projects the same {@link ProofResult} to a lean summary instead. */ export function shapeRichProofResult( result: ProofResult, opts: { provider: unknown, hasSecrets: boolean, attestorUrl?: string }, ): Record { // Persist the full claim to disk for the dev to inspect/share. let claimPath: string | undefined if(result.proof !== undefined && result.proof !== null) { try { mkdirSync(claimsDir(), { recursive: true }) claimPath = join(claimsDir(), `${Date.now()}.json`) writeFileSync(claimPath, JSON.stringify(result.proof, null, 2), 'utf8') } catch{ // Persistence is best-effort; don't fail the proof for it. claimPath = undefined } } const out: Record = { verified: result.verified, extractedValue: result.extractedValue, extractedParameters: result.extractedParameters, } if(result.identifier !== undefined) { out.identifier = result.identifier } if(result.owner !== undefined) { out.owner = result.owner } if(claimPath !== undefined) { out.claimPath = claimPath } if(result.proof !== undefined && result.proof !== null) { out.claim = prettifyClaim(result.proof) } if(result.error !== undefined) { out.error = result.error } if(result.errorKind !== undefined) { out.errorKind = result.errorKind } // Trim the LLM-visible logs — full stream goes to runLogPath. if(result.attestorLogs !== undefined && result.attestorLogs.length > 0) { const logs = result.verified ? warnsAndErrors(result.attestorLogs) : tailLogs(result.attestorLogs) if(logs.length > 0) { out.attestorLogs = logs } } if(result.sentParams !== undefined) { out.sentParams = result.sentParams } // Persist a diagnostic dump for forensics — gated behind // RECLAIM_AGENT_TRACE=1 (matches the attestor logger capture). Off by // default since happy-path runs don't need it; turn on when investigating // proof failures and the file at runLogPath will carry the input provider, // sentParams, full attestor log stream, and result. if(agentTraceEnabled()) { const runLogPath = writeProofRunDump({ provider: opts.provider, hasSecretParamsRef: opts.hasSecrets, attestorUrl: opts.attestorUrl, result: { verified: result.verified, error: result.error, errorKind: result.errorKind, extractedValue: result.extractedValue, extractedParameters: result.extractedParameters, identifier: result.identifier, owner: result.owner, sentParams: result.sentParams, attestorLogs: result.attestorLogs, claimPath, }, }) if(runLogPath !== undefined) { out.runLogPath = runLogPath } } return out }