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.

217 lines (216 loc) 13 kB
/** * # frame/types — the plain, typed inputs a Frame renderer consumes (ADR-0008/0009) * * The renderer is a **pure function of these objects** — it invents no value (ADR-0008 * lineage): every number it prints is a field on the input, and an absent/UNKNOWN field * renders as an explicit `—` (never a guessed or defaulted number). The stepped day runner * populates these from the canonical state (`spot`/`hod`/`lod`/`vwap`/opening range, RUNTIME * §2), the book reduction ({@link ../bus/types.ts BookState}), and the fill/plan engines * (positions, resting orders, fills, budget, plan lifecycle — RUNTIME §5–6). * * ## Date-blind by construction (EVALUATION.md) * There is **no date field anywhere** in these inputs. Time is carried three ways, all * relative or clock-only: minutes-to-close (`T-92m to close`), a wake ordinal + minutes since * the last vantage (`wake 3, 41m since last`), and an **HH:MM ET clock** string on tape rows * and headers (`13:24`). `session_date`, day-of-week, and epoch-millisecond values are * **forbidden** — the runner must not put them here, and the renderer never emits them. A CI * grep over rendered output enforces this. * * ## Unknowns are modelled explicitly * A value that is genuinely unavailable is `null` (or an omitted optional) — the renderer * turns it into `—`. This is the fail-closed discipline of RUNTIME §8 carried into the screen: * an unknown is shown as unknown, never filled in. * * ## The Frame of Fields + the cockpit lead block (this milestone) * Two additions land here on top of the plain inputs above: * - {@link Field} (4gl.1) — the one **watermarked, attributed** value type: every number the * author reads is a `Field { value, attribution, source, modelVer, confidence, asOfSeq }`, so a * number carries how it was derived (`OBS`/`CALC`/`MODEL`) and, for a `MODEL` value, its receipt * (source + modelVer) and confidence. {@link makeField} is the construct-time honesty guard: a * `MODEL` field that omits its receipt or confidence is REFUSED (fail-closed). * - The extended {@link Kernel} — the acting **cockpit lead block** (4gl.3): wake, per-vehicle * data-health + named unavailable capabilities, positions/inventory-claims, resting orders, * the risk-envelope budget, owner acts, the engine log, and predictor/regime claims — the * superset of the plan-lifecycle section. Every section is always present (absent renders as an * explicit `none`/`UNKNOWN`, never dropped), the block LEADS every frame, and it is * non-configurable (built in code and prepended outside any pane selection — see * {@link KERNEL_SECTION_LABELS} / {@link RESERVED_PANE_IDS}). */ /** * Raised by the honesty guards when a claim/field would misrepresent its provenance. Throwing is * the fail-closed response: a dishonest `MODEL` claim is REFUSED, never rendered as if honest. */ export class KernelHonestyError extends Error { constructor(message) { super(message); this.name = "KernelHonestyError"; } } /** * The `[0,1]` range contract {@link Field.confidence} pins, as an executable predicate (4gl.19). * Merely FINITE is not honest: a `confidence` of `1.7` or `-0.5` is a number the contract cannot * express, so admitting it would let a guard report "honest MODEL claim" while the frame renders * `conf=1.70` — a silent-false. Bounds are CLOSED (`0` and `1` are honest: no confidence, and * certainty). `NaN`/`±Infinity` fail both comparisons, so they are refused here too. */ function isHonestConfidence(confidence) { return confidence >= 0 && confidence <= 1; } /** * Construct a {@link Field}, refusing a dishonest one at construct time (the 4gl.1 honesty guard): * - a `MODEL` field MUST carry `source` + `modelVer` + a `confidence` in `[0,1]` — a `MODEL` claim * missing any of them, or carrying an out-of-range confidence, is REFUSED (fail-closed); * - an `OBS`/`CALC` field MUST carry neither a `modelVer` receipt nor a `confidence` — a * mis-attributed field that smuggles a model receipt in as `OBS`/`CALC` is REFUSED. * Never returns a Field that violates the attribution contract. */ export function makeField(spec) { const { attribution } = spec; if (attribution === "MODEL") { if (spec.source === undefined || spec.source === "") { throw new KernelHonestyError("MODEL Field carries no source watermark — a MODEL value MUST carry source(modelVer) + confidence"); } if (spec.modelVer === undefined || spec.modelVer === "") { throw new KernelHonestyError("MODEL Field carries no modelVer receipt — a MODEL value MUST carry source(modelVer) + confidence"); } if (spec.confidence === undefined) { throw new KernelHonestyError("MODEL Field carries no confidence — a MODEL value MUST carry source(modelVer) + confidence"); } if (!isHonestConfidence(spec.confidence)) { throw new KernelHonestyError(`MODEL Field carries an out-of-range confidence ${spec.confidence} — MODEL confidence MUST be in [0,1]`); } } else { if (spec.confidence !== undefined) { throw new KernelHonestyError(`${attribution} Field must carry no confidence`); } if (spec.modelVer !== undefined) { throw new KernelHonestyError(`${attribution} Field must carry no modelVer receipt`); } } // Build without ever assigning `undefined` to an optional key (exactOptionalPropertyTypes). const out = { value: spec.value, attribution }; if (spec.source !== undefined) out.source = spec.source; if (spec.modelVer !== undefined) out.modelVer = spec.modelVer; if (spec.confidence !== undefined) out.confidence = spec.confidence; if (spec.asOfSeq !== undefined) out.asOfSeq = spec.asOfSeq; return out; } /** * The honesty guard, as a validator (4gl.3): a predictor/regime claim MUST be a `MODEL` {@link * Field} with a source watermark, a `modelVer` receipt, and a `confidence` in `[0,1]`. An * `OBS`/`CALC` "claim", or a `MODEL` claim missing its receipt/confidence or carrying an * out-of-range confidence (4gl.19), is REFUSED — throwing a {@link KernelHonestyError} * (fail-closed; a dishonest claim never renders as if honest). */ export function assertClaimHonest(claim) { const label = claim.claimType; const f = claim.field; if (f === undefined || f === null) { throw new KernelHonestyError(`claim ${label} carries no Field`); } if (f.attribution !== "MODEL") { throw new KernelHonestyError(`claim ${label} is attributed ${f.attribution}, not MODEL — a predictor/regime claim MUST be ` + "a MODEL Field with source + confidence (honesty guard)"); } if (f.source === undefined || f.source === "") { throw new KernelHonestyError(`MODEL claim ${label} carries no source watermark`); } if (f.modelVer === undefined || f.modelVer === "") { throw new KernelHonestyError(`MODEL claim ${label} carries no modelVer receipt`); } if (f.confidence === undefined) { throw new KernelHonestyError(`MODEL claim ${label} carries no confidence`); } if (!isHonestConfidence(f.confidence)) { throw new KernelHonestyError(`MODEL claim ${label} carries an out-of-range confidence ${f.confidence} — MODEL confidence MUST be in [0,1]`); } } // ── The non-configurable / fail-closed contract of the cockpit lead block ──────── // The kernel LEADS every frame and is built in code, prepended OUTSIDE any pane selection; it // cannot be dropped or reordered by configuration. These constants are the canonical contract a // renderer consumes: the lead sentinel, the 8 fixed-order section labels (every section always // present — absent-not-hidden), and the reserved pane ids a configured pane may never claim. /** The line the cockpit lead block begins with — the kernel-leads sentinel a frame checks for. */ export const KERNEL_LEAD_SENTINEL = "==== SAFETY / CONTROL KERNEL (non-configurable; leads every frame) ===="; /** * The line a **delta-encoded** kernel block begins with (kestrel-312, the KV-cache-efficiency half * of the v5 percept). In a cache-monotone stream (the `conversation` / `conversation-cached` * policies, where the reader provably holds the prior full kernel in cached context) a WAKE frame's * kernel is transmitted as ONLY the fields that MOVED since the prior frame — the byte-stable * skeleton (the {@link KERNEL_LEAD_SENTINEL} banner, the 8 section labels, and every unchanged field * value) lives in the reader's cached prefix and is NOT re-emitted. This sentinel LEADS such a block * so the lead guard ({@link ./render.ts assertKernelLeads}) still fires, and it tells the reader the * complete current kernel is `cached skeleton + these moved fields`. * * ## The reinterpreted lead invariant (fail-closed, honesty-preserving) * Every frame makes the COMPLETE current kernel available to the reader — as a full block in a * KEYFRAME, or as `cached-skeleton + delta` in a cache-monotone stream — and a fail-closed * composed-completeness guard ({@link ./render.ts composeKernelDelta}) verifies the composition * reconstructs the full kernel BYTE-IDENTICALLY (a delta that would compose to an INCOMPLETE kernel * throws, exactly as the lead guard does). Absent-not-hidden is preserved: a field that MOVED to * UNKNOWN is present-and-marked in the delta, never silently omitted — omitted-because-unchanged * (not in the moved-set, held in cache) is never confused with omitted-because-unknown. A KEYFRAME * (the OPEN briefing, and every `stateless-redraw` frame where the reader holds NO prior context) * ALWAYS carries the COMPLETE {@link KERNEL_LEAD_SENTINEL} block — self-complete frames stay * self-complete. */ export const KERNEL_DELTA_SENTINEL = "==== KERNEL DELTA (unlisted fields unchanged) ===="; /** The 8 fixed, always-present section labels — in fixed order; none may be dropped or reordered. */ export const KERNEL_SECTION_LABELS = [ "-- WAKE --", "-- DATA-HEALTH --", "-- POSITIONS / INVENTORY-CLAIMS --", "-- RESTING ORDERS --", "-- BUDGET / REMAINING-R --", "-- OWNER ENVELOPE + ACTS --", "-- L0/L1 ENGINE LOG --", "-- PREDICTOR / REGIME CLAIMS --", ]; /** Pane ids reserved for the non-configurable kernel — a configured pane may never claim them. */ export const RESERVED_PANE_IDS = ["kernel", "cockpit"]; /** * Fail-closed guard for pane configuration: a configured pane may not claim a reserved id (the * kernel is built in code and cannot be impersonated or displaced by a pane). Throws a * {@link KernelHonestyError} on a reserved id. */ export function assertPaneIdAllowed(paneId) { const id = paneId.trim().toLowerCase(); if (RESERVED_PANE_IDS.includes(id)) { throw new KernelHonestyError(`pane id "${paneId}" is reserved for the non-configurable cockpit kernel and cannot be ` + "claimed by a configured pane (fail-closed)"); } } /** * Fail-closed guard for a GRADED run: a benchmark cell MUST state what it is optimizing. Throws a * {@link KernelHonestyError} when there is no {@link Mandate} (or a malformed one — a blank objective, * a non-positive/absent `rUsd`, a blank success/risk rule). Takes the {@link Mandate} DIRECTLY (not a * Kernel) so it wires at the point a graded run COMMITS its mandate — the plan-fixture freeze, where * the mandate binds into `plan_fixture_sha` grade provenance (ADR-0032 §7, * {@link ../session/harness/plan-fixture.ts capturePlanFixture}). The deterministic grade driver never * renders, so the honesty is enforced at that commit, not behind a render flag no production path sets. * The renderer itself stays tolerant — an ungraded/degenerate frame renders without a mandate section. */ export function assertGradedMandate(m) { if (m === undefined || m === null) { throw new KernelHonestyError("graded run declares no MANDATE — a benchmark cell MUST state what it is optimizing (objective, " + "1R = $X, success criterion, bounded-risk rule). Fail-closed: the cell must supply a mandate."); } const bad = []; if (typeof m.objective !== "string" || m.objective.trim() === "") bad.push("objective"); if (typeof m.rUsd !== "number" || !Number.isFinite(m.rUsd) || m.rUsd <= 0) bad.push("rUsd (1R = $X, > 0)"); if (typeof m.successCriterion !== "string" || m.successCriterion.trim() === "") bad.push("successCriterion"); if (typeof m.riskRule !== "string" || m.riskRule.trim() === "") bad.push("riskRule"); if (bad.length > 0) { throw new KernelHonestyError(`graded run declares an INCOMPLETE mandate — missing/blank: ${bad.join(", ")} (fail-closed; a graded ` + "run must state a complete objective + R-definition + success criterion + bounded-risk rule)."); } }