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.

626 lines (585 loc) 39.6 kB
/** * # catalog/session-catalog — the public, versioned, content-addressed Session catalog (kestrel-djm.8) * * The publication-safe SYNTHETIC fixtures promoted into a VERSIONED, CONTENT-ADDRESSED public catalog: one * {@link SESSION_CATALOG} manifest of generic Session artifacts a client can RUN LOCALLY end-to-end through * djm.4's incremental Session controller ({@link openSession} → `start` → `advance` → `finalize` → Blotter → * graded bus) to reproduce each entry's pinned conformance root, byte-for-byte. The SAME manifest is * mirrorable by kestrel.markets WITHOUT rewriting, because every entry EMBEDS a djm.2 {@link CatalogEntry} * ({@link ./protocol/catalog.ts} — the ONE catalog contract, never forked) verbatim as `.entry`, and the * platform mirror consumes exactly {@link catalogEntries} — the same `CatalogEntry[]`, no rewrite. * * ## What each entry pins (djm.8 acceptance) * - content roots (content-addressed): the input TAPE root (`entry.provenance.source`) and the graded-bus * CONFORMANCE root (`sha256:<expectedConformanceRoot>`), both in `entry.contentRoots` (ADR-0010/0011: the * graded bus is the recorded truth; a projection pins here). * - provenance/rights + corpus tier: `entry.provenance` + `entry.corpusTier` (a PUBLIC tier). * - engine/Judge compatibility: `entry.requirements` (a client whose engine/Judge drift MUST fail closed). * - fill policy: `entry.permittedFillModel` (the single graded model — the two-bus rule). * - View/Rendering identities: `entry.viewIdentity` / `entry.renderingIdentity` (content-addressed handles). * - the local RUN RECIPE (`.recipe`) + the shipped generic tape (`.tape`) — the non-contract legs the LOCAL * package runs on; the platform mirror never needs them (it resolves the content roots server-side). * * ## FAIL CLOSED (djm.8 AC3) — a typed refusal, never a silent load/run * {@link loadSessionCatalog} / {@link runCatalogEntry} / {@link verifyCatalogEntry} refuse a corrupted * content root (`corrupt-content-root`), an unsupported schema (`unsupported-schema`), or an unknown fill * model (`unknown-fill-model`) with a typed {@link SessionCatalogError} — the entry is never partially loaded * or run against a drifted implementation. {@link resolveTapeEvents} is a content-addressed resolver: a tape * whose bytes no longer hash to `entry.provenance.source` is refused too. * * ## DETERMINISM (djm.8 AC2) — content-addressed, byte-stable * The conformance root IS `blotter.session.bus.sha256` (= `SimRunId(gradedBus)`, run-identity.ts) — a pure, * host-independent hash of the graded bus (no wall clock, no RNG on the record path). Same catalog + same * engine ⇒ the same root, every run. {@link runCatalogEntry} drives the REAL controller (Bus→Blotter→Grade), * so a catalog run is byte-identical to an independent `openSession → start → advance → finalize` drive. * * ── DESIGN FORK LOGGED (hard rules forbid .beads writes — recorded here like djm.4's 7dv.4 note) ── * djm.2's {@link CatalogEntry} is FROZEN and carries neither the local run recipe nor a dedicated * `expectedConformanceRoot`, so a bare `CatalogEntry[]` is not locally runnable. MINIMAL shape consistent * with djm.2 + content-addressing: a versioned WRAPPER ({@link SessionCatalogManifest}) whose entries embed a * `CatalogEntry` VERBATIM (`.entry`) alongside the execution `.recipe`, the shipped generic `.tape`, and the * bare `.expectedConformanceRoot`. The graded-bus conformance root is ALSO pinned INSIDE the djm.2 entry as a * content root (`sha256:<root>` ∈ `entry.contentRoots`), so it reconciles with djm.2's existing field without * a second contract; the mirror view is `catalogEntries()` → the same `CatalogEntry[]`. Roots are regenerable * via `scripts/catalog/compute-catalog-roots.ts`. Generic tickers only (SPX/SPY/QQQ), ARCHITECTURE §7. */ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { readBus, serializeBus, type BusEvent } from "../bus/index.ts"; import { sha256 } from "../crypto/sha256.ts"; import type { Blotter } from "../blotter/index.ts"; import type { CatalogEntry, CatalogListing, InputPin } from "../protocol/catalog.ts"; import { buildCatalogListing } from "./catalog-listing.ts"; import type { Brief, Mandate } from "../frame/index.ts"; import { PROTOCOL_VERSION } from "../protocol/index.ts"; import { BACKTEST_CONFIG, cellConfigId, declaresEnvelope, openSession, type AgentConfig, type FillModelName, type SessionController, type TimeOfDay, } from "../session/index.ts"; // ───────────────────────────────────────────────────────────────────────────── // Public surface — the versioned manifest + its embedded-entry wrapper // ───────────────────────────────────────────────────────────────────────────── /** The local RUN RECIPE the LOCAL package drives an entry on — the legs djm.2's frozen {@link CatalogEntry} * deliberately does not carry (it pins reproducibility facts, not an executable script). Passed VERBATIM to * {@link openSession}; the fill model rides {@link CatalogEntry.permittedFillModel} (the one graded model), so * it is not duplicated here. */ export interface SessionRecipe { /** The initial armed Kestrel document authored at OPEN (a NO-LLM Backtest: arm once, pass forever). */ readonly document: string; /** The authored wake vantages (ET times-of-day), strictly inside the driven window. */ readonly wakes: readonly TimeOfDay[]; /** The dollar value of one risk unit `R` for this grade. */ readonly rUsd: number; /** The cell's hard {@link Mandate} (ADR-0026) — objective + R-definition + success criterion + * bounded-risk rule the agent is graded against. Rendered in the kernel; the ONLY admission input. * OPTIONAL here (a NO-LLM backtest entry declares none); a GRADED LLM run MUST declare one. */ readonly mandate?: Mandate; /** The cell's soft {@link Brief} (ADR-0026) — directional English (goal/approach/persona), rendered * as standing context + hashed + attributed, but NEVER an admission input (the hard guard). */ readonly brief?: Brief; /** The injected constant fair-tau (years) function — a documented sim simplification. */ readonly fairTauYears: (now: number) => number | null; } /** * One public Session catalog entry: a djm.2 {@link CatalogEntry} embedded VERBATIM (`.entry` — the mirrorable * contract) alongside the shipped generic `.tape`, the local `.recipe`, and the bare `.expectedConformanceRoot` * (which is ALSO pinned as `sha256:<root>` inside `entry.contentRoots`). `id` is a stable, human-readable * identifier the executable examples / paper reproduction / practice benchmarks share. */ export interface SessionCatalogEntry { /** Stable content-addressable-independent identifier (shared across examples/paper/benchmarks). */ readonly id: string; /** The djm.2 catalog contract, embedded verbatim (the ONE contract — never forked). */ readonly entry: CatalogEntry; /** The shipped generic tape fixture basename (resolved under `fixtures/`, content-addressed to * `entry.provenance.source`). */ readonly tape: string; /** The local execution recipe (djm.2's `CatalogEntry` carries no runnable script). */ readonly recipe: SessionRecipe; /** The pinned graded-bus conformance root (a bare 64-hex sha256; also in `entry.contentRoots`). */ readonly expectedConformanceRoot: string; } /** The VERSIONED manifest: a version tag + the public entries. The version is bumped on any change to the * pinned roots / recipes / entry set, so a consumer can pin an exact catalog revision. */ export interface SessionCatalogManifest { readonly version: string; readonly entries: readonly SessionCatalogEntry[]; } /** The catalog schema version (independent of {@link PROTOCOL_VERSION}, which the embedded djm.2 entries * track). Bumped when the pinned roots / entries change. * * Bumped 1.0.0 → 1.1.0 (kestrel-a57.18): ADDITIVE, schema-stable re-pin. The graded session bus now carries * one agent-decision RECEIPT — a CONTROL `propose` per armed PLAN — so the restraint benchmark's `grade/receipts` * projector is non-empty on a real session (it was INERT in production before). No wire field was added or * removed; only the pinned conformance roots moved, by EXACTLY that +1 receipt-per-PLAN event (verified: stripping * the propose events reproduces the pre-change roots byte-for-byte). A MINOR bump is the honest SemVer signal for * a backward-additive change with an unchanged schema. Pre-launch — nobody had pinned 1.0.0 yet. No ADR mandates a * specific catalog-version policy (rg over docs/adr found none), so SemVer-additive was applied; the engine-epoch * bump that the root move requires is {@link ENGINE_VERSION} kestrel-engine/2 → /3 below. */ export const SESSION_CATALOG_VERSION = "1.1.0"; // ───────────────────────────────────────────────────────────────────────────── // Fail-closed error surface (djm.8 AC3) // ───────────────────────────────────────────────────────────────────────────── /** The typed refusal codes: a corrupted/mismatched content root, an unsupported schema, an unknown fill * model — the three fail-closed cases djm.8 mandates — plus the two m9i.27 plan-artifact refusals: a * fixed-plan cell whose pinned doc is MISSING (`plan-doc-missing`) or whose stored doc does NOT hash to its * pin (`plan-doc-mismatch`). A plan artifact gets its OWN typed refusal, distinct from a tape's * `corrupt-content-root`. */ export type SessionCatalogErrorCode = | "corrupt-content-root" | "unsupported-schema" | "unknown-fill-model" | "plan-doc-missing" | "plan-doc-mismatch"; /** A typed refusal from the Session catalog. Carries a {@link SessionCatalogErrorCode} so a caller can branch * fail-closed without string-matching a message (never a silent load/run). */ export class SessionCatalogError extends Error { readonly code: SessionCatalogErrorCode; constructor(code: SessionCatalogErrorCode, message: string) { super(message); this.name = "SessionCatalogError"; this.code = code; } } // ───────────────────────────────────────────────────────────────────────────── // Content-addressing helpers (pure, deterministic — no wall clock, no RNG) // ───────────────────────────────────────────────────────────────────────────── const HEX64 = /^[0-9a-f]{64}$/; /** * The supported catalog wire schema for an embedded djm.2 entry (e.g. `kestrel.session/0.2`). A different * schema fails closed as `unsupported-schema`. * * HONEST COUPLING (kestrel-b83) — this INTENTIONALLY and correctly tracks {@link PROTOCOL_VERSION}; it is NOT * the 2t0 silent-drift bug that {@link ENGINE_VERSION} / {@link JUDGE_VERSION} froze. Those are grade/replay * EPOCHS whose behaviour can change independently of the wire minor, so they must be frozen. This tag, by * contrast, IS the wire-protocol shape: `kestrel.session/<minor>` names the exact `CatalogEntry` shape a client * parses, so a session-schema change IS a protocol-minor change by definition — the two CANNOT move * independently, and freezing this to an epoch decoupled from the protocol would be the dishonest move (a * schema tag diverging from the protocol it names). It therefore correctly tracks PROTOCOL_VERSION, and AC1 * pins `entry.schema === kestrel.session/${PROTOCOL_VERSION}`. The only b83 gap was that this coupling was * SILENT; this comment makes it explicit. */ const SUPPORTED_SCHEMA = `kestrel.session/${PROTOCOL_VERSION}`; /** The known fill models an entry may be graded under (RUNTIME §6). Any other name fails closed as * `unknown-fill-model` — the entry is never run against an unknown model. */ const KNOWN_FILL_MODELS: readonly FillModelName[] = ["strict-cross-v1", "maker-fair-v1"]; /** The content address of a bus (tape or graded): the codebase's ONE bus serializer → the ONE portable * sha256 (`SimRunId`/`blotter.session.bus.sha256` use the identical convention), so a tape root here and a * conformance root there are the same kind of content id. */ function busRoot(events: readonly BusEvent[]): string { return sha256(serializeBus(events)); } /** Strip the `sha256:` prefix off a djm.2 {@link import("../protocol/catalog.ts").ContentRoot} handle, or * `null` when it is not a well-formed `sha256:<64-hex>` address. */ function bareRootOf(handle: string): string | null { const m = /^sha256:([0-9a-f]{64})$/.exec(handle); return m === null ? null : m[1]!; } function msgOf(e: unknown): string { return e instanceof Error ? e.message : String(e); } /** * The absolute path to a shipped tape fixture, resolved relative to THIS module — so it resolves * whether the package is consumed from `src/` under Bun or from the bundled `dist/` under node. * * `fileURLToPath(import.meta.url)`, NOT `import.meta.dir`: the latter is a Bun-ism that * `bun build --target=node` passes through VERBATIM, and under node it is `undefined`. That made * `join(undefined, …)` throw a raw TypeError deep inside the catalog — surfacing to an agent as * `corrupt-content-root` ("your content root is corrupt") when the truth was "this host has no Bun" * (kestrel-mkn review B1). The node-portable form is the only one that can be trusted in shipped code. */ function fixturePath(tape: string): string { return join(dirname(fileURLToPath(import.meta.url)), "fixtures", tape); } // ───────────────────────────────────────────────────────────────────────────── // Plan-doc / config INPUT PIN — content-addressed, recoverable, fail-closed (kestrel-m9i.27) // ───────────────────────────────────────────────────────────────────────────── /** * Content-address a FIXED plan doc: `sha256:` + the doc's OWN sha256 — the run-identity/SimRunId * convention (`busRoot` / `deriveConfigId` use the identical one sha256), NOT a name. Deterministic: the * SAME doc ⇒ the SAME pin; a CHANGED doc ⇒ a CHANGED pin — so a plan-doc difference is VISIBLE in the pin * (two different docs authored under one plan NAME get different pins, unlike the name-only `plan_instance` * id that collides them). Pure — no wall clock, no RNG. */ export function planDocPin(text: string): string { return `sha256:${sha256(text)}`; } /** * Derive the {@link InputPin} a cell pins from its run config + authored doc (kestrel-m9i.27). Discriminates * on {@link declaresEnvelope} — the SAME split the run-identity cell axis uses, so the pin and the cell axis * never drift: * - a NO-LLM Backtest declares no a57.14 envelope ⇒ there IS a fixed plan doc ⇒ pin the DOC * ({@link planDocPin} — recoverable byte-for-byte); * - a live-authoring config declares an envelope ⇒ the agent authors the plan LIVE ⇒ pin the CONFIG * (the {@link cellConfigId cell ConfigId} as a content root — the reproducible input is the config, not * a doc). * Pure — no wall clock, no RNG. */ export function deriveInputPin(config: AgentConfig, planDoc: string): InputPin { if (declaresEnvelope(config)) { return { kind: "agent-authored", config: `sha256:${cellConfigId(config)}` }; } return { kind: "fixed-plan", planDoc: planDocPin(planDoc) }; } /** * Recover a fixed-plan cell's EXACT authored plan doc from the manifest, content-address VERIFIED: the * stored doc (`e.recipe.document` — the content-addressed store the LOCAL package ships) is returned ONLY * when its sha256 equals the pinned `planDoc` hash, so the returned string is BYTE-FOR-BYTE the authored * plan the run drove on. A pin that is missing or agent-authored (no recoverable doc) fails closed as * `plan-doc-missing`; a stored doc that does NOT hash to its pin fails closed as `plan-doc-mismatch` — * never a silently-unverified doc (a benchmark cell is never treated as reproducible on trust). Pure. */ export function resolvePlanDoc(e: SessionCatalogEntry): string { const pin = e.entry.inputPin; if (pin === undefined || pin.kind !== "fixed-plan") { throw new SessionCatalogError( "plan-doc-missing", `entry ${e.id}: no fixed-plan doc to resolve (${pin === undefined ? "no inputPin" : `pin kind ${pin.kind}`})`, ); } const doc = e.recipe.document; const expected = bareRootOf(pin.planDoc); const actual = sha256(doc); if (expected === null || actual !== expected) { throw new SessionCatalogError( "plan-doc-mismatch", `entry ${e.id}: stored plan doc (sha256 ${actual}) does not match pinned ${JSON.stringify(pin.planDoc)}`, ); } return doc; } /** * Fail-closed verification of a cell's plan artifact (kestrel-m9i.27): a fixed-plan cell MUST pin its doc * AND the stored doc MUST hash to the pin; an agent-authored cell MUST pin a well-formed config content * root. A MISSING pin, a MISMATCHED doc, or a malformed config pin throws a typed {@link SessionCatalogError} * (`plan-doc-*`) — the cell is flagged incomplete, NEVER silently treated as reproducible. A shipped * {@link SessionCatalogEntry} drives a NO-LLM Backtest, so it MUST carry a fixed-plan pin whose doc is * recoverable. Pure — no side effects, no wall clock. */ export function verifyPlanArtifact(e: SessionCatalogEntry): void { const pin = e.entry.inputPin; if (pin === undefined) { throw new SessionCatalogError( "plan-doc-missing", `entry ${e.id}: fixed-plan cell carries no input pin — its authored plan doc is unrecoverable (fail closed)`, ); } if (pin.kind === "fixed-plan") { resolvePlanDoc(e); // re-hash the stored doc against the pin (throws plan-doc-mismatch on any drift) return; } // agent-authored: no fixed doc — the reproducible input is the CONFIG; verify it is a real content root. if (bareRootOf(pin.config) === null) { throw new SessionCatalogError( "plan-doc-mismatch", `entry ${e.id}: agent-authored config pin ${JSON.stringify(pin.config)} is not a sha256 content root`, ); } } /** * Fail-closed structural validation of ONE entry, independent of running it: the embedded schema is * supported, the fill model is known, the pinned conformance root is a bare 64-hex that is ALSO * content-addressed into `entry.contentRoots` (the graded bus is the recorded truth), AND the m9i.27 plan * artifact is present + content-verified ({@link verifyPlanArtifact}). Throws a typed * {@link SessionCatalogError} on the first violation — the entry is never partially accepted. */ function assertEntryValid(e: SessionCatalogEntry): void { if (e.entry.schema !== SUPPORTED_SCHEMA) { throw new SessionCatalogError( "unsupported-schema", `entry ${e.id}: schema ${JSON.stringify(e.entry.schema)} is not the supported ${JSON.stringify(SUPPORTED_SCHEMA)}`, ); } if (!KNOWN_FILL_MODELS.includes(e.entry.permittedFillModel as FillModelName)) { throw new SessionCatalogError( "unknown-fill-model", `entry ${e.id}: permittedFillModel ${JSON.stringify(e.entry.permittedFillModel)} is not a known fill model`, ); } if (!HEX64.test(e.expectedConformanceRoot)) { throw new SessionCatalogError( "corrupt-content-root", `entry ${e.id}: expectedConformanceRoot ${JSON.stringify(e.expectedConformanceRoot)} is not a 64-hex sha256`, ); } if (!e.entry.contentRoots.includes(`sha256:${e.expectedConformanceRoot}`)) { throw new SessionCatalogError( "corrupt-content-root", `entry ${e.id}: conformance root sha256:${e.expectedConformanceRoot} is not pinned in entry.contentRoots`, ); } // m9i.27: the plan artifact must be pinned + content-verified — a fixed-plan cell whose authored doc is // missing or does not hash to its pin is refused here (never silently reproducible), on BOTH the load path // and the run path (both flow through assertEntryValid). verifyPlanArtifact(e); } // ───────────────────────────────────────────────────────────────────────────── // Load / mirror (djm.8 AC1) // ───────────────────────────────────────────────────────────────────────────── /** * Fail-closed load + validate a manifest (the shipped {@link SESSION_CATALOG} by default): every entry is * structurally validated ({@link assertEntryValid}) AND its tape is content-address-verified * ({@link resolveTapeEvents}) before any is returned — a corrupt/unsupported/unknown entry throws a typed * {@link SessionCatalogError}, never a partial load. Returns the validated {@link SessionCatalogEntry} list. */ export function loadSessionCatalog( manifest: SessionCatalogManifest = SESSION_CATALOG, ): readonly SessionCatalogEntry[] { for (const e of manifest.entries) { assertEntryValid(e); resolveTapeEvents(e); // content-address the tape (throws corrupt-content-root on a byte mismatch) } return manifest.entries; } /** * The platform-mirror view (djm.8 AC1 / AC5): the validated manifest projected to the SAME djm.2 * `CatalogEntry[]` the managed backend consumes — no rewrite, no second contract. kestrel.markets mirrors * exactly this. */ export function catalogEntries( manifest: SessionCatalogManifest = SESSION_CATALOG, ): readonly CatalogEntry[] { return loadSessionCatalog(manifest).map((e) => e.entry); } // ───────────────────────────────────────────────────────────────────────────── // Content-addressed tape resolver (djm.8 AC3/AC4) // ───────────────────────────────────────────────────────────────────────────── /** * Resolve an entry's shipped generic tape to its {@link BusEvent} stream, content-addressed: the tape bytes * are read + re-serialized canonically, and the root MUST equal `entry.provenance.source` — a mismatch (a * corrupted or swapped tape) fails closed as `corrupt-content-root`, never a silent load. Pure + deterministic. */ export function resolveTapeEvents(e: SessionCatalogEntry): readonly BusEvent[] { let events: readonly BusEvent[]; try { events = [...readBus(fixturePath(e.tape))]; } catch (err) { throw new SessionCatalogError("corrupt-content-root", `entry ${e.id}: tape ${e.tape} is unreadable (${msgOf(err)})`); } const expected = bareRootOf(e.entry.provenance.source); const actual = busRoot(events); if (expected === null || actual !== expected) { throw new SessionCatalogError( "corrupt-content-root", `entry ${e.id}: tape ${e.tape} content root ${actual} does not match pinned ${e.entry.provenance.source}`, ); } return events; } // ───────────────────────────────────────────────────────────────────────────── // Run / verify one entry through djm.4's REAL Session controller (djm.8 AC2) // ───────────────────────────────────────────────────────────────────────────── /** Drive ONE entry end-to-end through djm.4's incremental controller: OPEN → author the recipe document → * pass every delivered Wake → finalize. IDENTICAL sequence to a bare fixed-plan drive, so a catalog run is * byte-identical to an independent `openSession → start → advance → finalize`. */ async function driveEntry(e: SessionCatalogEntry, events: readonly BusEvent[]): Promise<Blotter> { const ctrl: SessionController = openSession({ events, config: BACKTEST_CONFIG, wakes: e.recipe.wakes, fillModel: e.entry.permittedFillModel as FillModelName, rUsd: e.recipe.rUsd, ...(e.recipe.mandate !== undefined ? { mandate: e.recipe.mandate } : {}), ...(e.recipe.brief !== undefined ? { brief: e.recipe.brief } : {}), fairTauYears: e.recipe.fairTauYears, }); await ctrl.start(); let res = await ctrl.advance({ kind: "authored", document: e.recipe.document }); if (!res.ok) { throw new SessionCatalogError("corrupt-content-root", `entry ${e.id}: initial authoring refused (${res.diagnostic})`); } while (res.ok && res.next !== null) { res = await ctrl.advance({ kind: "pass" }); if (!res.ok) { throw new SessionCatalogError("corrupt-content-root", `entry ${e.id}: wake advance refused (${res.diagnostic})`); } } return (await ctrl.finalize()).blotter; } /** * Run ONE entry LOCALLY end-to-end through the REAL incremental Session path (djm.4's controller over * `runSimulateSession` → Bus → Blotter → Grade) and return its graded {@link Blotter} + `conformanceRoot` * (= `blotter.session.bus.sha256` = `SimRunId`). Fail-closed FIRST ({@link assertEntryValid} + * {@link resolveTapeEvents}), so an unknown fill model / corrupted root / unsupported schema is refused before * anything runs. Deterministic — same entry + same engine ⇒ the same root. */ export async function runCatalogEntry( e: SessionCatalogEntry, ): Promise<{ readonly blotter: Blotter; readonly conformanceRoot: string }> { assertEntryValid(e); const events = resolveTapeEvents(e); const blotter = await driveEntry(e, events); return { blotter, conformanceRoot: blotter.session.bus.sha256 }; } /** * Run ONE entry and VERIFY its produced conformance root against the manifest's pinned root, fail-closed: a * drift (the produced root differs from `expectedConformanceRoot`, or is not pinned in `entry.contentRoots`) * throws a typed `corrupt-content-root`. Returns the verified root on success. This is the reproducibility * contract a client checks before trusting a grade. * * HONEST DRIFT (kestrel-2t0): the pins are golden-like ("as of {@link ENGINE_VERSION}, entry E ⇒ root Y"), so * a produced-root mismatch is the SAME signal as an engine change that outran the frozen epoch. Its message is * therefore ACTIONABLE — it names ENGINE_VERSION (bump it) and `scripts/catalog/compute-catalog-roots.ts` * (regenerate the pins) — rather than a bare hash mismatch that reads as a mysterious CI red. The * determinism-only guard (run each entry twice ⇒ identical root, tests/catalog.session.test.ts) covers the * churn-robust reproducibility question separately, so this exact check can stay strict without being brittle * to an intended engine change. */ export async function verifyCatalogEntry(e: SessionCatalogEntry): Promise<string> { const { conformanceRoot } = await runCatalogEntry(e); if (conformanceRoot !== e.expectedConformanceRoot) { throw new SessionCatalogError( "corrupt-content-root", `entry ${e.id}: produced conformance root ${conformanceRoot} does not match the root pinned as of ` + `ENGINE_VERSION ${ENGINE_VERSION} (${e.expectedConformanceRoot}). If this is an INTENDED engine change, ` + `bump ENGINE_VERSION in src/catalog/session-catalog.ts and regenerate the pins via ` + `'bun scripts/catalog/compute-catalog-roots.ts' (then re-bake the mirror via ` + `'bun scripts/sdk/bake-catalog-records.ts'); otherwise the engine is non-reproducible (fail closed).`, ); } if (!e.entry.contentRoots.includes(`sha256:${conformanceRoot}`)) { throw new SessionCatalogError( "corrupt-content-root", `entry ${e.id}: produced conformance root sha256:${conformanceRoot} is not pinned in entry.contentRoots`, ); } return conformanceRoot; } // ───────────────────────────────────────────────────────────────────────────── // The manifest — generic synthetic Session artifacts (QQQ; no leak) // ───────────────────────────────────────────────────────────────────────────── /** The generic Backtest recipe every catalog entry runs: arm one at-the-money long call at OPEN, then pass * every Wake (a NO-LLM Backtest — {@link BACKTEST_CONFIG}). Two vantages strictly inside the ~08:30–09:29 ET * window; a constant 6.5h fair-tau so `@fair`/`lean` build (a documented sim simplification, RUNTIME §4). */ const RECIPE: SessionRecipe = { document: ["PLAN atm-rider budget 0.5R ttl +60m", " WHEN phase open", " DO buy 1 atm C @ lean(bid, fair, 0.5)"].join("\n"), wakes: [ { hour: 8, minute: 50 }, { hour: 9, minute: 10 }, ], rUsd: 10_000, fairTauYears: (): number => 6.5 / (365 * 24), }; /** The ISO-8601 instant the catalog roots were recorded (a fixed, deterministic stamp — never a wall clock). */ const RECORDED_AT = "2026-07-13T00:00:00.000Z"; /** A PUBLIC corpus tier, aligned with the bus `corpus_tier` vocabulary (AC4: never a private/licensed tier). */ const CORPUS_TIER = "public-baseline"; /** * The FROZEN engine epoch a faithful replay must satisfy (kestrel-2t0). Deliberately an explicit INTEGER * epoch (`kestrel-engine/<N>`), DECOUPLED from {@link PROTOCOL_VERSION} (a wire * `major.minor`): the per-entry `expectedConformanceRoot` pins are GOLDEN-LIKE — "as of THIS engine, entry E * reproduces to root Y" — so a root-drifting fill/grade/settle change is an EXPLICIT epoch bump HERE, never a * silent drift behind an unchanged version string (the pre-2t0 bug: `kestrel-engine/${PROTOCOL_VERSION}` moved * the root while the string stayed `kestrel-engine/0.2`). Exported so the pins are honestly inspectable as "as * of ENGINE_VERSION X"; the drift path in {@link verifyCatalogEntry} names it. Bump on any change that moves a * pinned root, then regenerate the pins via `scripts/catalog/compute-catalog-roots.ts` and re-bake the mirror * via `scripts/sdk/bake-catalog-records.ts`. */ export const ENGINE_VERSION = "kestrel-engine/4"; // epoch 4 (RESTATED 2026-07-18, kestrel-ukwz / ADR-0037 §2): @fair now derives the forward from put-call parity (F* = K* + (Cmid − Pmid)) instead of assuming forward = spot, threaded into BOTH the IV inversion and the Black-76 price. Epoch 4 additionally carries the fail-closed parity/fair-vs-book guards (kestrel-ku99) and a tau derived from the CONTRACT expiry (kestrel-wcnd); both are INERT on these 0DTE fixtures (expiry == session_date, and the parity band admits every strike here), so neither moves a root on its own. This is BENCHMARK-AFFECTING — it moves every fair-priced fill on a real option book. Only the choppy-1101-strict conformance root drifted (87dbb000…181b7 → 6b523f15…f35d1); spike/trending fills do not touch the forward wedge, so their roots are byte-unchanged. Recomputed via `bun scripts/catalog/compute-catalog-roots.ts` on the corrected engine and re-baked into the mirror via `bun scripts/sdk/bake-catalog-records.ts` — no root hand-edited. Bumped (never a silent drift behind /3) per the "Bump on any change that moves a root" rule below. // epoch 3: the agent-decision RECEIPT producer (kestrel-a57.18) — the driver now writes one CONTROL `propose` per armed PLAN onto the graded session bus (the grade/receipts projector was INERT in production before), so every graded-bus conformance root below moved by EXACTLY that +1 receipt event per PLAN. Schema UNCHANGED (thesis is the honest UNSTATED encoding); tape roots unchanged. Verified: stripping the propose events + re-densifying seq reproduces the epoch-2 roots byte-for-byte. // epoch 2: bus_schema v6 — the graded META advertises the clock-honest deliberation record (ADR-0040 / kestrel-w7la.1); the graded-bus conformance roots below moved with the META bytes (tape roots unchanged) /** * The FROZEN Judge (grade-engine) epoch a faithful replay must satisfy (kestrel-b83, mirroring * {@link ENGINE_VERSION}). Deliberately an explicit INTEGER epoch (`kestrel-judge/<N>`), DECOUPLED from * {@link PROTOCOL_VERSION} (a wire `major.minor`): a Judge BEHAVIOUR change (grade algorithm, * floor/expected/realized channels, gating) is NOT a wire-protocol minor bump, so it MUST be an EXPLICIT * epoch bump HERE, never a silent drift behind an unchanged version string (the pre-b83 bug: * `kestrel-judge/${PROTOCOL_VERSION}` would let a Judge change land while the string stayed * `kestrel-judge/0.2`). Exported so `entry.requirements.judgeVersion` is honestly inspectable as "graded under * Judge epoch N". Bump on any Judge-behaviour change, then re-bake the mirror via * `scripts/sdk/bake-catalog-records.ts`. */ export const JUDGE_VERSION = "kestrel-judge/1"; /** The Backtest View / Rendering identities — content-addressed handles over stable generic descriptors (a * NO-LLM Backtest perceives the machine Frame under one fixed identity). */ const VIEW_IDENTITY = `sha256:${sha256(`kestrel.view/backtest/${PROTOCOL_VERSION}`)}`; const RENDERING_IDENTITY = `sha256:${sha256(`kestrel.rendering/machine-frame/${PROTOCOL_VERSION}`)}`; /** Assemble one entry from its generic tape + the pinned tape/conformance roots (regenerated by * `scripts/catalog/compute-catalog-roots.ts`). Both roots are content-addressed into the embedded djm.2 * entry's `contentRoots`; the tape root is also its `provenance.source`. */ function makeEntry(args: { readonly id: string; readonly tape: string; readonly fillModel: FillModelName; readonly tapeRoot: string; readonly conformanceRoot: string; }): SessionCatalogEntry { const entry: CatalogEntry = { // The addressing handle rides on the wire entry too (kestrel-33yr.2): the SAME id as the wrapping // SessionCatalogEntry, so a client that only ever sees the mirrored `catalogEntries()` projection can // open the subject by `entry.id` — the discover→open loop closes with no reach into SESSION_CATALOG. id: args.id, contentRoots: [`sha256:${args.tapeRoot}`, `sha256:${args.conformanceRoot}`], schema: SUPPORTED_SCHEMA, corpusTier: CORPUS_TIER, requirements: { engineVersion: ENGINE_VERSION, judgeVersion: JUDGE_VERSION }, permittedFillModel: args.fillModel, viewIdentity: VIEW_IDENTITY, renderingIdentity: RENDERING_IDENTITY, provenance: { source: `sha256:${args.tapeRoot}`, recordedAt: RECORDED_AT }, // m9i.27: a NO-LLM Backtest (BACKTEST_CONFIG declares no envelope) ⇒ pin the FIXED plan doc by content // hash. The doc itself ships content-addressed on `.recipe.document` (resolvePlanDoc recovers it // byte-for-byte, re-hashed against this pin). inputPin: deriveInputPin(BACKTEST_CONFIG, RECIPE.document), }; return { id: args.id, entry, tape: args.tape, recipe: RECIPE, expectedConformanceRoot: args.conformanceRoot }; } /** * The public Session catalog: one generic synthetic tape per market regime (choppy / spike / trending, all * QQQ), graded across both shipped fill models. Each entry RUNS locally to its pinned conformance root through * djm.4's controller ({@link runCatalogEntry}) and is mirrorable by the platform as {@link catalogEntries}. */ /** * The number of decision Wake frames every catalog subject presents — the honest {@link CatalogListing} * `frameCount` (kestrel-dnxq). Single-sourced from {@link RECIPE} here (the heavy, engine-side authority) and * re-exported so the LIGHT discovery-metadata table (`src/catalog/catalog-listing.ts`, which cannot import the * engine-bearing RECIPE) is guarded against drift by tests/sdk.catalog-equal-projection.test.ts (`WAKE_FRAMES` * there MUST equal this). */ export const RECIPE_WAKE_COUNT = RECIPE.wakes.length; export const SESSION_CATALOG: SessionCatalogManifest = { version: SESSION_CATALOG_VERSION, entries: [ makeEntry({ id: "choppy-1101-strict", tape: "choppy-1101.jsonl", fillModel: "strict-cross-v1", tapeRoot: "7166b08f3438c8ed6ee23b5e86ae785736f94870207a63051893a00f08b462b1", // RESTATED 2026-07-18 (kestrel-ukwz, ADR-0037 §2/Consequences "BENCHMARK-AFFECTING"): the @fair // parity-implied forward (F* = K* + (Cmid − Pmid)) moved the strict-cross fill on this choppy tape's // fair-priced OTM-put leg, so the graded-bus conformance root drifted 87dbb000…181b7 → 6b523f15…f35d1. // Recomputed by `bun scripts/catalog/compute-catalog-roots.ts` on the corrected engine (not hand-edited); // tape root unchanged; spike/trending roots unchanged (their fills do not touch the forward wedge). conformanceRoot: "6b523f157a0810ae97375e2a913906899e04f4f74f79157054d16829079f35d1", }), makeEntry({ id: "spike-1102-maker-fair", tape: "spike-1102.jsonl", fillModel: "maker-fair-v1", tapeRoot: "5b50609e931959aa911b1c576a94d81c0947ff1e0193de8a40054de969baa583", conformanceRoot: "20a1ac0ea1264b3f02449e81ee5d8d31afdd6439d6bc2629e7d56cbdf4bffb5b", }), makeEntry({ id: "trending-1103-maker-fair", tape: "trending-1103.jsonl", fillModel: "maker-fair-v1", tapeRoot: "2528243e6a4b708bf169beffe6352b95e4db0e3374891e0ab78bfee8a7b871f2", conformanceRoot: "fcd23dc97426d2c082e95fc1f86e0ff94fa0c601fd3836c000add10085f494bd", }), ], }; /** * The catalog DISCOVERY projection (kestrel-dnxq) — the `catalog` op's canonical {@link CatalogListing}[], the * offline in-process sample this transport lists. One row per {@link SESSION_CATALOG} entry, built through the * SHARED {@link buildCatalogListing} constructor (the SINGLE source of the discovery metadata, in the light * `catalog-listing.ts` module — so the LOCAL projection and the light contract server's `GET /catalog` can * never drift). Distinct from {@link catalogEntries} (the reproducibility-complete djm.2 surface resolved at * open); an id with no listing metadata fails closed there (never a silently blank listing row). */ export function catalogListing(): readonly CatalogListing[] { return buildCatalogListing(SESSION_CATALOG.entries.map((e) => e.id)); }