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