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.

284 lines (262 loc) 15 kB
/** * # cli/commands/certify — `certify <proof-url>` (LIGHT, node-runnable): OPEN RECOMPUTATION (G10, kestrel-8kvs). * * "Don't trust us — run it." From the PUBLISHED npm package, `kestrel certify <proof-url>` fetches the * proof's OPEN-RECOMPUTATION evidence bundle (`GET /proof/{id}/evidence`), RE-PROJECTS the Blotter * LOCALLY with the same shipped projector the platform ran, and asserts the local projection reproduces * the hosted PUBLISHED Blotter **byte-identically** — the determinism leg of platform launch gate G10 * (the L1 minimum, clearly labeled; judged-Grade recomputation follows by L3). Without this, "certified" * in landing copy is a G6 claims violation. * * The check is `serialize(project([...readBusText(bundle.bus)])) === bundle.blotter`, plus a local * pure-projector self-leg (project the same bus twice ⇒ byte-identical, ADR-0011). Two byte-identity * claims, both reported: * - `determinism_self` — the OSS projector is a pure function of the bus (project twice, same bytes); * - `reproduced` — the OSS re-projection equals the platform's PUBLISHED Blotter, byte-for-byte. * * Verdicts (DATA on stdout): `REPRODUCED` (both hold — exit 0) · `MISMATCH` (the local re-projection * differs from the published Blotter — genuine divergence, exit 1) · `NON_DETERMINISTIC` (the local * projector is not pure — fail-closed, exit 1) · `NOT_FOUND` (no re-projectable free-tier bundle for * this id — exit 3). Fail-closed everywhere: a bundle whose bus is not a graded bus (`project` refuses * it) is a loud typed refusal, never a fabricated verdict. * * BOUNDARY: `certify` re-projects the PUBLISHED Bus with OSS code only — it imports no platform business * logic and holds no key. It is the OSS half of G10 (SEAM edge); the platform half (kestrel-markets-tk82) * serves the identical `kestrel.evidence-bundle/v1` shape. LIGHT: `project`/`serialize`/`readBusText` and * `node:crypto`-free `sha256` — no bun/chdb, no engine drive, so it runs under bun-less node via npx. */ import { project, serialize, type Blotter } from "../../blotter/index.ts"; import { toProtocolBlotter } from "../../blotter/protocol-view.ts"; import { readBusText } from "../../bus/index.ts"; import { sha256 } from "../../crypto/sha256.ts"; // The numbers↔tape / numbers↔root binding legs (OSS-ADR-0051) recompute from the SAME dependency-free // protocol Judge + pure root derivation an outsider runs — no platform code, keeping `certify` LIGHT. import { grade, type GradeResult } from "../../protocol/index.ts"; import type { SessionId } from "../../protocol/session.ts"; import { certifiedRoot, ROOT_HASH_PREFIX } from "../../protocol/attestation.ts"; import { HostedFunnel, type PublishedEvidenceBundle } from "../backend/hosted.ts"; import type { OutputCtx, GlobalFlags } from "../context.ts"; import { parseArgs } from "../args.ts"; import { CliError, EXIT } from "../errors.ts"; import { resolveSimBase, resolveCopyToken } from "./sim.ts"; import { parseProofRef } from "./verify.ts"; type FetchLike = (url: string, init?: RequestInit) => Promise<Response>; interface Env { readonly [k: string]: string | undefined; } /** The four honest verdicts an open-recomputation check resolves to. */ export type CertifyVerdict = "REPRODUCED" | "MISMATCH" | "NON_DETERMINISTIC" | "NOT_FOUND"; /** Verdict → CLI exit: REPRODUCED passes; every other outcome is a nonzero, machine-legible code. */ const VERDICT_EXIT: Readonly<Record<CertifyVerdict, number>> = { REPRODUCED: 0, MISMATCH: EXIT.GENERIC, // the local re-projection differs from the published Blotter — genuine divergence NON_DETERMINISTIC: EXIT.GENERIC, // the local projector is not pure — fail-closed (ADR-0011) NOT_FOUND: EXIT.NOT_FOUND, // no re-projectable free-tier evidence bundle for this id }; /** `sha256:`-prefixed content hash of a UTF-8 string (the legible, byte-identity-equivalent digest). */ function contentHash(text: string): string { return `sha256:${sha256(text)}`; } /** * The PURE open-recomputation core: re-project the published Bus LOCALLY and compare to the published * Blotter, byte-for-byte. Deterministic — no wall clock, no RNG. `project` fail-closes (throws) on a * bus that is not a GRADED bus (the two-bus rule, ADR-0011); the caller turns that into a loud refusal * rather than a verdict. Returns both byte-identity legs so the verdict never over-claims. */ export interface ReprojectionResult { readonly verdict: Exclude<CertifyVerdict, "NOT_FOUND">; readonly determinismSelf: "pass" | "fail"; readonly reproduced: boolean; readonly busHash: string; readonly publishedBlotterHash: string; readonly localBlotterHash: string; /** The re-projected engine Blotter object (the pure `project(bus)` result) — carried so the numbers↔tape * binding leg can grade it (`grade(reproject(bus))`, OSS-ADR-0051) without re-projecting a second time. */ readonly localBlotter: Blotter; } export function reproject(bundle: PublishedEvidenceBundle): ReprojectionResult { const events = [...readBusText(bundle.bus)]; // The local projection, computed TWICE — the pure-projector self-leg (ADR-0011: same bus ⇒ // byte-identical Blotter). `project` refuses a non-graded bus loudly; that throw propagates. const localBlotter = project(events); const local = serialize(localBlotter); const localAgain = serialize(project(events)); const determinismSelf: "pass" | "fail" = local === localAgain ? "pass" : "fail"; // The open-recomputation claim: the local re-projection reproduces the PUBLISHED Blotter byte-for-byte. const reproduced = local === bundle.blotter; const verdict: Exclude<CertifyVerdict, "NOT_FOUND"> = determinismSelf === "fail" ? "NON_DETERMINISTIC" : reproduced ? "REPRODUCED" : "MISMATCH"; return { verdict, determinismSelf, reproduced, busHash: contentHash(bundle.bus), publishedBlotterHash: contentHash(bundle.blotter), localBlotterHash: contentHash(local), localBlotter, }; } /** A binding-leg outcome (OSS-ADR-0051): `yes` — the recomputation matches the published value; `no` — * it does NOT (a tampered published number); `unknown` — the bundle carried nothing to bind (the leg is * additive-optional and is skipped). */ export type BindLeg = "yes" | "no" | "unknown"; /** Do two metric maps agree exactly? Both come off the Judge's shared 1e-8 grid, so equality is exact * (not tolerance-based): same keys, same finite values. */ function sameMetrics(a: Readonly<Record<string, number>>, b: Readonly<Record<string, number>>): boolean { const ak = Object.keys(a); const bk = Object.keys(b); if (ak.length !== bk.length) return false; for (const k of ak) if (!Object.is(a[k], b[k])) return false; return true; } /** * The numbers↔tape binding (OSS-ADR-0051): grade the re-projected tape with the SAME dependency-free OSS * Judge and bind the result to the bundle's PUBLISHED numbers. `unknown` when the bundle publishes none. * Pure — no wall clock, no RNG. Grades the already-projected engine Blotter through the light * `toProtocolBlotter` view, so the numbers an outsider re-derives from the tape must equal the published ones. */ export function numbersBindTapeOf(result: ReprojectionResult, bundle: PublishedEvidenceBundle): BindLeg { if (bundle.publishedGrade === undefined) return "unknown"; const local: GradeResult = grade([toProtocolBlotter(bundle.publishedGrade.subjectSessionId as SessionId, result.localBlotter)]); return sameMetrics(local.metrics, bundle.publishedGrade.metrics) ? "yes" : "no"; } /** * The numbers↔root binding (OSS-ADR-0051): recompute `certifiedRoot(coveredFields)` and bind it to the * bundle's published `root` (stripping the `sha256:` display prefix). `unknown` when the bundle carries no * covered fields or no root. A tampered published number breaks this recompute (→ `no`). Fail-safe: a * derivation error resolves to `unknown` (inability to adjudicate), never a fabricated `yes`. */ export async function rootBindOf(bundle: PublishedEvidenceBundle): Promise<BindLeg> { if (bundle.coveredFields === undefined || bundle.root === undefined) return "unknown"; try { const bare = bundle.root.startsWith(ROOT_HASH_PREFIX) ? bundle.root.slice(ROOT_HASH_PREFIX.length) : bundle.root; const recomputed = await certifiedRoot(bundle.coveredFields); return recomputed === bare ? "yes" : "no"; } catch { return "unknown"; } } export interface CertifyDeps { readonly fetch?: FetchLike; readonly env?: Env; } /** The structured report `certify` emits (the `--json` twin + the source of the text line). */ export interface CertifyReport { readonly verdict: CertifyVerdict; readonly proof_id: string; /** The gate + leg this check settles — the L1 minimum, honestly labeled. */ readonly leg: "determinism"; readonly determinism_self?: "pass" | "fail"; readonly reproduced?: boolean; readonly bus_sha256?: string; readonly published_blotter_sha256?: string; readonly local_blotter_sha256?: string; readonly engine_version?: string; readonly fill_model?: string; /** The numbers↔tape binding leg (OSS-ADR-0051): does `grade(reproject(bus))` reproduce the PUBLISHED * numbers? `no` (a tampered published number) forces MISMATCH; `unknown` when the bundle published none. */ readonly numbers_bind_tape?: BindLeg; /** The numbers↔root binding leg (OSS-ADR-0051): does `certifiedRoot(coveredFields)` equal the published * root? `no` (a tampered published number) forces MISMATCH; `unknown` when the bundle carried no root. */ readonly root_bind?: BindLeg; } function emit(ctx: OutputCtx, report: CertifyReport): void { if (ctx.mode === "json") { process.stdout.write(JSON.stringify({ schema: "kestrel.certify/v1", ...report }) + "\n"); return; } const parts = [report.verdict, `proof=${report.proof_id}`, `leg=${report.leg}(L1)`]; if (report.reproduced !== undefined) parts.push(`reproduced=${report.reproduced}`); if (report.published_blotter_sha256 !== undefined) parts.push(`published_blotter=${report.published_blotter_sha256}`); if (report.local_blotter_sha256 !== undefined) parts.push(`local_blotter=${report.local_blotter_sha256}`); if (report.verdict === "MISMATCH" && report.numbers_bind_tape === "no") parts.push("note=published-numbers-differ-from-grade-of-the-reprojected-tape"); else if (report.verdict === "MISMATCH" && report.root_bind === "no") parts.push("note=published-numbers-do-not-rebuild-the-signed-root"); else if (report.verdict === "MISMATCH") parts.push("note=local-reprojection-differs-from-the-published-blotter"); if (report.verdict === "NON_DETERMINISTIC") parts.push("note=the-local-projector-is-not-a-pure-function-of-the-bus"); if (report.numbers_bind_tape !== undefined) parts.push(`numbers-bind-tape=${report.numbers_bind_tape}`); if (report.root_bind !== undefined) parts.push(`root-bind=${report.root_bind}`); process.stdout.write(parts.join("\t") + "\n"); } /** * The open-recomputation path: fetch the evidence bundle, re-project the Bus LOCALLY, byte-compare to * the published Blotter, emit the verdict as data, and return the verdict's exit code. A bundle whose * Bus is not a graded bus (`project` refuses) is a loud typed refusal (never a fabricated verdict). */ export async function runCertify( client: HostedFunnel, id: string, ctx: OutputCtx, forward: { readonly copyToken?: string }, ): Promise<number> { const bundle = await client.evidenceBundle(id, forward); if (bundle === null) { emit(ctx, { verdict: "NOT_FOUND", proof_id: id, leg: "determinism" }); return VERDICT_EXIT.NOT_FOUND; } let result: ReprojectionResult; try { result = reproject(bundle); } catch (e) { // The Bus is not a re-projectable GRADED bus (the two-bus rule / a malformed tape): the projector // refused. Refuse LOUDLY rather than emit a bogus MISMATCH — the bundle itself is uncertifiable. throw new CliError({ code: "GENERIC", exit: EXIT.GENERIC, message: `certify could not re-project the evidence bus for proof ${id}: ${(e as Error).message}`, hint: "the published evidence bundle's bus is not a graded bus the OSS projector can re-project (ADR-0011)", }); } // The numbers-binding legs (OSS-ADR-0051), computed only when the reprojection itself held (a bundle // that already FAILED determinism / byte-reproduction is uncertifiable — do not over-claim a binding on // top of it). A `no` on either leg is a tampered published number: it forces MISMATCH even though the // Blotter bytes reproduced. Both are additive-optional (UNKNOWN when the bundle publishes nothing to bind). const numbersBindTape: BindLeg = result.verdict === "REPRODUCED" ? numbersBindTapeOf(result, bundle) : "unknown"; const rootBind: BindLeg = result.verdict === "REPRODUCED" ? await rootBindOf(bundle) : "unknown"; const bindingFailed = numbersBindTape === "no" || rootBind === "no"; const verdict: CertifyVerdict = result.verdict === "REPRODUCED" && bindingFailed ? "MISMATCH" : result.verdict; emit(ctx, { verdict, proof_id: id, leg: "determinism", determinism_self: result.determinismSelf, reproduced: result.reproduced, bus_sha256: result.busHash, published_blotter_sha256: result.publishedBlotterHash, local_blotter_sha256: result.localBlotterHash, ...(bundle.engine_version !== undefined ? { engine_version: bundle.engine_version } : {}), ...(bundle.fill_model !== undefined ? { fill_model: bundle.fill_model } : {}), numbers_bind_tape: numbersBindTape, root_bind: rootBind, }); return VERDICT_EXIT[verdict]; } /** * `certify` — open recomputation of a PUBLISHED proof (gate G10). Fetches the evidence bundle, re-projects * the Blotter locally, and asserts byte-identical reproduction of the hosted result. The verdict is DATA * on stdout (REPRODUCED/MISMATCH/NON_DETERMINISTIC/NOT_FOUND); the exit code reflects it (0 only for * REPRODUCED). Accepts a full proof URL (`https://kestrel.markets/proof/<id>`) or a bare id. */ export async function certifyCommand( argv: readonly string[], ctx: OutputCtx, globals: GlobalFlags, deps: CertifyDeps = {}, ): 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: "certify needs a proof URL or id", hint: "usage: kestrel certify <https://kestrel.markets/proof/<id> | <id>>", }); const env = deps.env ?? (process.env as Env); const id = parseProofRef(positionals[0]!); 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 } : {}) }); return runCertify(client, id, ctx, forward); }