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.
157 lines (156 loc) • 7.64 kB
JavaScript
/**
* # vocab — the single source for every closed lexical vocabulary
*
* Each runtime table below is DERIVED from a closed string-union in {@link ./ast.ts}, so the
* vocabulary the parser recognizes and the printer emits can never silently drift from the
* type that defines the language. Every table is a `Record<Union, …>`: removing a union
* member leaves an excess key here (a compile error at the table), and adding a member leaves
* a missing key here (also a compile error at the table). The human-facing ordered lists
* (`ANCHOR_NAMES`, `TIME_UNITS_LIST`) come from `Object.keys` of those Records — insertion
* order is stable for non-integer keys — so "expected one of …" error strings are
* single-sourced too, never a hand-retyped third copy.
*
* `parse.ts` and `print.ts` consume this module; neither keeps a private copy (ADR-0004: the
* two projections must agree, and the surest way is to read the same table).
*/
/** The keys of a closed-vocabulary table, in insertion order, typed back to the union. */
function keysOf(table) {
return Object.keys(table);
}
// ── Time units (TimeUnit) ──────────────────────────────────────────────────────
const TIME_UNIT_TABLE = {
s: true,
m: true,
h: true,
d: true,
w: true,
mo: true,
q: true,
y: true,
};
/** Ordered for the "a time unit (…)" / "expected one of …" parse messages. */
export const TIME_UNITS_LIST = keysOf(TIME_UNIT_TABLE);
export const TIME_UNITS = new Set(TIME_UNITS_LIST);
// ── Ordinal-literal prefixes (SessionOrdinal / ExpiryOrdinal — Train 1B, ADR-0041 §1) ───────────
//
// The two single-letter prefix words that open a tight ordinal literal in a pane-argument position:
// `d-<n>` (a SessionOrdinal — `d-0` current session, `d-1` prior…) and `e-<n>` (an ExpiryOrdinal —
// `e-0` nearest expiry…). Date-blind RELATIVE addresses (never a date — same class as wake ordinals),
// each with ONE canonical printed form so `print(parse(text))` stays byte-stable (ADR-0004). The
// parser assigns the SUPERSORT from the prefix with zero catalog knowledge (the `wf` rung); the
// catalog `ParamSlot` refines it (SessionOrdinal / ExpiryOrdinal) at the `mat` rung.
export const ORDINAL_PREFIX = "d";
export const EXPIRY_PREFIX = "e";
// ── Price anchors (AnchorName) ─────────────────────────────────────────────────
const ANCHOR_TABLE = {
fair: true,
intrinsic: true,
basis: true,
bid: true,
ask: true,
mid: true,
last: true,
join: true,
improve: true,
stub: true,
// Emergent anchor (ADR-0030 / kestrel-ipc): admitting `spot` here auto-updates the parser's
// accepted set (ANCHORS) AND the "expected a price: an anchor (…)" message (ANCHOR_NAMES) —
// a single-sourced, strict-superset relaxation.
spot: true,
};
/** Ordered for the "an anchor (fair, intrinsic, …)" parse message. */
export const ANCHOR_NAMES = keysOf(ANCHOR_TABLE);
export const ANCHORS = new Set(ANCHOR_NAMES);
// ── Exit time-stop keywords (TimeHeldStop / ClockStop) — the 0DTE time-exit surface ──
/** The lead keyword for each 0DTE time-exit stop node, single-sourced so the parser that reads
* it (`parse.ts`) and the printer that emits it (`print.ts`) — and the WATCHER grammar overlay
* that references `clockET` (`scripts/gen-grammar.ts`) — all draw the SAME spelling from one
* table, never a hand-retyped copy (ADR-0004; the file's single-source thesis). Derived from the
* {@link TimeHeldStop}/{@link ClockStop} `kind` union so adding/removing a stop is a compile error
* here. `held` is the same spelling the `break-hold` postfix uses; `clockET` is the new token
* this axis adds (it mirrors the watcher's `atClockET` wake vocabulary). */
const TIME_STOP_TABLE = {
"held-stop": "held",
"clock-stop": "clockET",
};
export const TIME_STOP_KEYWORDS = TIME_STOP_TABLE;
/** `held` — the lead keyword of a `TimeHeldStop` (`EXIT held 90m`). */
export const HELD_STOP_KEYWORD = TIME_STOP_TABLE["held-stop"];
/** `clockET` — the lead keyword of a `ClockStop` (`EXIT clockET 15:40`). The NEW vocab token. */
export const CLOCK_STOP_KEYWORD = TIME_STOP_TABLE["clock-stop"];
// ── Fill-lifecycle events (FillEvent["event"]) ─────────────────────────────────
const FILL_EVENT_TABLE = {
filled: true,
"partial-fill": true,
unfilled: true,
rejected: true,
cancelled: true,
};
export const FILL_EVENTS = new Set(keysOf(FILL_EVENT_TABLE));
// ── Standing lifecycle (Standing) ──────────────────────────────────────────────
const STANDING_TABLE = {
authored: true,
armed: true,
versioned: true,
superseded: true,
};
export const STANDINGS = new Set(keysOf(STANDING_TABLE));
// ── Grade subjects (GradeSubject["what"]) ──────────────────────────────────────
const GRADE_WHAT_TABLE = {
plan: true,
wake: true,
view: true,
pod: true,
tag: true,
};
export const GRADE_WHATS = new Set(keysOf(GRADE_WHAT_TABLE));
// ── Citation content-hash algorithms (Citation["algo"]) — the `because` clause ─
/** The closed set of content-hash algorithms the `because` citation accepts (kestrel-rtf).
* Derived from the {@link Citation} union so the lexed `sha256:<64 hex>` token shape and the
* type can never drift; reuses the repo's ubiquitous sha256 digest shape (no second scheme). */
const CITATION_ALGO_TABLE = {
sha256: true,
};
export const CITATION_ALGOS = new Set(keysOf(CITATION_ALGO_TABLE));
// ── Provenance tiers + authority rank (ProvenanceTier) ─────────────────────────
/** Authority ordering for the provenance ceiling: a higher rank is more authority. The
* Record over `ProvenanceTier` doubles as the closed-vocabulary table — membership is its
* key set, so the rank map and the tier vocabulary can never diverge. */
export const PROV_RANK = {
unvetted: 0,
candidate: 1,
vetted: 2,
};
export const PROV_TIERS = new Set(keysOf(PROV_RANK));
// ── Comparison operators (CompareOp) — ONE bidirectional map ───────────────────
/** The canonical direction the printer emits (op → punctuation). `Record<CompareOp, …>`
* keeps it exhaustive over the union. */
export const CMP_TO_PUNCT = {
gt: ">",
ge: ">=",
lt: "<",
le: "<=",
eq: "==",
ne: "!=",
};
/** The parser's inverse (punctuation → op), derived mechanically from `CMP_TO_PUNCT` so the
* two directions are one table, not two hand-kept copies that could disagree. */
export const CMP_FROM_PUNCT = Object.fromEntries(Object.entries(CMP_TO_PUNCT).map(([op, punct]) => [punct, op]));
// ── Ordinals — ONE list (parser matches words, printer emits them) ─────────────
/** 1-based ordinals; index 0 (`zeroth`) is a slot filler so `ORDINALS[n]` lines up with the
* cardinal `n` (the printer refuses n < 1, the parser matches from index 1). */
export const ORDINALS = [
"zeroth",
"first",
"second",
"third",
"fourth",
"fifth",
"sixth",
"seventh",
"eighth",
"ninth",
"tenth",
"eleventh",
"twelfth",
];