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
text/typescript
/**
* # 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;
}