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
JavaScript
/**
* # 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).");
}
}