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.

843 lines (771 loc) 81 kB
/** * # bus/types — the typed append-only event bus envelope + payloads (RUNTIME §1) * * The bus is the single substrate underneath every mode: it is the **audit trail**, the * **replay corpus**, and the **grading corpus** at once (RUNTIME §1). Every event is one * flat JSONL record with the envelope `{ seq, ts, stream, type, ...payload }` and a * monotonic `seq`. The engine writes everything it does back onto the bus; a Session in * `sim` mode reads a recorded/synthetic bus *as its clock* — `now` is the current event's * `ts` (RUNTIME §0), so nothing on the runtime path reads a wall clock. * * This file is the **typed object model of the bus**. Other modules import these types; * they never redefine an envelope or a payload. The model is a discriminated union keyed on * `stream` (and, within `TICK`, on `type`) — narrow on `event.stream` first, then on * `event.type`. * * ## Streams (RUNTIME §1) * `META` (session header) · `TICK` (`SPOT` | `BOOK` | `HEARTBEAT`) · `DETECTOR` · * `PLAN` (lifecycle) · `ORDER` (place/cancel/fill/reject) · `WAKE` · `CONTROL` (author * actions) · `REGIME` (tag writes) · `JOURNAL` (author reasoning, full markdown inline) · * `TELEMETRY` (engine-emitted per-order observability). Schema-versioned via {@link BUS_SCHEMA}, * carried on the `META` header and validated by the reader. * * `JOURNAL` is the one stream that is **author metadata, not an engine input** (a57.11): the * per-event pass never folds it, so adding JOURNAL records leaves the emitted engine event * stream — and the determinism hash — byte-identical. It is also the one stream discriminated * on `kind` rather than an engine `type` (there is no `(stream,type)` pairing for it); the * reader tolerates a forward/unknown `kind` by a logged skip instead of a throw. * * `TELEMETRY` (a57.9) is the mirror-image case: it IS engine output (emitted onto the same * stream as PLAN/WAKE, so it joins the determinism hash — same input ⇒ same telemetry ⇒ stable * hash) but it is **observational only** — no engine DECISION reads it back, so the per-event * pass skips it exactly like JOURNAL and its presence never changes an order, fill, or plan * outcome. It carries an **open** `type` vocabulary (like DETECTOR/CONTROL) so per-order * observability can grow without a schema bump; a reader tolerates an unrecognized telemetry * `type` by simply ignoring it, and a report over a bus that carries no telemetry degrades to a * documented default (esc stage 0, reprice count 0) rather than crashing (RUNTIME §8). * * ## Equity-only sessions * An equity-only session is representable two ways, both first-class: emit no `BOOK` events * at all (SPOT-only tape), or emit `BOOK` events whose `legs` array is empty. Options are a * per-instrument overlay on the underlier tape, never a structural requirement. */ // ───────────────────────────────────────────────────────────────────────────── // Schema + enumerations // ───────────────────────────────────────────────────────────────────────────── /** The bus schema version new buses are written at. Bumped on any breaking envelope/payload * change — including the addition of a new stream tag, since a reader that predates the tag * hard-throws on it (fail-closed, but mid-stream): v2 adds the {@link JournalEvent JOURNAL} stream, * v3 adds the {@link TelemetryEvent TELEMETRY} stream. (The TELEMETRY `type` vocabulary is open and * may grow WITHOUT a bump — that is a within-stream extension a telemetry-aware reader tolerates; * introducing the STREAM tag itself is the breaking change, exactly as JOURNAL was.) * * v4 (kestrel-a57.1 slice 1, ADR-0011) records the **probabilistic accounting on the bus** so the * Blotter projector re-derives `totals{floor,expected}` + `orders[].support` with no engine scrape: * a settle-outcome TELEMETRY record ({@link TelemetrySettlePayload}, `type: "settle"`) per order, and * the **judge self-description** on a GRADED bus's META ({@link MetaPayload.fill_model}/`instance`/ * `fidelity`). Both are strictly additive & OPTIONAL — the settle `type` rides the already-open * TELEMETRY vocabulary (no reader gate to widen) and the META judge fields are absent on a fill-model- * agnostic INPUT tape (the two-bus rule, ADR-0011) — so a v1..v3 bus reads clean here and the bump is * really only about advertising the new self-describing shape. The reader accepts every schema in * {@link SUPPORTED_BUS_SCHEMAS} and refuses anything outside it loudly (fail-closed, RUNTIME §8). * * v5 (kestrel-22j.15) mints a per-Plan-**instance** identity — {@link PlanPayload.plan_instance}, carried * onto the authored {@link OrderPayload}/{@link WakePayload}/{@link ControlPayload} evidence — so a legal * same-NAME replacement (a Lineage key recurs after its predecessor is `done`, CONTEXT: Lineage) no longer * collapses into the prior instance's lifecycle trace at report projection. The id is minted DETERMINISTICALLY * at registration (the authored name + a registration ordinal; NO wall clock, NO unseeded RNG, RUNTIME §0) — * same bus + documents ⇒ byte-identical ids. It is additive & OPTIONAL (a v1..v4 bus simply carries none), but * the bump is DELIBERATE: it advertises that a graded bus now self-identifies its Plan instances, and it draws * the fail-closed line for the migration. A projector reads the explicit `plan_instance` when present and, for a * pre-v5 bus that lacks it, MIGRATES EXPLICITLY by sessionizing each name's lifecycle at its structural * `done`→re-authored boundary ({@link ../blotter/project.ts}) — never inferring identity from timestamps or * fuzzy name-matching (which the F4 same-name-supersession guard makes exact: a name only recurs after its * predecessor is `done`). The reader accepts every schema in {@link SUPPORTED_BUS_SCHEMAS} and refuses anything * outside it loudly (fail-closed, RUNTIME §8). * * v6 (kestrel-w7la.1, ADR-0040 — clock-honest wakes) records **deliberation cost on the Bus**: the * within-stream WAKE type `"deliberation"` ({@link DeliberationEvent}) prices one clocked Seat turn — * `ts` IS the derived return time `checkpoint.ts + measured_ms + buffer_ms`, `wake_seq` names the WAKE * checkpoint it belongs to — so replay/certification re-project byte-identically from recorded time * (the determinism invariant's recorded-time clause, RUNTIME §0). The type is within-stream-ADDITIVE, * yet the bump is DELIBERATE (exactly the v5 precedent): a v5 build that *ignored* deliberation records * would re-project a clocked bus into a *different* byte stream — silent wrongness — so stamping v6 * makes every pre-0040 build refuse a clocked bus up front at the META line. Only session-OUTPUT (graded) * buses stamp v6; synthetic INPUT tapes keep their pinned literal schema (an input tape can never carry a * deliberation record, and re-stamping the corpus would force a corpus re-mint — ADR-0018). A `deliberation` * record on a bus whose META stamps `bus_schema < 6` is refused loudly by the reader (a clocked record on a * pre-clocked bus is a corrupt bus). See docs/design/clock-honest-wakes.md §1. */ export const BUS_SCHEMA = 6 as const; export type BusSchema = typeof BUS_SCHEMA; /** The schemas a bus META may legally carry under this build — {@link SUPPORTED_BUS_SCHEMAS} as a type. * {@link MetaPayload.bus_schema} is typed on THIS union (not the literal {@link BusSchema}) because input-tape * generators deliberately keep writing their pinned literal schema (v5 today — the input-tape-literal rule, * clock-honest wakes §1) and must typecheck; the reader still refuses anything outside the set at runtime. */ export type SupportedBusSchema = 1 | 2 | 3 | 4 | 5 | 6; /** The bus schemas this build can read. JOURNAL landed in v2, TELEMETRY in v3, the settle-outcome * telemetry + META judge self-description in v4, and the per-Plan-instance identity * ({@link PlanPayload.plan_instance}) in v5 — every one an append-only / additive change that * moves no engine semantics, so older buses are forward-compatible: a v1 bus simply has no JOURNAL/ * TELEMETRY records, a v2 bus no TELEMETRY, a v3 bus no settle-outcome record and a bare (judge-less) * META, a v4 bus no `plan_instance` (a projector migrates it explicitly by lifecycle sessionization, * never a silent mis-identify). The reader tolerates any schema listed here and rejects the rest * loudly — a build that predates a stream refuses a bus carrying it up front at the META line rather * than throwing mid-stream (fail-closed, §8). v6 (clock-honest wakes, ADR-0040) adds the WAKE * `deliberation` record; a v1..v5 bus simply carries none and replays byte-identically. */ export const SUPPORTED_BUS_SCHEMAS: ReadonlySet<number> = new Set<number>([1, 2, 3, 4, 5, 6]); /** The streams (RUNTIME §1). `JOURNAL` (v2) is author metadata, never an engine input; * `TELEMETRY` (a57.9) is engine output but observational only — never an engine input. */ export type Stream = | "META" | "TICK" | "DETECTOR" | "PLAN" | "ORDER" | "WAKE" | "CONTROL" | "REGIME" | "JOURNAL" | "TELEMETRY"; /** The set of legal stream tags, for reader validation (fail-closed on an unknown stream). */ export const STREAMS: ReadonlySet<Stream> = new Set<Stream>([ "META", "TICK", "DETECTOR", "PLAN", "ORDER", "WAKE", "CONTROL", "REGIME", "JOURNAL", "TELEMETRY", ]); /** What an order means when it fires (CONTEXT: Mode). One engine path; only the gate differs. */ export type Mode = "sim" | "paper" | "live"; /** Session-calendar phase (RUNTIME §2 `session phase`), carried on heartbeats. */ export type SessionPhase = "pre" | "open" | "regular" | "close" | "post"; /** Option right — the two contract kinds (shared vocabulary with the language `Leg`). */ export type Right = "C" | "P"; // ───────────────────────────────────────────────────────────────────────────── // Envelope // ───────────────────────────────────────────────────────────────────────────── /** The fields every bus event carries. `stream`/`type` are added per event as literal * discriminants; the payload fields are spread flat alongside (RUNTIME §1: the envelope IS * `{ seq, ts, stream, type, ...payload }`, not a nested payload object). */ export interface EnvelopeBase { /** Monotonic sequence number, assigned by the single writer. Determinism key. */ readonly seq: number; /** Injected timestamp (epoch milliseconds). In `sim` this is the clock (RUNTIME §0). */ readonly ts: number; } // ───────────────────────────────────────────────────────────────────────────── // META — the session header (RUNTIME §1) // ───────────────────────────────────────────────────────────────────────────── /** One instrument the session carries. An equity-only session lists only equity/index * instruments and never emits BOOK legs against them. */ export interface SessionInstrument { readonly symbol: string; /** The asset class of the underlier. `option-underlier` signals that BOOK legs may appear * for this symbol; the others are quoted by SPOT alone. */ readonly assetClass: "equity" | "index" | "future" | "option-underlier"; /** The plan-side role, when the session pins one (a signal-space vs execution-space * instrument, ARCHITECTURE §2 `USING`). Absent when unroled. */ readonly role?: "signal" | "exec"; } /** * The judge that produced a GRADED bus's fills — the fill model's identity + calibration receipt * (CONTEXT: Fidelity; ADR-0006 "every grade stamps its judge"). EVs across fill-model versions do * NOT naively compare, so the version + the exact calibration bytes are pinned here. `calibration_sha` * is the sha256 over the calibration descriptor (a stable digest of `{version, calibrated}` for a * hazard model, or of a `"none"` sentinel for a pure floor model with no calibration) — two grades * under byte-identical calibration share it; a re-fit produces a new one. */ export interface FillModelStamp { readonly name: string; readonly version: string; readonly calibration_sha: string; /** * What the judge structurally could NOT observe (a57.4) — DECLARED by the fill model itself and stamped * here, in a stable order, so a projector reads the judge's self-limitation off the graded META rather than * guessing it from `name` (a lookup that silently mislabelled the calibrated `maker-fair-v1+<version>` * judge — kestrel-o32). OPTIONAL/additive exactly like the other graded-only fields: a legacy graded bus * (or an input tape) carries none, and a projector fails closed to "unstated"/maximally-limited on its * absence — never a silent default to unlimited observability. */ readonly self_limitation?: readonly { readonly code: string; readonly note: string }[]; } /** * The Instance identity of a GRADED bus (CONTEXT: Instance; ADR-0011 recommendation 1 — reuse the * Ledger model). `lineage` is the **authored NAME** (names-are-data, ADR-0006 — the Ledger's * aggregation key: `fade-ladder` authored on 40 days is one lineage with 40 instances); `version` is * the `plans_sha256` (the canonical parse-print hash of the armed document set); `mode` is the session * mode; `pod` is present ONLY when the Session runs an actual Pod document (a plans library is itself a * versioned module, ADR-0003 — so a bare-plans grade carries no `pod`). */ export interface InstanceIdentity { readonly lineage: string; readonly version: string; readonly mode: Mode; readonly pod?: string; } /** * Fidelity — how *real* a Session's fills are (CONTEXT: Fidelity): the axis from **modeled** to * **realized**. `sim` and `paper` are both `modeled` (the same probabilistic FillModel yields a * per-fill `pFill` scored over the survival product `Π(1 − pFillᵢ)`); `live` is `realized` (the fills * are actual). Carried on a GRADED bus's META so the Blotter can *claim* its fill realism — orthogonal * to Certification (a sim Blotter can be certified yet low-fidelity). Richer per-fill fill-claims land * with a57.4; slice 1 carries only this level. */ export type Fidelity = "modeled" | "realized"; // ───────────────────────────────────────────────────────────────────────────── // The agent/model EXPERIMENTAL envelope (kestrel-a57.14) — the injected identity of the author // ───────────────────────────────────────────────────────────────────────────── /** * The token accounting the {@link ExperimentalEnvelope} carries (a57.14) — the INJECTED spend the author * incurred (`input`/`output`/`thinking`), sourced off the deterministic record path from an authenticated * or explicitly injected input (the owner-away conservative default), NEVER re-derived on the record path. * The natural shape of the provider's usage (`LlmUsage`, `src/session/agent.ts`). */ export interface TokenAccounting { readonly input: number; readonly output: number; readonly thinking: number; } /** * The interface **face** an author reached the model through (kestrel-m9i.2 owner tweak, ENV·CFG): the * closed vocabulary the m9i.7 face tournament pivots on — `http` (raw provider HTTP), `sdk` (a * provider/AI SDK client), `cli` (a terminal harness), `mcp` (a Model Context Protocol server). A CLOSED * set: the projector fail-closes a declared envelope whose `face` is outside it (a typed vocabulary, * never an open string that drifts). * * CANONICAL in the protocol leaf ({@link import("../protocol/index.ts").Face}, ADR-0004) — the bus * re-exports it so the a57.14 envelope-identity axis validates against the SAME closed vocabulary the * surfaces project, one source of truth across the protocol/bus seam (arch-review A6). Protocol does * not import bus, so this cannot cycle. */ export { FACES } from "../protocol/index.ts"; export type { Face } from "../protocol/index.ts"; /** * The **corpus tier** a benchmark run draws on (kestrel-m9i.26, platform ADR-0018 three-tier addendum): the * closed vocabulary that gates the practice-vs-holdback wall. Exactly like {@link Face} — a CLOSED set, not * an open string that drifts — so the projector fail-closes a declared envelope whose `corpus_tier` is * outside it (a typo or the retired `public-baseline` never silently certifies): * * - `public` — a freely-trainable, NON-ranking PRACTICE corpus (well-known events; prior knowledge OK). * - `semi-private` — a sealed forward-window HOLDBACK corpus (blinded holdback; event-date > training_cutoff). * - `private` — a custom HOLDBACK corpus (owner-authored; event-date > training_cutoff), ranking-eligible. * * NOTE — distinct from the catalog's opaque `CorpusTier` (src/protocol/catalog.ts, deliberately open, a * DIFFERENT axis, b83-owned): this is the a57.14 envelope-identity axis, mirrored on the FACES pattern. */ export type CorpusTier = "public" | "semi-private" | "private"; /** The closed {@link CorpusTier} vocabulary in a FIXED order (deterministic validation reasons; mirrors * {@link FACES}). */ export const CORPUS_TIERS: readonly CorpusTier[] = ["public", "semi-private", "private"]; /** * The practice-vs-holdback **benchmark-tiering wall** (kestrel-m9i.26; benchmark-matrix-design §3; ADR-0018 * season) mapped explicitly onto the {@link CorpusTier} vocabulary. A pure, TOTAL classifier over the closed * set: `public` is the freely-trainable, non-ranking PRACTICE tier; `semi-private`/`private` are the sealed * forward-window HOLDBACK tiers (event-date > training_cutoff, ranking-eligible). No wall clock/RNG. */ export function corpusTierWall(tier: CorpusTier): "practice" | "holdback" { return tier === "public" ? "practice" : "holdback"; } /** * The complete agent/model **experimental envelope** (kestrel-a57.14): the identity of the author that * produced this session, captured from authenticated or explicitly INJECTED inputs and stamped on the * GRADED bus META at open — exactly where the judge self-description (`fill_model`/`instance`/`fidelity`, * a57.4) already rides — so a projector reads it as a PURE input and stamps it on the finalized Blotter. * Its input half is the seam's `AgentConfig`/`ModelIdentity` descriptor; every value has traceable * provenance and is never fabricated. * * NOTE — distinct from the bus event {@link EnvelopeBase} (the `{seq,ts,stream,type}` record envelope): * this is the *experimental* envelope (the author's identity), a payload field, not the wire envelope. * * FAIL-CLOSED (a57.14; FOURTEEN fields since the m9i.2 owner tweaks — `face` + `adapter` + * `adapter_version` joined the original eleven, mandatory-for-certified BEFORE the m9i.7 face/harness * tournament): the projector treats all fourteen fields as REQUIRED identity. A declared envelope * missing/empty on ANY one finalizes the Blotter as an explicit PROVISIONAL result with a recorded reason * naming the field — no field is ever silently defaulted to `""`/`"unknown"`. The `prompt_hash` carries the * prompt HASH, never the prompt content (the provenance fence). */ export interface ExperimentalEnvelope { /** The model provider/family (generic identity — e.g. a vendor label). NEVER a credential. */ readonly provider: string; /** The pinned model identity (family/snapshot). */ readonly model: string; /** The model version/snapshot token. */ readonly version: string; /** The model's training cutoff (the basis for model-relative holdback gating). */ readonly training_cutoff: string; /** The sha256 HASH of the author prompt/profile — a hash, NEVER the prompt content (`AuthorPolicy`). */ readonly prompt_hash: string; /** The tokenizer identity token costs are measured under (`RenderingIdentity`). */ readonly tokenizer: string; /** The tool policy the author ran under (e.g. `no-tools`). */ readonly tool_policy: string; /** The Rendering identity — how the Frame was rendered to tokens (`RenderingIdentity`). */ readonly rendering_identity: string; /** What woke the author (e.g. `structural-cadence`). */ readonly wake_source: string; /** The INJECTED token accounting (off the deterministic record path — the owner-away default). */ readonly token_accounting: TokenAccounting; /** The corpus tier the author draws on (`public`|`semi-private`|`private`) — the practice-vs-holdback * wall (kestrel-m9i.26). Typed loosely here (injected bytes, exactly like `face`); the projector enforces * the closed {@link CorpusTier} vocabulary fail-closed (a typo / the retired `public-baseline` refuses). */ readonly corpus_tier: string; /** The interface {@link Face} the author reached the model through (`http`|`sdk`|`cli`|`mcp`) — the * m9i.7 tournament axis. Typed loosely here (injected bytes); the projector enforces the closed * {@link FACES} vocabulary fail-closed (m9i.2 owner tweak). */ readonly face: string; /** The adapter/harness identity that drove the author (e.g. `kestrel-live-agent` vs an external coding * harness) — the field the our-harness-vs-others comparison pivots on (m9i.2 owner tweak). */ readonly adapter: string; /** The adapter/harness version/revision token (paired with `adapter`). */ readonly adapter_version: string; } /** * The typed fail-closed reason the {@link ExperimentalEnvelope}'s `training_cutoff` cross-check emits * (kestrel-m9i.23), alongside the a57.14 "missing identity field" reasons. The declared `training_cutoff` * is only a basis for model-relative holdback if it AGREES with the platform model registry keyed by * `model` id (the `MODEL_KNOWLEDGE_CUTOFF` fence mirrored in `src/blotter/model-registry.ts`). Two ways it * fails closed — each NAMES the disagreement so the projector can render `detail` onto the certification * reasons and flip the Blotter to PROVISIONAL, never a silent default and never a silent correction of the * declared value (an attestation-side VERIFICATION, never a rewrite): * * - `unknown-model-id` — no registry entry for `model`: the cutoff cannot be verified, so the row * cannot certify as post-cutoff (NOT treated as post- or pre-cutoff). * - `declared-registry-mismatch`— the declared `training_cutoff` disagrees with the registry's cutoff for * `model` (`registry` present): the declared value is RECORDED, not rewritten. */ export interface TrainingCutoffReason { readonly kind: "unknown-model-id" | "declared-registry-mismatch"; /** The declared model id the cross-check keyed on. */ readonly model: string; /** The declared `training_cutoff` (recorded verbatim — never corrected to the registry's). */ readonly declared: string; /** The registry's cutoff for `model` — present ONLY on a `declared-registry-mismatch`. */ readonly registry?: string; /** The stable, NAMED message projected onto `certification.reasons` (fail-closed, deterministic). */ readonly detail: string; } /** * The closed vocabulary of a per-leg failure the DERIVED `rankable` axis NAMES (kestrel-m9i.25), a sibling * of the a57.14 certification reasons and the m9i.23 {@link TrainingCutoffReason}. `rankable` is a * CONJUNCTION of five legs — CERTIFIED ∧ POST-CUTOFF ∧ DATE-BLIND ∧ (SEASON) FROZEN ∧ CLOCK-HONEST * (the fifth leg is ADR-0040 / kestrel-w7la.3, appended LAST so every pre-existing reason ordering stays * byte-stable) — DERIVED at projection from already-stamped fields, NEVER a stamped verdict a producer * sets. Each `kind` names WHICH leg did not pass, and whether it was a hard-false (`not-certified` / * `pre-cutoff` / `not-date-blind` / `season-not-frozen` / `not-clock-honest`) OR a fail-closed UNKNOWN * (`cutoff-unknown` / `date-blind-unknown` / `season-unknown` / `clock-honest-unknown` — an ABSENT or * malformed leg input is UNKNOWN, never a silent pass). `not-clock-honest` is reachable ONLY via a * platform-side revocation (`meta.clock_honest: false`) — the driver stamps `true` or nothing, so an * unattested (latency-blind) session is `clock-honest-unknown`, never `not-clock-honest`. */ export type RankableReasonKind = | "not-certified" | "pre-cutoff" | "cutoff-unknown" | "not-date-blind" | "date-blind-unknown" | "season-not-frozen" | "season-unknown" | "not-clock-honest" | "clock-honest-unknown"; /** * A typed, NAMED per-leg reason the DERIVED `rankable` axis (kestrel-m9i.25) records — mirroring the shape * of {@link TrainingCutoffReason}: a closed {@link RankableReasonKind} plus the stable human `detail`. A * not-rankable row can never be silently non-comparable — every non-passing leg NAMES itself. Present on the * derived rankability ONLY when the row is not rankable (a rankable row carries none). */ export interface RankableReason { readonly kind: RankableReasonKind; /** The stable, NAMED message the derived rankable axis records (fail-closed, deterministic). */ readonly detail: string; } /** * `{session_date, instruments, mode, bus_schema}` — the session header (plus, on a GRADED bus, the * judge self-description). Exactly one META event opens a well-formed bus; the reader validates its * `bus_schema`. * * **The two-bus rule (ADR-0011).** The market-data **INPUT tape** is fill-model-*agnostic* — the same * recorded tape is graded under many models — so `fill_model`/`instance`/`fidelity` are **absent** on * it. The **GRADED bus** (the input plus the engine's reactions, produced under one fill model) * self-describes its judge: the session driver stamps these three at open (they are session INPUTS, * known before the first event). They are therefore **OPTIONAL** here — present on a graded bus, absent * on an input tape — and a future projector fails closed on a graded bus that lacks them. */ export interface MetaPayload { readonly session_date: string; // YYYY-MM-DD (opaque calendar token) readonly instruments: readonly SessionInstrument[]; readonly mode: Mode; /** The schema this bus is written at — any {@link SupportedBusSchema}, deliberately NOT the literal * {@link BusSchema}: graded OUTPUT buses stamp the current {@link BUS_SCHEMA}, while synthetic INPUT * tapes keep their pinned literal (v5 — the input-tape-literal rule, clock-honest wakes §1) so the * frozen corpus is never re-minted by a schema bump. */ readonly bus_schema: SupportedBusSchema; /** The fill model that produced this graded bus's fills (a GRADED bus only; absent on an input tape). */ readonly fill_model?: FillModelStamp; /** The Instance identity of this graded session (a GRADED bus only; absent on an input tape). */ readonly instance?: InstanceIdentity; /** The fill realism this session may claim (a GRADED bus only; absent on an input tape). */ readonly fidelity?: Fidelity; /** The author's experimental envelope (kestrel-a57.14) — the INJECTED agent/model identity, stamped at * open alongside the judge fields. Additive/OPTIONAL exactly like `fill_model`/`instance`/`fidelity`: an * input tape (and a pre-a57.14 graded bus) carries none. When present, the projector requires it COMPLETE * or fails the Blotter closed to PROVISIONAL (a57.14). */ readonly envelope?: ExperimentalEnvelope; /** The FULL `ConfigId` of the run's config descriptor (`sha256(canonical AgentConfig)` — kestrel-5zl.6), * stamped at open alongside `envelope` whenever the config declares one (m9i.2 owner tweak). A SUPERSET * identity of the envelope: it additionally folds the sampling axis (`temperature`/`thinkingLevel`), * `format`, and `label`, so temperature-only config variants carry DISTINCT ids — the identity the grid * CellKey keys on (run-identity.ts). Additive/OPTIONAL like `envelope`: absent on an input tape, a * config-less graded bus, and a pre-tweak graded bus. */ readonly config_id?: string; /** The **cell config axis** — the full ConfigId with the `fillSeed` REPLICATE axis STRIPPED * (`deriveConfigId(cellCanonicalConfig(config))`, kestrel-9gu.8.1 / ADR-0016 §3). This is the config axis * the grid CellKey/`cellOf` keys on: seed-only variants share it (replicates in one cell), while * temperature/behavioral variants still split. The driver stamps it ONLY when it DIVERGES from `config_id` * (i.e. the config carried a `fillSeed`) — a seedless run's cell axis EQUALS `config_id`, so no separate * stamp and the graded bus is byte-identical to before. `config_id` keeps the FULL, seeded identity (the * run identity feeding `SimRunId`). Additive/OPTIONAL: absent on an input tape, a config-less bus, a * pre-tweak bus, and every seedless run. */ readonly cell_config_id?: string; /** The sampled-channel qualification claim ({@link SampledQualificationClaim}, kestrel-9gu.8.6/9gu.12), * stamped at open ONLY when the run's config records an owner-approved qualification AND the run is * structurally consistent with it (a seeded run under a calibrated judge — the driver refuses to stamp a * contradiction, RUNTIME §8). Additive/OPTIONAL: absent on an input tape and on every unqualified graded * bus, whose headline therefore FAILS CLOSED to the strict-cross floor (9gu.8 AC#7). */ readonly sampled_qualification?: SampledQualificationClaim; /** The **date-blind ATT attestation** (kestrel-m9i.25): the run is attested date-blind — the agent could * not read the calendar date off the tape and the date-blinding held (the T-5 date-blind briefing, * SYSTEM-PROFILE.md). A Layer-3 GOVERNANCE input stamped on the graded META beside the judge fields — NOT * an a57.14 {@link ExperimentalEnvelope} field (the 14-field identity envelope stays frozen), and OFF the * ConfigId hash (like `config_id`/`sampled_qualification`, it is a stamped result, never a config input). * One of the DERIVED `rankable` legs the projector reads (never producer-trusted): `true` passes the * date-blind leg, `false` fails it (`not-date-blind`), ABSENT/malformed is UNKNOWN ⇒ fail-closed * (`date-blind-unknown`), never a silent rankable. Additive/OPTIONAL: absent on an input tape and on every * un-attested graded bus. */ readonly date_blind?: boolean; /** The **season-frozen ATT attestation** (kestrel-m9i.25): the benchmark season / submission is * frozen/sealed — the content-addressed frozen submission before the window opens (platform ADR-0018). A * Layer-3 GOVERNANCE input stamped on the graded META — NOT an a57.14 envelope field, and OFF the ConfigId * hash. One of the DERIVED `rankable` legs (never producer-trusted): `true` passes the season leg, `false` * fails it (`season-not-frozen`), ABSENT/malformed is UNKNOWN ⇒ fail-closed (`season-unknown`), never a * silent rankable. Additive/OPTIONAL exactly like `date_blind`. */ readonly season_frozen?: boolean; /** The **clock-honest attestation** (ADR-0040 / kestrel-w7la.1): the session ran clocked semantics — * deliberation consumed tape time, priced by {@link DeliberationEvent} records — AND the §4 data-floor * verification held (sub-minute ground truth). A Layer-3 stamped RESULT beside `date_blind`/ * `season_frozen`: NOT an envelope field, OFF the ConfigId hash. The driver stamps `true` or nothing — * it NEVER stamps `false` (decided): `false` is reserved as a platform-side *revocation* value * (governance marking a discovered clock-honesty defect hard-bad post-hoc); an OSS run is either * attested-true or unattested. Absent ⇒ latency-blind ⇒ the run can never ground a latency claim * (the m9i.25-style rankability leg that READS this is kestrel-w7la.3's). Additive/OPTIONAL: absent * on an input tape and on every latency-blind graded bus — those stay byte-identical. */ readonly clock_honest?: boolean; } export interface MetaEvent extends EnvelopeBase, MetaPayload { readonly stream: "META"; readonly type: "session"; } /** * The owner-recorded qualification claim for the SAMPLED fill channel (kestrel-9gu.8.6; 9gu.8 AC#7): * the sampled (seeded-Bernoulli) channel may become the HEADLINE P&L only after its qualification study * passes all three legs — **calibration** (the hazard judge is calibrated against live ground truth), * **causality** (sampled fills enter positions/Plan management causally, not as terminal EV), and * **fidelity** (realized distributions reconcile with live fill behaviour). `reference` NAMES where the * approval is recorded (a bead / ADR) — provenance, never blank. Stamped on a GRADED bus's META * ({@link MetaPayload.sampled_qualification}) so the headline selection re-derives from bus bytes * (ADR-0011). No claim on record ⇒ the gate FAILS CLOSED and strict-cross floor stays the headline * (kestrel-9gu.12). */ export interface SampledQualificationClaim { /** Leg 1 — the hazard calibration passed qualification (9gu.8.6). */ readonly calibration: boolean; /** Leg 2 — causal integration passed qualification (sampled fills drive management, 9gu.8). */ readonly causality: boolean; /** Leg 3 — realized-fill fidelity passed qualification (reconciles with live ground truth). */ readonly fidelity: boolean; /** Where the approval is recorded (bead/ADR id) — non-empty provenance. */ readonly reference: string; } // ───────────────────────────────────────────────────────────────────────────── // TICK — SPOT | BOOK | HEARTBEAT (RUNTIME §1) // ───────────────────────────────────────────────────────────────────────────── /** `{instrument, px}` — one underlier print. For an **equity/spot instrument** the SPOT tick * may also carry a two-sided **NBBO quote** (`bid`/`ask`, ADR-0017) so a spot leg fills against * the instrument's own book (instrument-keyed strict-cross) and `@fair` resolves to the quote mid * (`exec-fair-quote-v1`). Both are OPTIONAL and additive: an option-underlier SPOT sets neither, so * every existing tape is byte-identical; a missing side is `null` (dark), never omitted or zero. */ export interface SpotPayload { readonly instrument: string; readonly px: number; /** The equity NBBO bid (spot instruments); `null` when dark, absent on a plain underlier print. */ readonly bid?: number | null; /** The equity NBBO ask (spot instruments); `null` when dark, absent on a plain underlier print. */ readonly ask?: number | null; } export interface SpotEvent extends EnvelopeBase, SpotPayload { readonly stream: "TICK"; readonly type: "SPOT"; } /** * One option-leg quote inside a BOOK event. A missing side is `null` — not zero and not * omitted — so the reducer can tell a genuinely one-sided or dark book (the MM-pull * fingerprint of a real move; the observed mid is then a health signal, never a price, * ARCHITECTURE §4) from a two-sided quote. `bid`/`ask` `null` ⇒ that side is dark; both * `null` ⇒ the whole leg is dark. */ export interface OptionQuote { readonly strike: number; readonly right: Right; readonly bid: number | null; readonly ask: number | null; /** Resting size on the bid (contracts); `null` when the bid is dark. */ readonly bsz: number | null; /** Resting size on the ask (contracts); `null` when the ask is dark. */ readonly asz: number | null; /** Last trade price, when one has printed for this leg in-session. */ readonly last?: number; } /** `{instrument, underlier_px, expiry?, legs}` — an option chain slice around the underlier * at one moment, plus the underlying context (`underlier_px`) it was quoted against. `legs` * is empty for an equity-only BOOK. */ export interface BookPayload { /** The option underlier symbol the legs are written against. */ readonly instrument: string; /** The underlier price at the moment this book was observed (the underlying context). */ readonly underlier_px: number; /** The chain's expiry (a date `2026-07-17` or a tag `0dte`), when the session pins one. */ readonly expiry?: string; /** The option legs. Empty for an equity-only session. */ readonly legs: readonly OptionQuote[]; } export interface BookEvent extends EnvelopeBase, BookPayload { readonly stream: "TICK"; readonly type: "BOOK"; } /** A cadence event — proof of life on a quiet tape (RUNTIME §1). Carries the session phase * when the calendar advances it. Used by the engine's staleness backstop. */ export interface HeartbeatPayload { readonly phase?: SessionPhase; } export interface HeartbeatEvent extends EnvelopeBase, HeartbeatPayload { readonly stream: "TICK"; readonly type: "HEARTBEAT"; } /** The three TICK events. */ export type TickEvent = SpotEvent | BookEvent | HeartbeatEvent; // ───────────────────────────────────────────────────────────────────────────── // DETECTOR — versioned pure-reducer output (RUNTIME §2) // ───────────────────────────────────────────────────────────────────────────── /** A detector emission. `detector`/`version` name the reducer (same input sequence ⇒ same * detector event sequence); `type` is the detector's own event vocabulary (open). */ export interface DetectorPayload { readonly detector: string; readonly version: string; readonly instrument?: string; readonly value?: number | string | boolean | null; } export interface DetectorEvent extends EnvelopeBase, DetectorPayload { readonly stream: "DETECTOR"; /** The detector's own event name (open vocabulary). */ readonly type: string; } // ───────────────────────────────────────────────────────────────────────────── // PLAN — lifecycle transitions (RUNTIME §5) // ───────────────────────────────────────────────────────────────────────────── /** Plan lifecycle state (RUNTIME §5: authored → armed → fired → managing → done). */ export type PlanState = "authored" | "armed" | "fired" | "managing" | "done"; /** Terminal outcome, present only on the `done` transition. */ export type PlanOutcome = "filled" | "expired" | "invalidated"; export interface PlanPayload { /** The human-authored plan NAME — the **Lineage** key (names-are-data, ADR-0006). A name may legally * RECUR after an earlier instance is `done` (CONTEXT: Lineage), so it is NOT an instance identity. */ readonly plan: string; /** The exact per-Plan-**instance** identity (bus_schema v5, kestrel-22j.15): minted DETERMINISTICALLY at * registration (the name + a registration ordinal; no wall clock / no unseeded RNG, RUNTIME §0), stable * across replay. Distinguishes a legal same-name replacement from its predecessor so a report projection * keeps them as TWO instances instead of collapsing one terminal outcome onto the other. ABSENT on a * pre-v5 bus (a projector then migrates by lifecycle sessionization — never a silent mis-identify). */ readonly plan_instance?: string; readonly state: PlanState; readonly outcome?: PlanOutcome; /** A logged reason for a de-arm / invalidation (fail-closed transitions are never silent). */ readonly reason?: string; } export interface PlanEvent extends EnvelopeBase, PlanPayload { readonly stream: "PLAN"; readonly type: "lifecycle"; } // ───────────────────────────────────────────────────────────────────────────── // ORDER — place / cancel / fill / reject (RUNTIME §5–6) // ───────────────────────────────────────────────────────────────────────────── export type OrderAction = "place" | "cancel" | "fill" | "reject"; export interface OrderPayload { readonly order_id: string; /** The plan NAME (Lineage key) that authored the order, when it came from one. */ readonly plan?: string; /** The exact Plan-**instance** identity that authored the order (bus_schema v5, kestrel-22j.15) — the same * minted id carried on the plan's {@link PlanPayload.plan_instance}, so an order joins its exact instance, * not merely its recurring name. Present when the order came from a plan on a v5 bus. */ readonly plan_instance?: string; readonly instrument: string; readonly side: "buy" | "sell"; readonly qty: number; /** Limit price on `place`, fill price on `fill`. */ readonly px?: number; readonly strike?: number; readonly right?: Right; /** Price-resolution annotation (e.g. `fair=fallback(mid)`) or reject/cancel reason. A * silent mid is forbidden (RUNTIME §4); the annotation rides here. */ readonly reason?: string; } export interface OrderEvent extends EnvelopeBase, OrderPayload { readonly stream: "ORDER"; readonly type: OrderAction; } // ───────────────────────────────────────────────────────────────────────────── // WAKE — attention deliveries (CONTEXT: Wake, Wake delivery) // ───────────────────────────────────────────────────────────────────────────── /** A wake delivery, a coalesced/downgraded squelch record (visible in the kernel, never a * silent drop), or a fire-then-inform lifecycle wake. */ export type WakeKind = "wake" | "coalesced" | "downgraded"; export interface WakePayload { readonly wake: string; /** The exact Plan-**instance** identity this wake belongs to (bus_schema v5, kestrel-22j.15) when the wake * is a plan's fire-then-inform / de-arm inform — the same minted id on the plan's * {@link PlanPayload.plan_instance}. Present for a plan-authored wake on a v5 bus. */ readonly plan_instance?: string; /** The View the wake delivers, when it names one. */ readonly view?: string; readonly priority?: number; readonly reason?: string; } export interface WakeEvent extends EnvelopeBase, WakePayload { readonly stream: "WAKE"; readonly type: WakeKind; } /** * The **Deliberation record** (bus_schema v6 — ADR-0040 / kestrel-w7la.1, CONTEXT "Deliberation * record"): the cost of ONE clocked Seat turn, recorded on the Bus so replay and certification * re-project byte-identically from recorded time (the determinism invariant's recorded-time clause). * One record per delivered vantage ANSWERED BY A SEAT (OPEN and every wake), in a clock-honest * session only — a latency-blind session emits none, and an engine-synthesized turn (a replay * divergence stand-down / inserted-vantage pass) is not Seat deliberation and records nothing. * * The envelope `ts` **is** the derived return time — `ts = checkpoint(wake_seq).ts + measured_ms + * buffer_ms` — deliberately carrying no redundant `cost_ms`/`return_ts` field. The identity is scoped * **record-vs-checkpoint, never record-vs-landing**: the OPEN turn's actions legitimately land at * `max(firstTs, returnTs)`, later than this record's `ts` (clock-honest wakes §1/§2.6); the reader * verifies the record against the checkpoint it names and the DRIVER owns landing semantics. All * arithmetic is integer-ms addition — cross-arch bit-stable, never a hardware-minted NaN * (kestrel-gdih). Distinct from {@link WakePayload} on purpose: `WakeKind`/`WakePayload` are NOT * widened (`WakePayload` requires `wake: string`, which a deliberation record does not carry). */ export interface DeliberationPayload { /** The `seq` of the WAKE checkpoint this cost belongs to (the vantage the Seat answered). */ readonly wake_seq: number; /** Measured wall time of the Seat's WHOLE turn (bounded authoring loop + repair retries included, * §2.7), minted ONCE at the driver's injectable `now` seam (§7). Integer ms ≥ 0. */ readonly measured_ms: number; /** The configured latency buffer ({@link import("../session/agent.ts").AgentConfig.latencyBufferMs}) * added to every measured Seat turn. Integer ms ≥ 0; `0` declares a colocated seat. */ readonly buffer_ms: number; } export interface DeliberationEvent extends EnvelopeBase, DeliberationPayload { readonly stream: "WAKE"; readonly type: "deliberation"; } // ───────────────────────────────────────────────────────────────────────────── // CONTROL — author actions (arm/disarm/author/supersede/allocate) // ───────────────────────────────────────────────────────────────────────────── export interface ControlPayload { /** The statement or pod node the action targets (by NAME — the Lineage key an author-action addresses). */ readonly target?: string; /** The exact Plan-**instance** identity a control action targets (bus_schema v5, kestrel-22j.15), when the * action addresses one specific instance rather than the recurring name. Author-actions still address by * `target` (the name), so this is present only where a control resolves to a single minted instance. */ readonly plan_instance?: string; /** The authenticated channel provenance tier, when the action carries one. */ readonly provenance?: string; readonly note?: string; } export interface ControlEvent extends EnvelopeBase, ControlPayload { readonly stream: "CONTROL"; /** The author action (`arm` | `disarm` | `author` | `supersede` | `allocate` | …). Open * vocabulary — control is the L2→L1 intent channel, extended as the org grows. The de-arm * discriminant (`disarm`) and its reason wording are owned by `src/engine/disarm.ts` * (`DISARM_CONTROL_TYPE`, kestrel-z473.5) — the disarm EVENT itself is intrinsic and lives here. */ readonly type: string; } // ───────────────────────────────────────────────────────────────────────────── // REGIME — open-vocabulary tag writes (CONTEXT: Regime tag) // ───────────────────────────────────────────────────────────────────────────── export interface RegimePayload { /** The tag scope (`intraday`, `session`, …). */ readonly scope: string; /** The tag value — an opaque, open-vocabulary token; the platform defines no taxonomy. */ readonly value: string; /** Who wrote it (trader-agent / PM / app detector) — writes are always attributed. */ readonly writer: string; readonly reason?: string; } export interface RegimeEvent extends EnvelopeBase, RegimePayload { readonly stream: "REGIME"; readonly type: "tag"; } // ───────────────────────────────────────────────────────────────────────────── // JOURNAL — the author's reasoning, inline (CONTEXT: Bus/Blotter; bus_schema v2) // ───────────────────────────────────────────────────────────────────────────── /** * What a JOURNAL record is (CONTEXT: Bus — "reasoning rides the bus too"): * - `author` — reasoning written **pre-hoc**, BEFORE arming. Provable pre-hoc because its * `seq` is strictly less than the first armed `PLAN`'s `seq` — the ordering is a fact of the * log, never a claim of convention. * - `debrief` — reasoning written **post-hoc**, AFTER the position CLOSEs, before finalize. * Provable post-hoc because its `seq` is strictly greater than the CLOSE `seq`. * - `note` — a free author note anywhere in the session. * * The reader tolerates an unknown/forward `kind` (a v3 journal read by a v2 build) by a logged * skip, never a throw — so this vocabulary may grow without breaking older readers. */ export type JournalKind = "author" | "debrief" | "note"; /** The known JOURNAL kinds. A JOURNAL whose `kind` is outside this set is well-formed but * unknown — the reader drops it with a logged reason (crash-tolerant forward-compat, RUNTIME §8), * never widening it onto the union. */ export const JOURNAL_KINDS: ReadonlySet<string> = new Set<JournalKind>(["author", "debrief", "note"]); /** `{kind, body}` — the author's reasoning with the **full markdown `body` inline**. Text is * truth (ADR-0010): the reasoning is not a reference to some external doc, it rides the bus. */ export interface JournalPayload { readonly kind: JournalKind; readonly body: string; } /** * A JOURNAL record. Unlike every other stream it carries **no engine `type`** — its discriminant * is `kind` — because the engine never reacts to it (it is author metadata, so it has no * `(stream,type)` pairing and does not participate in {@link isValidStreamType}). The reader * validates it on `kind`/`body` directly and never feeds it to the per-event pass, which is what * keeps the determinism invariant intact when JOURNAL records are added to a bus. */ export interface JournalEvent extends EnvelopeBase, JournalPayload { readonly stream: "JOURNAL"; } // ───────────────────────────────────────────────────────────────────────────── // TELEMETRY — engine-emitted per-order observability (a57.9) // ───────────────────────────────────────────────────────────────────────────── /** * The telemetry event vocabulary. `order` is the per-order **working-life** record (a57.9) — the * escalation stage a working order occupies plus a marker for the reprice (cancel/replace) that * produced it. `settle` is the per-order **outcome** record (a57.1 slice 1) — the accrued * probabilistic accounting the engine commits to the bus AT settle. `guard` is the per-order * **fill-guard** record (kestrel-9gu.6) — the generic assessment inputs (side / moneyness bucket / * covered-state) and the directional far-OTM cap decision the engine applied when it submitted a * leg in the far-OTM wing; it carries NO strategy-specific data (no calibration constants, no * private thresholds). The vocabulary is deliberately **open** (like DETECTOR/CONTROL) so per-order * observability can grow without a schema bump; {@link isValidStreamType} accepts any non-empty * telemetry `type` and a consumer ignores the ones it does not recognize (crash-tolerant, §8) — * which is why adding `settle` / `guard` needs no reader-gate change. */ export type TelemetryType = "order" | "settle" | "guard" | "hazard" | "settle_mark" | "theta_bleed"; /** The telemetry `type`s this build emits/recognizes. Not a closed reader gate (telemetry is * open-vocabulary) — it is the set a consumer folds; an unrecognized `type` is skipped, not an * error. */ export const TELEMETRY_TYPES: ReadonlySet<string> = new Set<TelemetryType>(["order", "settle", "guard", "hazard", "settle_mark", "theta_bleed"]); /** Coarse moneyness bucket of a leg vs the decision-time underlier (the axis the directional fill * guard keys on). Redeclared here so `src/bus` stays a leaf with no dependency on `src/fill` * (mirrors {@link FillSupport}); it is the same vocabulary as the fill model's `Moneyness`. */ export type MoneynessBucket = "itm" | "atm" | "otm" | "deep_otm"; /** The directional far-OTM cap decision recorded on a {@link TelemetryGuardPayload} (kestrel-9gu.6): * `applied` = a standalone far-OTM SELL floored to the no-fill cap (the fantasy the guard closes)