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
Markdown
# 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.