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.

80 lines 5.46 kB
/** * # bus/anchor-ledger — the SPOT-anchor UNKNOWN-window ledger + fail-closed under-report validator (ven.4) * * The de-arm PRIMITIVE already lives in the series layer: an unresolvable value anchor reads * UNKNOWN (`CanonicalState.spot: number | Unknown`) and any statement/detector referencing it * de-arms with a logged reason — it can never fire on a value it does not have (fail-closed, * RUNTIME §3/§8). What was missing is a **LEDGER** of those value gaps: a past-only record a * consumer can read so it is never misled into thinking a gapped/dead-feed window carried a real * value. * * ## ONE TRUTH — the ledger records EXACTLY the de-arm's windows (never a forked concept) * The value anchor is UNKNOWN precisely while no SPOT for the signal instrument has been folded — * the exact condition `CanonicalState.applyEvent` uses to set `spotVal` (a `TICK`/`SPOT` event for * the matching instrument; RUNTIME §2). This module re-derives THAT SAME predicate at the bus layer * — it does not import the series layer (the module spine is `series → bus`, never the reverse), so * the equivalence is proved event-by-event by the ven.4 suite rather than by a cross-layer call. * The ledger's covered seqs therefore equal the seqs where `CanonicalState.spot === UNKNOWN`: the * detectors de-arm across exactly the windows the ledger records. One truth, not two. * * ## PAST-ONLY / NO-LOOKAHEAD (RUNTIME §0) * A window is derived by a single forward replay; every `{start,end,dur}` bound is an * already-elapsed event. A window that resolves ends at the LAST still-UNKNOWN event — the event * *before* the resolving SPOT — never the resolver and never a synthetic future sentinel. A window * still open at end-of-tape ends at the last elapsed event. `dur = end.ts - start.ts`. No wall * clock, no RNG: same bus ⇒ byte-identical ledger (the determinism invariant). * * ## NO UNDER-REPORT — honest BY CONSTRUCTION (kestrel-voy9) * The ledger can never under-report the gaps, because {@link spotAnchorUnknownWindows} IS the sole * derivation: it replays the de-arm predicate forward and records EXACTLY the seqs the anchor read * UNKNOWN (a dropped window, a later start, an earlier end, a shortened `dur` are all unrepresentable * — the derivation emits the real span or nothing). The ONE-TRUTH property is proved end-to-end in * the ven.4 suite by driving this real derivation and asserting its covered seqs BYTE-MATCH the * series-layer `CanonicalState.spot === UNKNOWN` primitive, event-by-event, across a resolving, a * never-resolving, and a px-blind bus. There is NO standalone under-report validator: a guard that * only ever re-checks this same constructor's own honest output is a tautology (it can never fire), * and no production path SUPPLIES a foreign ledger to validate — so the honesty lives in the * construction, not in an unreachable export. */ import type { BusEvent } from "./types.ts"; /** A `(seq, ts)` reading — an already-elapsed bus event, referenced by both the writer's monotonic * `seq` and its injected `ts` (RUNTIME §0/§1). */ export interface SeqTs { readonly seq: number; readonly ts: number; } /** * One window the SPOT/value anchor was UNKNOWN, with PAST-ONLY bounds. `start` is the first event at * which the anchor read UNKNOWN (session open / META — the LEADING pre-first-spot span); `end` is the * LAST still-UNKNOWN event (never the resolving SPOT, never a future sentinel); `dur` is the elapsed * span `end.ts - start.ts`. The value anchor is STICKY (see {@link spotAnchorUnknownWindows}), so a * well-formed session yields exactly ONE such window — the leading one — never a mid-session re-lapse. */ export interface AnchorWindow { readonly start: SeqTs; readonly end: SeqTs; readonly dur: number; } /** * Derive the SPOT-anchor UNKNOWN-window ledger from a bus, purely and deterministically * (RUNTIME §0). A single forward replay: the anchor is UNKNOWN until the first px-bearing SPOT for * `instrument` is folded, at which point it resolves. * * The value anchor is **STICKY**: `CanonicalState.spotVal` is set once by `onSpot` and, on a * well-formed tape, never reverts (barring a `CanonicalState` reset, which this replay does not * model), so the `anchorKnown` latch below likewise never resets. This replay therefore records * **exactly the LEADING window** — the pre-first-spot gap — and, when no SPOT ever resolves the * anchor, the whole tape as one window. It does NOT record a mid-session lapse back to UNKNOWN: that * "gapped/dead-feed mid-session staleness" is a SEPARATE primitive concern (a staleness/heartbeat * backstop over `spotTs`, not the value-anchor de-arm this ledger mirrors) and is out of scope here — * mirroring it would be forking a second window concept, which ONE TRUTH forbids. Every bound is * past-only. The input is never mutated. * * @param events the bus, in `seq` order (as the writer emits it). * @param instrument the signal instrument whose value anchor is tracked (mirrors * `CanonicalStateConfig.instrument`). * @returns the leading window the anchor was UNKNOWN (0 or 1 windows), in `seq` order — the same span * the value-anchor de-arm covers. */ export declare function spotAnchorUnknownWindows(events: readonly BusEvent[], instrument: string): readonly AnchorWindow[]; //# sourceMappingURL=anchor-ledger.d.ts.map