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.

322 lines 23.1 kB
/** * # frame/pane-catalog — the ONE pane registry: single source for render / View / prompt (kestrel-4gl.13) * * Every available (non-kernel) pane is catalogued HERE, exactly once, with its metadata + its pure * builder. This registry is what three consumers all read, so they can never drift: * 1. the **renderer** ({@link ./render.ts}) MATERIALIZES panes from it; * 2. the **View language** ({@link ../lang/ast.ts ViewStatement}) SELECTS panes from it (by id); * 3. a **prompt** will later ADVERTISE the menu from it (`title` + one-line `description`). * * The **cockpit kernel is NOT in this catalog** — it is built in code and LEADS every frame, * non-configurably, OUTSIDE any View selection (its reserved ids `kernel`/`cockpit` are refused, * {@link ../frame/types.ts assertPaneIdAllowed}). This catalog is the *configurable body* only. * * ## Single-source guarantee (the whole point) * A pane that is NOT in {@link PANE_CATALOG} CANNOT render (there is no other builder path), and a * View naming an id absent from the catalog FAILS CLOSED ({@link resolveView}) — never a silent * drop. Adding a new pane is one entry here; it is then selectable, renderable, and advertisable * with no change anywhere else. * * ## Attribution class (charter) + token cost * Each entry declares an {@link PaneAttribution} (`OBS` observed | `CALC` derived | `MODEL` a model * output — a MODEL pane carries a receipt in its rendered text, e.g. the chain's `fair` fairNote) * and a documented `tokenCostEstimate` (a rough chars/4 figure for the prompt menu + the View * budget planner; the REAL cost is measured at materialization under the active tokenizer). * * ## Purity (ADR-0009) * Every builder is a PURE function of its {@link PaneBuildContext} — no wall clock, no RNG; the same * context renders a byte-identical block. Invents no value (`—` for the unknowns, {@link ./format.ts}). */ import type { SeatId } from "./seat.ts"; import type { Tokenizer } from "../render/index.ts"; import type { ArmedPlanTerms, InstrumentSpec, Kernel, MarketPane, PriorSessionTape, PriorVantage, TapeRow } from "./types.ts"; /** Everything a body-pane builder may read — a pure projection of the OPEN/WAKE frame input. A * builder is a total function of THIS; it never reaches outside it (no wall clock, no RNG). */ export interface PaneBuildContext { readonly instruments: readonly InstrumentSpec[]; readonly market: MarketPane; readonly kernel: Kernel; readonly frameKind: "OPEN" | "WAKE"; /** The prior author vantage's SERVED values (kestrel-wa0j.48) — a pure projection of the WAKE * frame input's {@link ../frame/types.ts PriorVantage}. Present only on a WAKE frame the driver * threaded a prior vantage into; the `delta` pane fails closed to absent-with-reason without it. * Absent on OPEN and on any frame with no prior vantage (additive: those frames are byte-identical). */ readonly priorVantage?: PriorVantage; /** Minutes since the author's last vantage (the WAKE frame's `minutesSinceLast`) — the `(<N>m ago)` * cue on the `delta` header. Absent on OPEN / when unknown ⇒ the header omits the age (never a * fabricated gap). PURE: a value carried on the frame input, not a wall-clock read. */ readonly minutesSinceLast?: number | null; /** The prior sessions' tapes (kestrel-wa0j.20) — a pure projection of the frame input's * {@link ../frame/types.ts PriorSessionTape}[]. Present only on a frame the driver threaded a multiday * tape into; `tape d-N` serves ordinal N's rows as its OWN single-session block, else it renders the * Train 1B absence line (byte-identical). Absent on a single-day frame (additive, byte-identical). */ readonly priorSessions?: readonly PriorSessionTape[]; /** The ARMED plans' enforced terms (kestrel-wa0j.29) — a pure projection of the frame input's * {@link ../frame/types.ts ArmedPlanTerms}. Present only on a frame the driver threaded armed-plan * terms into (a wake with a plan in an enforced state); the `armed-plan` pane fails closed to exactly * one absent-with-reason line without it. Absent ⇒ byte-identical (the pane is catalog-only). */ readonly armedPlan?: ArmedPlanTerms; } /** * CALC re-bucketing (kestrel-wa0j.19 §1): aggregate consecutive runs of `factor` served rows into * ONE wider bucket. Deterministic AGGREGATION of real inputs — every number on an output row is a * function of the input rows' own numbers (open = the run's FIRST open, high = max high, low = min * low, close = the run's LAST close, clock = the CLOSING row's clock — the bucket closed there, * volume = the sum WHEN every grouped row carries one; a partially-volumed group omits it rather * than present a partial sum as the bucket's volume). A TRAILING PARTIAL run aggregates the rows * that exist — it is real data, and nothing on it is invented. Pure; input order preserved. * Exported so the aggregation semantics are directly testable (and reusable by the session lane). */ export declare function rebucketTape(rows: readonly TapeRow[], factor: number): TapeRow[]; /** * TRUE iff a `tape <window>` arg is SERVABLE by a context whose rows arrive bucketed at `bucketMin` * minutes: the window has a fixed minute width AND is a positive INTEGER MULTIPLE of the served * bucket (equal ⇒ factor 1). The ONE shared validity predicate (kestrel-wa0j.19 §1): the session * lane checks a scheduled View's window arg against it at SCHEDULE time (so a bad window is refused * before it ever reaches an unguarded delivery render), and the arged tape builder enforces the * SAME predicate at materialization — the two can never drift. Pure unit arithmetic, no context. */ export declare function windowServableBy(bucketMin: number, window: { readonly value: number; readonly unit: string; }): boolean; /** * The ATM straddle **extrinsic** (time value remaining) off the near-money book — `gross(book mid) − * intrinsic` at the strike nearest spot that carries BOTH a C and a P leg. Pure CALC over the OBSERVED * chain + spot, mirroring the `vol` pane's decomposition (kestrel-wa0j.7). Returns `null` when it is * NOT honestly computable: spot unknown, no ATM strike with both legs, a dark/one-sided ATM leg, or * duplicate (strike,right) rows (an ambiguous book) — never a fabricated value. Exported + shared so * the `delta` pane's CURRENT read and the driver's PRIOR-vantage capture compute the SAME number * (they can never drift). The "never a price anchor" rule is respected: this is a labelled read of the * book's own mids, never used to anchor an order price. */ export declare function atmStraddleExtrinsic(market: MarketPane): number | null; /** The sell-floor POLICY line — the never-naked floor's decision-nearest spelling (close.txt POLICY). * ONE definition: if the kernel ever renders a standing floor line, it must reuse THIS byte string so * the two occurrences are byte-identical (ADR-0041 §1 exactly-twice channel invariant). */ export declare const CLOSE_SELL_FLOOR_POLICY = "sell floor=intrinsic; BELOW rows cannot anchor a sell"; /** A pane's attribution class (the Field/Attribution charter, 3-class): `OBS` an observed fact, * `CALC` a derived quantity, `MODEL` a model output that carries a receipt in its rendered text. */ export type PaneAttribution = "OBS" | "CALC" | "MODEL"; /** A pane argument as the renderer consumes it — the structural mirror of the language's * {@link ../lang/ast.ts PaneArg}, decoupled from the AST exactly as {@link ViewSelection} is (a * caller can supply args from config without importing the language; the AST's own `PaneArg` is * assignable to this shape). It carries only a **syntactic supersort** — a bare ident, a window * `5m`, or a bare count numeral `12` — which a {@link ParamSlot} REFINES to a semantic sort at * materialization (ADR-0041 §1: the parser assigns the supersort with zero catalog knowledge). */ export type PaneSelectionArg = { readonly kind: "arg-ident"; readonly name: string; } | { readonly kind: "arg-window"; readonly window: { readonly value: number; readonly unit: string; }; } | { readonly kind: "arg-count"; readonly count: number; } | { readonly kind: "arg-ordinal"; readonly ordinal: number; } | { readonly kind: "arg-expiry"; readonly expiry: number; }; /** A closed index sort a {@link ParamSlot} may denote (ADR-0041 §1). LIVE subset after Train 1B: * `LevelName` / `Window` / `Count` / `SessionOrdinal` / `ExpiryOrdinal` — Train 1B opened * `SessionOrdinal` (`tape d-N`) and `ExpiryOrdinal` (`chain e-N`) by the sort-introduction ceremony * (ADR-0041 §3: teaching attestation + canonical-print/round-trip + denotation + consuming signature * + reject fixture). `Instrument` is listed because it is pinned into the {@link POSITION_CLASS_ORDER} * now (its slot arrives with the CALC cross-instrument lexeme, a later train — pinning ahead of need * is the whole point: argument order is canonical bytes, hash-breaking to retrofit). The DEFERRED * roster member `Band` is opened only by its own ceremony as its train lands — the roster is closed at * any instant. */ export type ParamSort = "Instrument" | "LevelName" | "Window" | "Count" | "SessionOrdinal" | "ExpiryOrdinal"; /** The five closed SURFACE classes the parser assigns with zero catalog knowledge (ADR-0041 §1 — the * `wf` rung). A {@link ParamSlot}'s semantic {@link ParamSort} refines FROM exactly one of these. */ export type SyntacticSupersort = "ident" | "window" | "numeral" | "ordinal" | "expiry"; /** * THE ONE PINNED CROSS-PANE POSITION-CLASS ORDERING (ADR-0041 §1 — "one hypothesis, one hash"). * * The single order in which sorts emit in a pane's canonical argument sequence, fixed ONCE across * EVERY pane. Because printed argument order is part of the canonical bytes (and the template SKU is * the sha256 of those bytes), this ordering is **hash-breaking to retrofit** and so is pinned here, * in ONE place, before Train 1 prints its first canonical form. * * DERIVATION (reconciled with `tests/golden/accept/views.kestrel` + the Train 1B honest-absence * teaching forms, NOT chosen by taste — ADR-0041 §1 "derived by reconciling with what views.kestrel * already teaches"): * • `Instrument` FIRST — it is the concord head (ADR-0041 §1 Concord: "every pane's Instrument * agrees with the head"); the marked cross-instrument exception spells its two `Instrument` args * ahead of everything else, so the head noun leads the phrase. * • `LevelName` next — the named-address selectors (`levels vwap hod`) name WHICH addresses inside * the already-fixed instrument. * • `Window` next — the timescale scopes the temporal resolution of the addressed slice. * • `Count` next — a cardinality over the already-addressed, already-scoped slice (`chain 12`). * • `SessionOrdinal` next, then `ExpiryOrdinal` LAST (Train 1B, APPENDED — extend-only). * * WHY SessionOrdinal / ExpiryOrdinal are APPENDED (not inserted before Window/Count), despite a * session/expiry selector reading semantically like an "addressing" class that might sit earlier: * the ordering constant is **extend-only, existing ranks unchanged** — the hash-stability rule * (ADR-0041 §1: printed argument order is canonical bytes, hash-breaking to retrofit). Inserting a * sort mid-list would renumber `Window`/`Count` and rewrite the canonical bytes of the already-pinned * Train-1A forms — forbidden. The COORDINATION is not free-floating: the Train 1B teaching forms * `tape 5m d-0` (Window BEFORE SessionOrdinal) and `chain 12 e-0` (Count BEFORE ExpiryOrdinal) are * the corpus evidence that PINS Window < SessionOrdinal and Count < ExpiryOrdinal — the append order * is exactly what those canonical forms already print. `SessionOrdinal < ExpiryOrdinal` (session * addressing outer to expiry selection; no single pane carries both, so their mutual order is inert, * pinned session-before-expiry to match the `d-` before `e-` reading). Existing ranks — Instrument 0, * LevelName 1, Window 2, Count 3 — are UNCHANGED, so no Train-1A canonical form re-hashes. * views.kestrel contains no counter-example: its one multi-sort line (`tape skyline 5m vwap * detector-strip`) is the DEFERRED full-tape per-pane position layout (style<window<overlay<strip — * distinct position classes, LATENT until the multi-arg tape train), not the cross-pane SORT total * order pinned here. * * ENFORCED (not merely documented): {@link paramSortRank} + a signature-order invariant test assert * every catalog signature declares its slots in non-decreasing rank, so no pane can ever spell its * args out of the pinned order. */ export declare const POSITION_CLASS_ORDER: readonly ParamSort[]; /** The rank of a sort in the pinned {@link POSITION_CLASS_ORDER} (lower prints earlier). */ export declare function paramSortRank(sort: ParamSort): number; /** How many args a slot binds: exactly `one`, or `many` (variadic — MUST be the terminal slot of a * signature, since a variadic slot consumes the rest of the positional args). */ export type SlotArity = "one" | "many"; /** One position class of a pane's signature (ADR-0041 §1). `name` labels the slot in a repair * message; `sort` is the semantic sort an arg must elaborate to; `arity` is one/many; `default` is * the elaborated value when the slot is unfilled (UNTOUCHED in Train 1A — no pane declares one, so * the no-arg path stays byte-identical). */ export interface ParamSlot { readonly name: string; readonly sort: ParamSort; readonly arity: SlotArity; readonly default?: PaneSelectionArg; } /** One catalogued pane — the single source of truth entry. `id` is what a {@link * ../lang/ast.ts ViewStatement} names; `title`/`description` are the prompt menu; `attribution` * + `tokenCostEstimate` are planning metadata; `builder` is the pure renderer. */ export interface PaneCatalogEntry { /** Stable pane id — what a View selects by, and what the prompt advertises. Never a reserved * kernel id ({@link ../frame/types.ts assertPaneIdAllowed}). */ readonly id: string; /** Human title for the prompt menu. */ readonly title: string; /** One-line description for the prompt menu (what this pane shows). */ readonly description: string; /** Attribution class (OBS | CALC | MODEL — a MODEL pane carries a receipt in its text). */ readonly attribution: PaneAttribution; /** A documented rough token-cost estimate (chars/4 order-of-magnitude) for the prompt menu + * View budget planning. The REAL cost is measured at materialization under the active tokenizer. */ readonly tokenCostEstimate: number; /** The pure builder: a total function of the {@link PaneBuildContext}. */ readonly builder: (ctx: PaneBuildContext) => string; /** The ordered {@link ParamSlot} signature (ADR-0041 §1 inflection substrate) — the closed index * sorts this pane's positional args address. Its PRESENCE (with an {@link argedBuilder}) is what * lets a View pass args; {@link bindArgs} elaborates the args against it at the `mat` rung. Absent * ⇒ the pane takes no args ({@link resolveView} refuses any). Slots MUST be declared in * non-decreasing {@link paramSortRank} (the pinned cross-pane ordering — enforced by test). */ readonly signature?: readonly ParamSlot[]; /** The ARGED builder — present ONLY on a pane with a {@link signature} (kestrel-wa0j.3 / * ADR-0041 §1). It receives args ALREADY bound + sort-checked by {@link bindArgs} (the `mat` * rung), and applies the `renderable`-rung VALUE checks that need the Frame (a window the served * bucket cannot express; a count the frozen chain is short of; a level ident absent from the * LevelSet), THROWING a {@link KernelHonestyError} naming pane + arg. Pure, exactly like `builder`. */ readonly argedBuilder?: (ctx: PaneBuildContext, args: readonly PaneSelectionArg[]) => string; } /** The ordered list of catalogued panes (single source of truth). New panes slot in here. */ export declare const PANE_CATALOG: readonly PaneCatalogEntry[]; /** Look up a catalogued pane by id, or `undefined`. The ONLY builder path — a pane not here cannot render. */ export declare function paneById(id: string): PaneCatalogEntry | undefined; /** The OPEN keyframe's hardcoded pane order. kestrel-wa0j.47: `macro` left the default set — in the * v1 harness the pane can only ever render its absence, and that absence already renders once in the * kernel's unavailable-capabilities line (a non-safety fact renders exactly once, ADR-0041 §1). The * pane stays catalogued and addressable: a View that names `macro` still renders absent-with-reason. */ export declare const DEFAULT_OPEN_PANES: readonly string[]; /** The WAKE delta frame's hardcoded pane order (byte-identical to the pre-catalog renderer). */ export declare const DEFAULT_WAKE_PANES: readonly string[]; /** The default (no View supplied) ordered pane ids for a frame kind — the current hardcoded set. */ export declare function defaultPaneIds(frameKind: "OPEN" | "WAKE"): readonly string[]; /** One resolved pane of a {@link ResolvedView}: its validated catalog id + the (possibly empty) * args the View passed it — validated as UNDERSTOOD at resolution (only a pane with an * `argedBuilder` may carry args), with the arg VALUES validated against the context at * materialization (the builders are pure functions of the context; resolution has none). */ export interface ResolvedPane { readonly id: string; readonly args: readonly PaneSelectionArg[]; } /** A View resolved against the catalog: an ordered list of validated panes (id + args) + an * optional token budget. This is what the renderer materializes; the ViewStatement is resolved * into it once. `panes` is the canonical field; `paneIds` is its id projection (kept for id-only * consumers), derived ONLY by {@link resolvedViewOf} — never assembled by hand, so the two can * never drift. */ export interface ResolvedView { readonly name: string; readonly paneIds: readonly string[]; readonly panes: readonly ResolvedPane[]; readonly budget?: number; } /** The minimal View selection shape the renderer consults — a subset of {@link * ../lang/ast.ts ViewStatement} (name + ordered pane names, each with the ViewStatement's optional * pane args + optional token budget). Decoupled from the AST so a caller can supply a View from * config without importing the language (the AST's `PaneRef.args` is assignable to `args`). */ export interface ViewSelection { readonly name: string; readonly panes: readonly { readonly name: string; readonly args?: readonly PaneSelectionArg[]; }[]; readonly budget?: number; } /** * Resolve a View selection against the catalog into an ordered, VALIDATED {@link ResolvedView}, or * fall back to a founder seat View / the frame kind's DEFAULT View when none is supplied. FAIL-CLOSED: * a pane id the View names that is absent from the catalog THROWS a {@link KernelHonestyError} (never * a silent drop — a View can only select panes that exist); a View may never name a reserved kernel id * ({@link ../frame/types.ts assertPaneIdAllowed}); and args passed to a pane that has no * `argedBuilder` are REFUSED here (an arg is never silently dropped — kestrel-wa0j.3). Arg VALUES * are validated against the build context at materialization (resolution has no context). Pure. * * ## Role-keyed resolution (ADR-0041 §2 / A1, kestrel-wa0j.26) * The optional `seat` binds the acting pod {@link SeatId} for this frame. The PRECEDENCE is strict: * * explicit authored View > seat founder View > phase default * * 1. an explicit `view` ALWAYS wins — a seat never overrides an authored/scheduled/forced lens * (`seat` is ignored when `view` is present); * 2. else, when a `seat` is supplied AND it has a founder seed for this frame kind * ({@link ./founder-registry.ts founderViewFor}), that founder View resolves — the graded SEED * for the role (never a blessed default: it is delivered ONLY because the caller opted in); * 3. else the frame kind's phase default (byte-identical to the pre-role-axis behaviour). * * NO-CHURN: `seat` is optional and defaulted absent, so EVERY existing call site (no seat) resolves * byte-identically to before this axis existed — the founder branch is unreachable without a seat, and * a seat with no founder seed for the frame falls through to the very same phase-default path. The * config opt-in that decides whether the driver passes a seat at all lives in the driver * (`AgentConfig.seatViews`), never here — this function only applies the precedence it is handed. */ export declare function resolveView(view: ViewSelection | undefined, frameKind: "OPEN" | "WAKE", seat?: SeatId): ResolvedView; /** * Render ONE catalogued pane through the real bind → build path — the exact per-pane step {@link * materializePanes} drives for every pane in a resolved View, exposed as the public per-pane entry * (kestrel-z473.4). It IS that path (materialization calls this), so the bind/compose layer gets * covered by every pane test for free and the `builder`/`argedBuilder` handles no longer need to be * reached directly: no args ⇒ the pure `builder`; args ⇒ {@link bindArgs} (the `mat`-rung sort/arity * refusal, catalog-only + Frame-free) THEN the `argedBuilder` (the `renderable`-rung VALUE checks that * need the Frame). FAIL-CLOSED: an `id` absent from the catalog THROWS a {@link KernelHonestyError} * (a pane not catalogued cannot render — never a silent empty block), and args passed to an arg-less * pane are REFUSED here exactly as materialization refuses them. Pure — every builder is a total * function of `ctx` (no wall clock, no RNG). Output bytes are byte-identical to the direct handle call. */ export declare function renderPane(id: string, args: readonly PaneSelectionArg[], ctx: PaneBuildContext): string; /** * Materialize a resolved View into its ordered list of rendered body-pane blocks (the kernel is * NOT here — it leads, non-configurably, in the renderer). Each pane is built PURELY from `ctx` via * {@link renderPane} (the single per-pane bind → build path) and measured under `tokenizer`. BUDGET * GUARD (fail-closed): if the View declares a token budget and the SELECTED panes' measured total * exceeds it, this THROWS a {@link KernelHonestyError} naming the overage — never a silent drop of a * pane the View asked for (an over-budget View is a defect the author must fix, exactly as the * unknown-pane and kernel-lead guards fail closed). Pure. */ export declare function materializePanes(resolved: ResolvedView, ctx: PaneBuildContext, tokenizer: Tokenizer): readonly string[]; //# sourceMappingURL=pane-catalog.d.ts.map