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.

153 lines (134 loc) 10.1 kB
/** * # ledger/sim-runs — the regenerable sim_runs + agent_configs projection (kestrel-5zl.9) * * A QUERY-ONLY, REGENERABLE index over the finalized {@link ../blotter Blotter}s (the graded-run records), * surfaced additively off the ledger barrel ({@link ./index.ts}) alongside the existing run/plan/lineage * ledger — which it never touches. Unlike that ledger (a durable `bun:sqlite` store of {@link EpisodeReport}s), * this is a PURE PROJECTION: a deterministic function of the source Blotters, NOT a stateful mutable store. * "Regenerable" means projecting the same source twice yields a BYTE-IDENTICAL index — no wall clock, no RNG, * no accumulation across calls, no hidden persisted state (RUNTIME §0). It reads the source and answers * queries; it never mutates the source, the runs, or persists anything. * * Two views + the fail-closed exclusions: * * • sim_runs — one row per {@link import("../session/run-identity").SimRunId SimRunId} (the graded-bus * content hash the projector already computes as `blotter.session.bus.sha256` — a pure * read, not a second stamp), carrying its cell identity * ({@link import("../session/run-identity").cellOf CellKey}), its `ConfigId` * (`session.config_id`, m9i.2), and the a57.14 experimental **envelope** * (`session.envelope`). Queryable by run / cell / config. * • agent_configs — the AgentConfig registry: one row per DISTINCT ConfigId (so runs group by config), * carrying that config's envelope. Replicate runs (same `config_id`, byte-different graded * buses, distinct SimRunIds) collapse to ONE registry row; two distinct configs are two. * • excluded — the FAIL-CLOSED refusals: a Blotter missing its a57.14 envelope OR its m9i.2 ConfigId is * EXCLUDED-WITH-REASON, never silently indexed with a fabricated/blank identity. The row is * still keyed by the always-present graded-bus receipt (`session.bus.sha256`), so the * excluded record is NAMED — a refusal is an audit entry, never a blank. * * The keys are read from the codebase's ONE definition of each (5zl.7 `cellOf`, m9i.2 `session.config_id`, * a57.14 `session.envelope`), never re-derived here — the projection carries identity, it does not mint it. */ import type { Blotter, ExperimentalEnvelope } from "../blotter/index.ts"; import { cellOf } from "../session/run-identity.ts"; // ───────────────────────────────────────────────────────────────────────────── // Row shapes — the two views + the fail-closed exclusion, all pure values // ───────────────────────────────────────────────────────────────────────────── /** A sim_runs row — keyed by SimRunId, carrying its cell identity, ConfigId, and the a57.14 envelope. */ export interface SimRunRow { /** The run identity — `blotter.session.bus.sha256` (the SimRunId, a pure read of the graded bus receipt). */ readonly sim_run_id: string; /** The comparison cell this run belongs to — `cellOf(blotter)` (tape × fill_model × config, 5zl.7). */ readonly cell_key: string; /** The run's FULL ConfigId — `session.config_id` (m9i.2); the identity agent_configs groups by. */ readonly config_id: string; /** The a57.14 experimental envelope, carried VERBATIM from `session.envelope`. */ readonly envelope: ExperimentalEnvelope; } /** An agent_configs row — one per DISTINCT ConfigId, carrying that config's envelope. */ export interface AgentConfigRow { readonly config_id: string; readonly envelope: ExperimentalEnvelope; } /** A fail-closed exclusion — a Blotter whose a57.14/m9i.2 provenance is absent, NAMED (never blank-keyed). * `sim_run_id` still identifies WHICH record was refused (the graded-bus receipt is always present). */ export interface ExcludedRow { readonly sim_run_id: string; readonly reason: string; } /** The regenerable index — a PURE VALUE (no handle, no store): the two views + the fail-closed exclusions. * Every array is in a DETERMINISTIC sorted order (sim_runs / excluded by `sim_run_id`, agent_configs by * `config_id`), so the index is byte-stable and order-insensitive over the source set. */ export interface SimRunsIndex { readonly sim_runs: readonly SimRunRow[]; readonly agent_configs: readonly AgentConfigRow[]; readonly excluded: readonly ExcludedRow[]; } // ───────────────────────────────────────────────────────────────────────────── // The projection — a deterministic fold of the source Blotters (pure, fail-closed) // ───────────────────────────────────────────────────────────────────────────── const bySimRunId = (a: { sim_run_id: string }, b: { sim_run_id: string }): number => a.sim_run_id < b.sim_run_id ? -1 : a.sim_run_id > b.sim_run_id ? 1 : 0; const byConfigId = (a: { config_id: string }, b: { config_id: string }): number => a.config_id < b.config_id ? -1 : a.config_id > b.config_id ? 1 : 0; /** * Project the finalized Blotters into the regenerable sim_runs + agent_configs index (kestrel-5zl.9). * * A deterministic fold — for each Blotter it READS `session.bus.sha256` (the SimRunId), `cellOf(blotter)`, * `session.config_id`, and `session.envelope`; nothing is stamped back and nothing is mutated. A Blotter that * carries BOTH a complete a57.14 envelope AND a non-empty m9i.2 ConfigId becomes a sim_runs row and registers * its agent_configs row; a Blotter missing EITHER is FAIL-CLOSED to an `excluded` row whose `reason` names the * absent provenance — no sim_runs row is ever synthesized with an absent envelope or a blank ConfigId. * * Regenerable: keyed folds (SimRunId → run, ConfigId → registry, SimRunId → exclusion) collapse duplicate * source records idempotently (mirroring the ledger's INSERT-OR-REPLACE doctrine), and every output array is * SORTED by its stable key — so the same source Blotters (in any order) yield a BYTE-IDENTICAL index. Pure: * no wall clock, no RNG, no accumulation across calls. Query-only: the source Blotters are never mutated. */ export function projectSimRuns(blotters: readonly Blotter[]): SimRunsIndex { const runs = new Map<string, SimRunRow>(); const configs = new Map<string, AgentConfigRow>(); const excluded = new Map<string, ExcludedRow>(); for (const blotter of blotters) { const session = blotter.session; const simRunId = session.bus.sha256; // the SimRunId — a pure read of the graded-bus receipt (5zl.7) const envelope = session.envelope; // a57.14: present ONLY when the graded bus declared a complete one const configId = session.config_id; // m9i.2: the driver-stamped FULL ConfigId (absent on a config-less bus) // FAIL-CLOSED: an absent experimental envelope OR an absent/blank ConfigId is a typed refusal — the // provenance the index keys on is missing, so the run is EXCLUDED-WITH-REASON, never blank-keyed. if (envelope === undefined || configId === undefined || configId.length === 0) { const missing: string[] = []; if (envelope === undefined) missing.push("experimental envelope (a57.14)"); if (configId === undefined || configId.length === 0) missing.push("config_id (m9i.2)"); excluded.set(simRunId, { sim_run_id: simRunId, reason: `excluded — missing ${missing.join(" and ")}`, }); continue; } // A valid experimental run: index it, and register its config in the distinct-ConfigId registry. The // envelope is a SUPERSET-determined function of the ConfigId, so replicates under one config carry the // same envelope — first-registered is deterministic under the sorted output. runs.set(simRunId, { sim_run_id: simRunId, cell_key: cellOf(blotter), config_id: configId, envelope }); if (!configs.has(configId)) configs.set(configId, { config_id: configId, envelope }); } return { sim_runs: [...runs.values()].sort(bySimRunId), agent_configs: [...configs.values()].sort(byConfigId), excluded: [...excluded.values()].sort(bySimRunId), }; } // ───────────────────────────────────────────────────────────────────────────── // Query API — by run / cell / config, over the pure index VALUE (never a source read) // ───────────────────────────────────────────────────────────────────────────── /** The sim_runs row for a SimRunId, or `undefined` when the index holds no such run. */ export function simRunById(index: SimRunsIndex, simRunId: string): SimRunRow | undefined { return index.sim_runs.find((r) => r.sim_run_id === simRunId); } /** The replicate runs in one comparison cell (the runs sharing a CellKey), in the index's sorted order. */ export function runsByCell(index: SimRunsIndex, cellKey: string): readonly SimRunRow[] { return index.sim_runs.filter((r) => r.cell_key === cellKey); } /** The runs grouped under one distinct ConfigId, in the index's sorted order. */ export function runsByConfig(index: SimRunsIndex, configId: string): readonly SimRunRow[] { return index.sim_runs.filter((r) => r.config_id === configId); }