UNPKG

kestrel.markets

Version:

A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.

192 lines (178 loc) 8.05 kB
/** * # cli/commands/replay — `replay <proofId>` (LIGHT, node-runnable): the reproduction verb (n04e.1). * * `replay <proofId>` looks the id up in the identity-bound hosted-run ledger (`~/.kestrel/hosted-runs.jsonl`, * the same store `runs list` reads, merged with the legacy CWD-local fallback — kestrel-fq78): * * - FOUND (a run THIS machine produced) → the row carries the canonical scenario slug, so * replay RE-RUNS that scenario on a fresh anonymous trial and asserts the new grade is * structurally reproducible (same metrics shape) AND its signature re-verifies → a * `REPRODUCED` verdict + the new proof URL. * - NOT FOUND (someone else's proof) → degrades HONESTLY to `verify` semantics with a one-line * notice. This respects no-leak: the public proof never carries the strategy source, so a * stranger's run can never be re-executed — but the verb stays meaningful (it verifies the * published proof instead). * * The ledger never stores the strategy SOURCE (no-leak holds locally too), so a reproduction * re-runs against the labeled stand-down baseline — deterministic for the default front-door * run, and honest for a `--plans` run (which cannot be re-executed from a receipt alone). * * LIGHT: reuses `sim.ts`'s hosted core + the local JSONL ledger (node fs only) + `verify.ts`'s * zero-trust check. No bun/chdb, no platform business logic — a thin client of the public API. */ import { HostedFunnel, resolveScenarioSlug, type ResolvedScenario, } from "../backend/hosted.ts"; import { readHostedRuns, readHostedRunsMerged, defaultHostedStorePath, LEGACY_CWD_HOSTED_STORE, type HostedRunRow, } from "../../ledger/hosted.ts"; import type { OutputCtx, GlobalFlags } from "../context.ts"; import { parseArgs } from "../args.ts"; import { CliError, EXIT } from "../errors.ts"; import { resolveSimBase, resolveCopyToken, loadStrategy, runHostedProof, type HostedProofOutcome } from "./sim.ts"; import { runVerify, verifyProofSignature, type VerifyVerdict } from "./verify.ts"; type FetchLike = (url: string, init?: RequestInit) => Promise<Response>; interface Env { readonly [k: string]: string | undefined; } /** Find the local ledger row a proof id names: by grade artifact id, proof URL, or operation id. */ export function findRun(rows: readonly HostedRunRow[], id: string): HostedRunRow | undefined { return rows.find( (r) => r.grade_artifact_id === id || r.operation_id === id || r.proof_url.endsWith(`/proof/${id}`), ); } export interface ReplayDeps { readonly fetch?: FetchLike; readonly env?: Env; readonly hostedStorePath?: string; } /** * `replay <proofId>` — reproduce a local run, or degrade to verifying a published proof. The * verdict is DATA on stdout; the exit code reflects it (0 only when the fresh run reproduced * AND its signature re-verifies, or when the degraded verify says VERIFIED). */ export async function replayCommand( argv: readonly string[], ctx: OutputCtx, globals: GlobalFlags, deps: ReplayDeps = {}, ): Promise<number> { const { positionals, flags } = parseArgs(argv, new Set(), new Set(["copy-token"])); if (positionals.length === 0) throw new CliError({ code: "USAGE", exit: EXIT.USAGE, message: "replay needs a proof id", hint: "usage: kestrel replay <proofId> (a proof this machine produced re-runs; any other is verified)", }); const id = positionals[0]!; const env = deps.env ?? (process.env as Env); // The identity-bound home store is both the WRITE target (a reproduction's fresh receipt) and the // primary READ source (kestrel-fq78). A caller may pin an explicit store (tests) — honored as-is. const storePath = deps.hostedStorePath ?? defaultHostedStorePath(env); const copyToken = resolveCopyToken(flags, env); const forward = copyToken !== undefined ? { copyToken } : {}; const base = resolveSimBase(globals, env); const client = new HostedFunnel({ baseUrl: base, ...(deps.fetch !== undefined ? { fetch: deps.fetch } : {}) }); // Resolve the proof id across the home store MERGED with the legacy CWD-local fallback, so replay // finds a run recorded from ANY directory (kestrel-fq78). An explicitly-pinned store (tests) is the // sole source — no implicit relative-path fallback leaks in. const rows = deps.hostedStorePath !== undefined ? readHostedRuns(storePath) : readHostedRunsMerged({ home: storePath, fallbacks: [LEGACY_CWD_HOSTED_STORE] }); const row = findRun(rows, id); // ── NOT in the local ledger → honest degrade to `verify` (no-leak: no strategy to replay) ── if (row === undefined) { return runVerify( client, id, ctx, forward, "no local strategy to replay — verifying the published proof instead", ); } // ── FOUND → re-run the recorded scenario on a fresh anonymous trial ── const { entries } = await client.catalog(); const resolved: ResolvedScenario | null = resolveScenarioSlug(row.slug, entries); if (resolved === null) { // The recorded scenario is gone from the catalog — reproduce is impossible; verify instead. return runVerify( client, row.grade_artifact_id, ctx, forward, `the recorded scenario ${JSON.stringify(row.slug)} is no longer in the catalog — verifying the recorded proof instead`, ); } // The ledger holds no strategy source (no-leak), so reproduce against the stand-down baseline. const strategy = loadStrategy(undefined); let outcome: HostedProofOutcome | undefined; const code = await runHostedProof({ client, resolved, strategy, ctx, hostedStorePath: storePath, quiet: true, // replay owns the report onProof: (o) => { outcome = o; }, ...forward, }); // A gated/settlement re-run already surfaced its Offer (exit 5) — pass it through. if (code !== 0) return code; if (outcome === undefined) // The re-run completed but its fresh proof is still SETTLING (transient R2 write lag, // kestrel-fzum) — there is no envelope to re-verify yet, so the reproducibility check cannot // conclude. Refuse loudly rather than fabricate a verdict; the re-run itself was not lost. throw new CliError({ code: "GENERIC", exit: EXIT.GENERIC, message: "replay re-ran the scenario, but the fresh grade proof is still settling (transient R2 write lag) — its signature cannot be re-verified yet", hint: "retry `kestrel replay` shortly; the settling warning on stderr names the re-run's operation id and grade artifact id (the proof URL is not yet readable — that is what settling means)", }); // Re-verify the FRESH proof's signature independently (zero-trust), and assert the metrics // shape reproduced (the three headline metrics are present + finite). const keys = await client.verifyKeys(forward); const sig: VerifyVerdict = verifyProofSignature( { proof_id: outcome.proofId, root: outcome.root, signature: outcome.signature, kid: outcome.kid, epoch: outcome.epoch, verificationClaim: true }, keys, ); const metricsReproduced = Number.isFinite(outcome.metrics.orderCount) && Number.isFinite(outcome.metrics.fillCount) && Number.isFinite(outcome.metrics.realizedPnl); const reproduced = sig === "VERIFIED" && metricsReproduced; const verdict = reproduced ? "REPRODUCED" : "NOT_REPRODUCED"; if (ctx.mode === "json") { process.stdout.write( JSON.stringify({ schema: "kestrel.replay/v1", verdict, scenario: row.slug, original_proof: row.proof_url, new_proof: outcome.proofUrl, signature: sig, metrics: outcome.metrics, }) + "\n", ); } else { process.stdout.write( [ verdict, `scenario=${row.slug}`, `original=${row.proof_url}`, `reproduced=${outcome.proofUrl}`, `signature=${sig}`, ].join("\t") + "\n", ); } return reproduced ? 0 : EXIT.GENERIC; }