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.

64 lines (56 loc) 4.79 kB
# The Blotter projector is a pure function of the Bus; the graded bus self-describes its judge > **Supersession mark (2026-07-16).** This ADR's references to the older `report` / > `expected_usd` **live projection** (the engine-scraped `SessionReport` that this ADR > still names as the incumbent second projection) are **retired by ADR-0039***the > Blotter is the sole Session projection; `SessionReport` is retired*. The pure-function > `project(bus) Blotter` invariant below stands unchanged and is the survivor; only the > report as a parallel live projection is superseded. The Blotter (`src/blotter`, kestrel-a57.1) is the deterministic projection of one Session Bus into the canonical typed record of that Session. Its one invariant: **`project(bus)` is a pure function — the same Bus projects to a byte-identical Blotter, and no engine-state scrape is ever permitted.** Everything below follows from taking that literally. This splits the world into **two buses**. The **market-data input bus** that Grade replays is fill-model-*agnostic* — the same recorded tape is graded under many models. The **graded bus** (the input plus the engine's reactions, produced under one fill model) is what the projector consumes, and *it* self-describes its judge: the session driver, at finalize, stamps `fill_model{name, version, calibration_sha}`, `instance{pod, lineage, version, mode}`, and `fidelity` onto the graded bus's `META`. We chose this over passing those as side inputs to `project()` because the fill model *produced* the fills on this bus — it is a property *of* the graded bus, not an external annotation — so putting it on the bus makes "the Bus is the truth" (ADR-0010) literal and makes certification (re-project + byte-compare) a pure operation on bus bytes with nothing external to reproduce. Taking "no engine-state scrape" literally has a sharp consequence for the **probabilistic fill model**. A grade is scored probabilistically — each resting order accrues a survival product `S = Π(1 pFillᵢ)`, and a Blotter carries both a conservative `floor` (definite fills only) and an `expected` E[$] (weighted by `1 S`). Today that accounting lives only in the engine's settle state and the report; the bus records fills but not their `pFill` or `calibrated|extrapolated` support. So the projector *cannot* honestly derive `totals.expected` or `orders[].fill_claim` from the bus as things stand. Therefore the **first slice of a57.1 extends the bus**: the engine (the single writer) records per-fill `pFill` + `support` (on the `fill` ORDER event or the `TELEMETRY` stream), and the projector re-aggregates the survival product, the floor/expected totals, and the fill-claims from bus bytes alone. This also finally lands the `9gu.3` support flag on the bus, where it was deferred. The projector emits **one canonical serialization: deterministic machine-JSON** (ordered keys, no wall clock), exactly as the Bus is canonical JSONL — this is what certification byte-compares. The Blotter is a *regenerable projection*, not a competing legible truth (ADR-0010 already seats legibility in the Bus JSONL and the inline JOURNAL markdown), so **all human/legible rendering is the viewer's job (a57.13), never the projector's** — which keeps the seam clean: `project(bus) Blotter`, `serialize(blotter) canonical bytes`, `certify(bus) verdict by re-projection`. Finally, a Blotter carries two *orthogonal* stamps that must never be conflated: **Certification** (is this record faithful and reproducible?) and **Fidelity** (how real are its fills?). a57.1 owns the *shape* of both, implements the **determinism leg** of Certification (re-project + byte-compare — the projector proving its own core invariant; in sim, determinism-pass with the paper/live legs `na` verdict `certified`), and carries **Fidelity** as a pass-through from the now-extended `META`. The richer certification legs (detectors bit-for-bit, fills live, lifecycle divergence explained `provisional`) land with paper/live modes in a57.3; the derived per-fill `fill_claim[]` lands in a57.4. Consequences: `META` gains `fill_model`/`instance`/`fidelity`, a deliberate `bus_schema` bump whose only golden churn is a one-time, in-commit `bus_sha256`/determinism-hash regeneration (the input tapes stay byte-identical; a re-grade reproduces them). The engine records the probabilistic accounting on the bus, so the report's `expected_usd` becomes a *projection* rather than an engine scrape. Grade replays buses and reads Blotters; review tooling and viewers import the Blotter (`src/blotter`) and never the judge. Certified never means good — a certified Blotter can hold a terrible strategy; Fidelity, not Certification, is what a result may claim.