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.
63 lines • 4.59 kB
TypeScript
/**
* # validate/unknown-series — the ONE arm-time unknown-series case-variant refusal (kestrel-h0ax)
*
* The single, LIGHT home of the arm-time market-vocabulary integrity check (kestrel-mte / OSS-ADR-0045):
* a trigger operand that names a CASE-VARIANT of a registered market fact (`HOD` for the scalar `hod`,
* `VELOCITY` for the windowed `velocity`) resolves UNKNOWN forever — the plan would arm but never fire.
* Because {@link SeriesRegistry.lookup} is EXACT-MATCH (OSS-ADR-0045; the 0.4.5 case-insensitive
* forgiveness is reverted), the case-variant name has NO phonebook record, so this refuses it AS DATA at
* arm — naming the offending token AND the intended market series — its healthy siblings still arm.
*
* ## Why this file exists (the mei bug: parse ≠ arm)
* This check used to live in exactly ONE place — `PlanEngine.#unknownSeriesRejection` in
* `src/engine/plans.ts` — reachable only by constructing the HEAVY {@link PlanEngine} (which drags the
* fill engine, the gate, the bus). A parse-only `/validate` never saw it, so `parse(HOD…)` answered
* `{valid:true}` and the same document then `422`'d at `/sim` (platform bead kestrel-markets-mei). This
* module lifts the check onto a LIGHT dependency set — the lang AST + the {@link SeriesRegistry}
* phonebook, with NO lake/feed/fill/session/bus/native dep — so a consumer (the hosted `/validate`
* route) can run the SAME semantic gate a real arm runs WITHOUT standing up a session.
* `PlanEngine.#unknownSeriesRejection` now DELEGATES here, so there is ONE implementation and the
* light-path verdict is byte-identical to the arm verdict (never a third drifting copy).
*
* Pure and deterministic: it reads only the phonebook (never a live value), so a currently-DARK
* registered series is never flagged (fail-closed on a value is the runtime's concern, not a NAME's);
* document-order traversal; no clock, no RNG, no IO.
*/
import { type Module, type PlanStatement, type SeriesRef, type Trigger } from "../lang/index.ts";
import type { SeriesRegistry } from "../series/registry.ts";
import type { ArmRefusal } from "../protocol/index.ts";
/** Every bare {@link SeriesRef} operand a trigger references, collected recursively — the arm-time
* input to the unknown-series integrity check (kestrel-mte). Mirrors `findMarkInTrigger`'s structural
* walk: only `cmp`/`cross`/`event` carry operands; the compound/decorator nodes recurse into their
* sub-triggers; `phase`/`time-window`/`fill` reference no named series. */
export declare function collectSeriesOperands(t: Trigger, out: SeriesRef[]): void;
/**
* Arm-time unknown-series integrity (kestrel-mte / OSS-ADR-0045) as a PURE preview (no mutation, no
* throw): the coded refusal a case-variant market-series trigger raises at arm — its DISPLAY `message`
* and a concrete did-you-mean `repair` (the canonical lowercase series name) — or `null` when every
* trigger operand resolves.
*
* The failure it catches: a trigger operand names a CASE-VARIANT of a registered market fact
* (`VELOCITY` for the windowed `velocity`, `VWAP` for the scalar `vwap`). Because
* {@link SeriesRegistry.lookup} is now EXACT-MATCH (OSS-ADR-0045; the 0.4.5 case-insensitive
* forgiveness is reverted), that name has NO phonebook record, so the org/market split routes it to the
* ORG side where it reads UNKNOWN forever: the plan would arm but never fire. The platform market
* vocabulary is FROZEN and known at arm time (the shared phonebook), so this is STATICALLY knowable and
* refused AS DATA.
*
* Scoped narrowly to a case-variant near-miss of a KNOWN market name (zero false positives — a genuine
* org fact like `pnl` never case-matches a market name); a truly unknown bare name with no market
* near-match stays an org path (org facts register implicitly / delegate to the book).
*/
export declare function unknownSeriesRejection(st: PlanStatement, registry: SeriesRegistry): {
message: string;
repair: string;
} | null;
/**
* The whole-document unknown-series gate: the coded {@link ArmRefusal}s for every plan statement whose
* trigger names a case-variant of a known market fact, in document order. Byte-identical to the
* `unknown-series` refusals `PlanEngine.armDocument` returns on its {@link import("../protocol/index.ts").ArmReport}
* (both call {@link unknownSeriesRejection}). Non-plan statements carry no arm trigger and are skipped.
*/
export declare function collectUnknownSeriesRefusals(mod: Module, registry: SeriesRegistry): ArmRefusal[];
//# sourceMappingURL=unknown-series.d.ts.map