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