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.
736 lines • 32.4 kB
TypeScript
/**
* # ast — the typed object model IS the language (ADR-0004)
*
* Kestrel's canonical representation is this discriminated-union family, not the text.
* `parse(text) -> objects` (phase 3) and `print(objects) -> text` (this phase, in
* `print.ts`) are inverse projections with a byte-stable round-trip. Every node carries a
* literal `kind` tag; there are no classes (data is plain readonly objects) and no `any`.
*
* One language, four statement kinds (ADR-0001): {@link ViewStatement},
* {@link WakeStatement}, {@link PlanStatement}, {@link GradeStatement} — plus the org
* nodes {@link PodStatement} / {@link BookStatement} (ADR-0002). All four surfaces share
* one lexical core: the {@link Trigger} algebra, {@link SeriesRef} operands, and
* {@link PriceExpr} price expressions.
*
* The AST is ALWAYS fully qualified — `USING` defaults are resolved at parse time, and
* the printer elides whatever matches the ambient `USING` (ADR-0004; ARCHITECTURE §2).
*/
/** One printed line's comment trivia. `at` is the line's ordinal WITHIN its owning block, in
* canonical print order: `0` = the block's own header line; `1..N` = the block's direct
* interior lines (USING/WHEN/BECAUSE/clauses/panes/risk/directives). Child blocks own their
* own trivia, so they never consume an ordinal here. */
export interface LineComment {
readonly at: number;
/** Own-line comments printed on their own line(s) ABOVE line `at`, verbatim, `#` excluded. */
readonly leading?: readonly string[];
/** An inline comment printed after line `at`'s content, one canonical space before `#`. */
readonly trailing?: string;
}
/** The comment sidecar for a block node (a statement, or the module). Absent entirely on a
* comment-free document, so it never perturbs deep-equality against builder output. */
export interface CommentLayer {
/** Comments bound to specific printed lines of this block, by ordinal. */
readonly lines?: readonly LineComment[];
/** Own-line comments trailing the block's last line, at the block's interior indentation. */
readonly tail?: readonly string[];
}
/** Timescale unit for windows and durations. There is no absolute shock: magnitude is
* judged at a window (CONTEXT: Window). */
export type TimeUnit = "s" | "m" | "h" | "d" | "w" | "mo" | "q" | "y";
/** The timescale parameter a rate/magnitude series carries — `velocity(1m)`. */
export interface Window {
readonly kind: "window";
readonly value: number;
readonly unit: TimeUnit;
}
/** A span of time used by `held`, `within`, `esc`, and relative `ttl`. */
export interface Duration {
readonly kind: "duration";
readonly value: number;
readonly unit: TimeUnit;
}
/** A wall-clock time of day (session calendar), `HH:MM`. */
export interface TimeOfDay {
readonly kind: "time-of-day";
readonly hour: number;
readonly minute: number;
}
/** A numeric quantity with an optional unit. Plain number, risk-fraction `R`, cents `c`,
* percent `%`, or basis points `bp`. Used as a trigger operand and in offsets. */
export interface Quantity {
readonly kind: "quantity";
readonly value: number;
readonly unit?: "R" | "c" | "%" | "bp";
}
/** A baseline-relative threshold — the portable, cross-instrument form of "big"
* (CONTEXT: Window). `p99` = 99th percentile of the window's own trailing baseline;
* `3sigma` = three standard deviations. */
export interface Baseline {
readonly kind: "baseline";
readonly stat: "p" | "sigma";
readonly n: number;
}
/** A selector on a path segment: an org quantifier (`children(any)`) or a named target
* (`plan(chase-urgent)`). */
export type PathSelector = {
readonly kind: "sel-quant";
readonly q: "any" | "all";
} | {
readonly kind: "sel-name";
readonly name: string;
};
/** One dotted segment of a series path, optionally selected. */
export interface PathSegment {
readonly name: string;
readonly selector?: PathSelector;
}
/**
* A named series whose value changes over time — the universal operand.
*
* Two kinds and only two (CONTEXT: Series): **market facts** (ambient per signal
* instrument, spectator-visible: `spot`, `vwap`, `velocity(1m)`) and **org facts**
* (path-scoped in the pod tree, acting-sessions only: `pnl`, `alpha.pnl`,
* `fills.avg_px`, `plan(chase-urgent).state`). The market/org distinction is **registry
* resolution metadata, not a syntactic property** — canonical text does not mark it (a
* dotted path prints identically for both), so it is NOT a field on this node; the
* registry (a later phase) resolves a path to its space. A bare symbolic value like a
* plan-state literal (`armed`) is a single-segment series — indistinguishable in text
* from any other name, and resolved by the registry (ADR-0004: the node is the syntactic
* identity; round-trip is mechanical, so nothing un-printable lives here).
*/
export interface SeriesRef {
readonly kind: "series";
readonly segments: readonly PathSegment[];
readonly window?: Window;
}
/** Any leaf value that a comparison or cross can stand on. A bare identifier operand (a
* plan-state literal like `armed`) is a single-segment {@link SeriesRef} — text cannot
* distinguish it from a series, and ADR-0004 forbids un-round-trippable distinctions. */
export type Operand = SeriesRef | Quantity | Baseline;
export type CompareOp = "gt" | "ge" | "lt" | "le" | "eq" | "ne";
/** `left OP right` — a level/series comparison. */
export interface Comparison {
readonly kind: "cmp";
readonly op: CompareOp;
readonly left: Operand;
readonly right: Operand;
}
/**
* `left crosses above|below right` — an edge event, not a level.
*
* Two hardening dials, both learned from a graded-sim failure where a bare cross phantom-
* fired 10/12 on a single anomalous print:
*
* - **`touch`** picks the boundary predicate. Omitted (the default) is a **strict** cross:
* the series must move strictly past `right` (`>`/`<`). Present (`touches above|below`) is
* an **at-or-touch** cross: reaching *or* touching the level fires (`>=`/`<=`).
* - **`band`** is a **re-arm** width. With a band, after the cross fires the series must
* clear back past `right ∓ band` (below `right−band` for an `above` cross, above
* `right+band` for `below`) and **re-arm** before it can fire again — so a lone spurious
* tick cannot phantom-fire. Omitted = a bare, single-shot cross (the fragile form). A
* positive magnitude; a zero/negative band cannot re-arm and is refused at parse time.
*
* Doctrine (fail-closed, all surfaces): a cross is an edge, not a level, so it may **not**
* be wrapped in `held` — sustaining an edge for a duration is the anti-pattern that phantom-
* fired; the parser rejects it, the builder refuses to construct it, the printer throws on
* it. Robustness comes from `band` (re-arm), not from holding the edge. (`within` over a
* cross stays legal: "the cross occurred inside this window" is a well-formed question.)
*/
export interface CrossEvent {
readonly kind: "cross";
readonly left: Operand;
readonly dir: "above" | "below";
readonly right: Operand;
readonly touch?: boolean;
readonly band?: Quantity;
}
/** Break-and-hold: `inner held <dur>` — the inner condition sustained for a duration. */
export interface BreakHold {
readonly kind: "break-hold";
readonly inner: Trigger;
readonly dur: Duration;
}
/** `inner within <dur>` — the inner event occurred inside a rolling window. */
export interface WithinWindow {
readonly kind: "within";
readonly inner: Trigger;
readonly dur: Duration;
}
/** `inner until <clock>` — the inner expectation is in force up to a wall-clock time; the
* thesis temporal envelope (kestrel-rtf). A postfix combinator over the SAME predicate
* surface as {@link WithinWindow} — `until`/`at` are siblings of `within`, not a second
* predicate language — each carrying a {@link TimeOfDay}. */
export interface UntilWindow {
readonly kind: "until";
readonly inner: Trigger;
readonly at: TimeOfDay;
}
/** `inner at <clock>` — the inner expectation is evaluated at a wall-clock time; the thesis
* temporal envelope (kestrel-rtf). A postfix combinator sibling of {@link WithinWindow}. */
export interface AtWindow {
readonly kind: "at";
readonly inner: Trigger;
readonly at: TimeOfDay;
}
/**
* `held <dur>` in EXIT/lead position — a 0DTE **time-held stop**: the position has been HELD
* for a duration (measured from the plan's first acquiring fill), independent of any market
* predicate. This is the in-grammar form for "exit N minutes after entry" the FOMC-options
* single-model runs reached for out-of-grammar (`EXIT held 90m`, `EXIT held 120m`) and
* parse-escaped on, de-arming the whole plan (docs/results/fomc-options-axis/report.md).
*
* It is NOT a {@link BreakHold}: break-hold sustains an INNER predicate for a duration (any
* flicker resets the clock); a `held-stop` has no inner predicate — it is a bare hold-clock on
* the acquired inventory. As a general {@link Trigger} it only has a referent inside a clause
* that manages a held position (EXIT); read in any other position it fails closed (UNKNOWN),
* exactly like {@link UntilWindow}/{@link AtWindow}. The engine fires it at the leg's own basis,
* respecting never-naked / covered-close, through the SAME {@link ExitClause} path.
*/
export interface TimeHeldStop {
readonly kind: "held-stop";
readonly dur: Duration;
}
/**
* `clockET <clock>` — a 0DTE **wall-clock time-stop**: fire at or after a wall-clock ET time
* (the theta-into-the-close cut the FOMC-options runs authored out-of-grammar as
* `EXIT clockET 15:40`). Semantically a sibling of `time after <clock>`, but named `clockET`
* because that is the surface the single-model authors emitted (mirroring the watcher's
* `scheduleWake at {kind:"atClockET"}` vocabulary). Carries a {@link TimeOfDay}.
*/
export interface ClockStop {
readonly kind: "clock-stop";
readonly at: TimeOfDay;
}
/** The Nth occurrence of an event: `second failed-break of HOD`. */
export interface NthEvent {
readonly kind: "nth";
readonly ordinal: number;
readonly event: Trigger;
}
/** A named structural market event, optionally of a level: `failed-break of HOD`. */
export interface StructuralEvent {
readonly kind: "event";
readonly name: string;
readonly of?: Operand;
}
/** A session-calendar phase event: `phase open`. */
export interface PhaseEvent {
readonly kind: "phase";
readonly phase: string;
}
/** A wall-clock window: `time 09:30..10:00`, `time after 15:45`, `time before 10:00`.
* At least one of `from` / `to` must be present. */
export interface TimeWindowTrig {
readonly kind: "time-window";
readonly from?: TimeOfDay;
readonly to?: TimeOfDay;
}
/** A fill-lifecycle event (fill telemetry as a trigger). */
export interface FillEvent {
readonly kind: "fill";
readonly event: "filled" | "partial-fill" | "unfilled" | "rejected" | "cancelled";
readonly leg?: number;
}
export interface And {
readonly kind: "and";
readonly terms: readonly Trigger[];
}
export interface Or {
readonly kind: "or";
readonly terms: readonly Trigger[];
}
export interface Not {
readonly kind: "not";
readonly term: Trigger;
}
/** The shared WHEN expression, used by Wake, Plan, and Grade filters alike. */
export type Trigger = Comparison | CrossEvent | BreakHold | WithinWindow | UntilWindow | AtWindow | TimeHeldStop | ClockStop | NthEvent | StructuralEvent | PhaseEvent | TimeWindowTrig | FillEvent | And | Or | Not;
/** A price anchor. `fair` = ExecutionFair (the honest MM fill price); `mid` is an
* authoring resting-price, never a fair-value source (ARCHITECTURE §4). */
export type AnchorName = "fair" | "intrinsic" | "basis" | "bid" | "ask" | "mid" | "last" | "join" | "improve" | "stub" | "spot";
export interface Anchor {
readonly kind: "anchor";
readonly name: AnchorName;
}
/** An absolute limit price, e.g. `4.20`. */
export interface AbsolutePrice {
readonly kind: "price-abs";
readonly value: number;
}
/** `base ± N(c|%)` — a tight offset from a base price, e.g. `fair-3c`. */
export interface PriceOffset {
readonly kind: "price-offset";
readonly base: PriceExpr;
readonly sign: "+" | "-";
readonly amount: number;
readonly unit: "c" | "%";
}
/** `lean(a, b, x)` — blend between two prices by fraction x (lean-to-MM). */
export interface Lean {
readonly kind: "lean";
readonly a: PriceExpr;
readonly b: PriceExpr;
readonly x: number;
}
/** `min(...)` / `max(...)` over price expressions. */
export interface PriceMinMax {
readonly kind: "price-fn";
readonly fn: "min" | "max";
readonly args: readonly PriceExpr[];
}
export type PriceExpr = Anchor | AbsolutePrice | PriceOffset | Lean | PriceMinMax;
/** One rung of an escalation ladder: escalate the resting price `to` after `after`. */
export interface EscStage {
readonly kind: "esc-stage";
readonly to: PriceExpr;
readonly after: Duration;
}
/** The resting-order execution policy: peg/fix, an esc ladder, cap/floor lists, a
* per-order cancel-if guard, and gtc. Distinct from *which* price (that is a
* {@link PriceExpr}). */
export interface OrderPolicy {
readonly kind: "order-policy";
readonly pricing?: "peg" | "fix";
readonly esc?: readonly EscStage[];
readonly caps?: readonly PriceExpr[];
readonly floors?: readonly PriceExpr[];
readonly cancelIf?: Trigger;
readonly gtc?: boolean;
}
/** How a strike is chosen: relative to the anchor grid (`+1`), an absolute strike
* (`450`), at-the-money (`atm`), or delta-targeted (`25d`). */
export type StrikeSpec = {
readonly kind: "strike-rel";
readonly steps: number;
} | {
readonly kind: "strike-abs";
readonly strike: number;
} | {
readonly kind: "strike-atm";
} | {
readonly kind: "strike-delta";
readonly delta: number;
};
/** One option leg: `buy 2 +1 C`.
*
* A SELL leg is legal ONLY as a **covered** close of inventory this plan actually holds — never-naked
* applies to OPTION legs exactly as it does to an {@link EquityLeg} (ADR-0017). Coverage is enforced
* per `(strike, right)` (`PlanEngine#sellCovered`: net-held-of-that-leg minus its resting sells) on each
* surface that can emit a sell: entry, ALSO, RELOAD, flatten, **TAKE-PROFIT**, and **EXIT**.
*
* The last two are called out because they were the hole (kestrel-h5nx). Both sized off the PLAN-TOTAL
* net-held while binding the sell to a SINGLE leg, so any plan holding more than one leg (a straddle, a
* two-strike ladder) oversold that leg and went NET SHORT an uncovered option. On a one-leg plan the two
* quantities coincide, which is why it survived for so long — and why a contaminated result had already
* been pinned into the committed golden fixtures. Multi-leg sells now ALLOCATE across the held legs, each
* capped by and named for the leg that covers it. */
export interface OptionLeg {
readonly kind: "leg";
readonly side: "buy" | "sell";
readonly qty: number;
readonly strike: StrikeSpec;
readonly right: "C" | "P";
/**
* The leg's own tenor (kestrel theta-cell seam a; AUTHORABLE since kestrel-ih5h seam 1).
*
* `undefined` means **inherit the ambient execution tenor** — the expiry authored once on the
* execution instrument (`USING exec … 0dte`, {@link ExpirySelector} on {@link Instrument}). It is
* never a silent 0dte: the inheritance is resolved at execution (`#execForLeg` in
* `src/engine/plans.ts`), and an expiry that cannot be resolved fails closed rather than defaulting.
*
* Authored per-leg with the `exp` marker — `buy 2 +1 C exp 0dte`, `sell 1 atm P exp 2026-07-17` —
* reusing the SAME selector vocabulary as the instrument form. The marker is required because a leg
* is followed by `,` or `@` and a bare tag selector would swallow the continuation; the instrument
* form can be positional only because a stop-set holds it apart. Legs written without `exp` print
* byte-identically to before this syntax existed (ADR-0004; the tail is additive).
*
* It is also what lets a leg RECONSTRUCTED from a held/marked position (not authored — e.g. the
* per-wake mark-to-model folding a held straddle back out of the tape's option book, whose
* `BookState` carries one expiry) carry its own tenor with it.
*
* Note the execution BOOK is still keyed by SYMBOL alone (`#books` in `src/engine/plans.ts`,
* `foldBook` in `src/bus/types.ts` keeps ONE expiry per instrument). A leg expiry therefore reaches
* execution as a CHECK, not a selector: `#bookFor` refuses a book whose expiry contradicts the
* authored one (fail-closed) rather than pricing against the wrong tenor. Re-keying the book by
* `(symbol, expiry)` — what a genuine multi-expiry / calendar position needs — is the other half of
* this seam and is deliberately NOT made here.
*/
readonly expiry?: ExpirySelector;
}
/** One equity/spot leg: `buy 100 shares` (ADR-0017). A spot instrument has neither a
* strike nor a right, so an equity leg carries only side + quantity + the `shares` marker;
* the tradeable symbol is the ambient `USING exec <SYMBOL>` with no expiry. `qty` is a share
* count. Deliberately long-biased in v1: an uncovered equity SELL is a naked short (unbounded
* risk) and is refused downstream (never-naked, ADR-0017). The SAME boundary governs an
* {@link OptionLeg} sell — never-naked is not an equity-only rule (kestrel-h5nx). */
export interface EquityLeg {
readonly kind: "equity-leg";
readonly side: "buy" | "sell";
readonly qty: number;
}
/** An order leg — an {@link OptionLeg} (`buy 2 +1 C`) or an {@link EquityLeg} (`buy 100
* shares`, ADR-0017). A discriminated union on `kind`; every consumer narrows, so the
* compiler enumerates the sites the instrument-general execution core must handle. */
export type Leg = OptionLeg | EquityLeg;
/** A held-inventory quantifier for clauses that act on pre-existing positions
* (ADR-0005 inventory binding). */
export type HeldQuantifier = {
readonly kind: "held-foreach";
} | {
readonly kind: "held-any";
};
/** `DO <legs> @ <price> <policy>` — the primary order ticket. `atomic` is reserved and
* refused symmetrically on every surface (ADR-0005): the parser REJECTS the keyword, the
* builders REFUSE to construct it, and the printer THROWS on it. The AST keeps the field
* anyway — the parser needs the vocabulary to reject it loudly, and the type documents the
* future surface (a whole-structure preflight + an atomic execution adapter) — but no valid
* v1 ticket ever carries it, so `parse(print(x))` round-trips (ADR-0004). Never faked. */
export interface DoTicket {
readonly kind: "do";
readonly legs: readonly Leg[];
readonly price: PriceExpr;
readonly policy?: OrderPolicy;
readonly atomic?: boolean;
}
/** `ALSO <legs> @ <price> <policy>` — an additional co-armed ticket. */
export interface AlsoTicket {
readonly kind: "also";
readonly legs: readonly Leg[];
readonly price: PriceExpr;
readonly policy?: OrderPolicy;
readonly atomic?: boolean;
}
/** `RELOAD [WHEN <trig>] <legs> @ <price> <policy>` — buy more on adverse movement. */
export interface ReloadClause {
readonly kind: "reload";
readonly when?: Trigger;
readonly legs: readonly Leg[];
readonly price: PriceExpr;
readonly policy?: OrderPolicy;
readonly atomic?: boolean;
}
/** What target a take-profit rests against. */
export type TpTarget = {
readonly kind: "tp-pct";
readonly pct: number;
} | {
readonly kind: "tp-mult";
readonly mult: number;
} | {
readonly kind: "tp-price";
readonly price: PriceExpr;
};
/** `TP <target> [frac F] [<held-quant>] [@ <price>] <policy>` — resting take-profit. */
export interface TpClause {
readonly kind: "tp";
readonly target: TpTarget;
readonly frac?: number;
readonly over?: HeldQuantifier;
readonly price?: PriceExpr;
readonly policy?: OrderPolicy;
}
/** `EXIT <trig> [<held-quant>] [@ <price>] <policy>` — get out when the thesis breaks. */
export interface ExitClause {
readonly kind: "exit";
readonly when: Trigger;
readonly over?: HeldQuantifier;
readonly price?: PriceExpr;
readonly policy?: OrderPolicy;
}
/** `INVALIDATE <trig>` — thesis dead: stop active management, ride the tail. */
export interface InvalidateClause {
readonly kind: "invalidate";
readonly when: Trigger;
}
/** `CANCEL-IF <trig>` — cancel resting orders (plan-scope, vs the per-order guard). */
export interface CancelIfClause {
readonly kind: "cancel-if";
readonly when: Trigger;
}
/** `ARM [WHEN <trig>] [basis <price>] [<held-quant>]` — the on-arm trigger + inventory
* binding (ADR-0005): the basis price anchor and a held-leg quantifier. */
export interface ArmClause {
readonly kind: "arm";
readonly when?: Trigger;
readonly basis?: PriceExpr;
readonly over?: HeldQuantifier;
}
export type PlanClause = DoTicket | AlsoTicket | ReloadClause | TpClause | ExitClause | InvalidateClause | CancelIfClause | ArmClause;
/** An expiry selector for an execution instrument. */
export type ExpirySelector = {
readonly kind: "expiry-dte";
readonly dte: number;
} | {
readonly kind: "expiry-date";
readonly date: string;
} | {
readonly kind: "expiry-tag";
readonly tag: string;
};
/** A tradeable instrument, optionally with an expiry (execution side). */
export interface Instrument {
readonly kind: "instrument";
readonly symbol: string;
readonly expiry?: ExpirySelector;
}
/** Scoped defaults: a signal-space instrument (where WHEN reads) and an execution-space
* instrument + expiry (where DO acts). The only v1 customization point for imports
* (ADR-0003). */
export interface Using {
readonly kind: "using";
readonly signal?: Instrument;
readonly exec?: Instrument;
}
export type ProvenanceTier = "vetted" | "candidate" | "unvetted";
/** Authorship + trust metadata. A declared provenance can only *narrow* the channel's
* authority (ARCHITECTURE §6 provenance ceiling). */
export interface Provenance {
readonly kind: "provenance";
readonly tier?: ProvenanceTier;
readonly author?: string;
readonly origin?: string;
readonly replay?: string;
}
/** A pane argument, carrying only its **syntactic supersort** — the class the lexer/parser assigns
* with ZERO catalog knowledge (ADR-0041 §1: the `wf` rung is catalog-independent, so the parser
* never picks a pane's semantic sort). Five supersorts: a bare **ident** (`skyline`, `vwap`), a
* **window** (`5m` — a numeral + a time unit), a bare **numeral count** (`12` — a numeral with
* NO unit, e.g. `chain 12`), a **session ordinal** (`d-0`, `d-1` — the `d-` prefix + a numeral,
* Train 1B), and an **expiry ordinal** (`e-0`, `e-1` — the `e-` prefix + a numeral, Train 1B). A
* catalog `ParamSlot` REFINES a supersort to a semantic sort
* (`Instrument`/`LevelName`/`Window`/`Count`/`SessionOrdinal`/`ExpiryOrdinal`) at materialization
* (`src/frame/pane-catalog.ts`). Each supersort has ONE canonical printed form so `print(parse(text))`
* stays byte-stable (ADR-0004): an ident prints its name, a window `<n><unit>`, a count its bare
* numeral, a session ordinal `d-<n>`, an expiry ordinal `e-<n>`. Panes are references + args only —
* a View never computes (no application node, ADR-0041 §1). */
export type PaneArg = {
readonly kind: "arg-ident";
readonly name: string;
} | {
readonly kind: "arg-window";
readonly window: Window;
} | {
readonly kind: "arg-count";
readonly count: number;
} | {
readonly kind: "arg-ordinal";
readonly ordinal: number;
} | {
readonly kind: "arg-expiry";
readonly expiry: number;
};
/** One named block of a View. */
export interface PaneRef {
readonly kind: "pane";
readonly name: string;
readonly args: readonly PaneArg[];
}
export interface ViewStatement {
readonly kind: "view";
readonly name: string;
readonly budget?: number;
readonly panes: readonly PaneRef[];
/** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
readonly comments?: CommentLayer;
}
/**
* A content-hash reference to a platform-side Thesis artifact — the `because` clause
* (kestrel-rtf). It carries ONLY the digest of the fixed shape `sha256:<64 hex>`; the thesis
* body lives platform-side, never inline (a `because` with an inline body is a parse refusal).
*
* Because the clause prints byte-stably like any other canonical text, it binds into the
* armed-document hash (`sha256(print(module))`) automatically — so pre-registration is
* tamper-evident once the plan is armed. It is an OPTIONAL field on {@link PlanStatement} and
* {@link WakeStatement}, NOT a fifth statement kind: the statement algebra stays View/Wake/
* Plan/Grade (ADR-0001), and `because` attached to a View or Grade is a fail-closed refusal.
*/
export interface Citation {
readonly kind: "citation";
readonly algo: "sha256";
/** The 64-lowercase-hex content hash of the platform-side Thesis (no inline body). */
readonly hash: string;
}
/** `DELIVER <view> [MANDATORY] [KEYFRAME]` — the wake decides *when*, the View *what*.
* Delta frame by default; keyframe on request. */
export interface DeliverSpec {
readonly kind: "deliver";
readonly view: string;
readonly mandatory?: boolean;
readonly keyframe?: boolean;
}
/** A wake's attention budget: wakes/day and/or tokens/day. */
export interface WakeBudget {
readonly kind: "wake-budget";
readonly wakesPerDay?: number;
readonly tokensPerDay?: number;
}
/** A Scan's universe scope (`all-listed`, `nyse`) — wide and slow, vs a narrow coverage
* wake. */
export interface UniverseScope {
readonly kind: "universe";
readonly universe: string;
}
export interface WakeStatement {
readonly kind: "wake";
readonly name: string;
readonly when: Trigger;
/** Optional `because sha256:<64 hex>` pre-registration citation (kestrel-rtf). */
readonly because?: Citation;
readonly deliver?: DeliverSpec;
readonly priority?: number;
readonly coalesce?: string;
readonly budget?: WakeBudget;
readonly universe?: UniverseScope;
/** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
readonly comments?: CommentLayer;
}
/** Budget as a risk fraction, `0.2R`. `size × max_loss ≤ budget` (ARCHITECTURE §6). */
export interface Budget {
readonly kind: "budget";
readonly value: number;
readonly unit: "R";
}
/** Time-to-live: relative (`+30m`) or an absolute clock (`16:00`, hold-to-close). */
export type Ttl = {
readonly kind: "ttl-rel";
readonly dur: Duration;
} | {
readonly kind: "ttl-at";
readonly at: TimeOfDay;
};
/** One tag binding of a regime gate: `intraday: trend`. */
export interface RegimeTagBinding {
readonly scope: string;
readonly value: string;
}
/** `regime {intraday: trend}` — an open-vocabulary tag gate (ADR-0005). */
export interface RegimeGate {
readonly kind: "regime-gate";
readonly tags: readonly RegimeTagBinding[];
}
/** The lifecycle standing of a standing statement (CONTEXT: Standing). */
export type Standing = "authored" | "armed" | "versioned" | "superseded";
export interface PlanStatement {
readonly kind: "plan";
readonly name: string;
readonly budget?: Budget;
readonly ttl?: Ttl;
readonly regime?: RegimeGate;
readonly priority?: number;
readonly standing?: Standing;
readonly provenance?: Provenance;
readonly using?: Using;
readonly when?: Trigger;
/** Optional `because sha256:<64 hex>` pre-registration citation (kestrel-rtf). */
readonly because?: Citation;
readonly clauses: readonly PlanClause[];
readonly atomic?: boolean;
/** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
readonly comments?: CommentLayer;
}
/** What is being graded — everything authored is gradable (ADR-0006). */
export interface GradeSubject {
readonly kind: "grade-subject";
readonly what: "plan" | "wake" | "view" | "pod" | "tag";
readonly name: string;
}
/** A counterfactual — syntax, not a bespoke script (ADR-0006). The structural null and
* bracket cross the SAME fill model. */
export type Counterfactual = {
readonly kind: "cf-ungated";
} | {
readonly kind: "cf-null";
} | {
readonly kind: "cf-bracket";
};
/** The corpus range a grade replays over: `2025-01..2026-06`. Opaque date tokens. */
export interface CorpusRange {
readonly kind: "corpus-range";
readonly from: string;
readonly to: string;
}
/** A `BY` stratification dimension — cells, not pools (ADR-0006). */
export type GradeDimension = {
readonly kind: "dim-series";
readonly series: SeriesRef;
} | {
readonly kind: "dim-vehicle";
} | {
readonly kind: "dim-name";
} | {
readonly kind: "dim-lineage";
};
export interface GradeStatement {
readonly kind: "grade";
readonly subject: GradeSubject;
readonly over?: CorpusRange;
readonly fill?: string;
readonly versus: readonly Counterfactual[];
readonly by: readonly GradeDimension[];
/** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
readonly comments?: CommentLayer;
}
/** Instruments assigned to a Trader PLUS the thesis for why (CONTEXT: Coverage). */
export interface Coverage {
readonly kind: "coverage";
readonly instruments: readonly Instrument[];
readonly thesis: string;
}
/** How a Book's concurrent plans compete for its envelope (open-vocabulary policy). */
export interface Arbitration {
readonly kind: "arbitration";
readonly policy: string;
readonly concurrency?: number;
}
/** One risk line: `RISK day-loss 2R -> halt`. The L0 envelope may clamp/veto, never open
* risk (ARCHITECTURE §3). */
export interface RiskLine {
readonly kind: "risk";
readonly metric: string;
readonly threshold: Quantity;
readonly action: string;
}
/** The leaf of the org tree — the only place positions and orders live (ADR-0002). */
export interface BookStatement {
readonly kind: "book";
readonly name: string;
readonly budget?: Budget;
readonly coverage?: Coverage;
readonly arbitration?: Arbitration;
readonly risk?: readonly RiskLine[];
/** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
readonly comments?: CommentLayer;
}
/** The recursive org node: an allocating PM role + a risk envelope + children (each a
* Book or another Pod). Depth is unbounded; the org is data (ADR-0002). */
export interface PodStatement {
readonly kind: "pod";
readonly name: string;
readonly risk: readonly RiskLine[];
readonly children: readonly (PodStatement | BookStatement)[];
/** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
readonly comments?: CommentLayer;
}
/** An ESM-like import of named statements from another module (ADR-0003). */
export interface ImportDecl {
readonly kind: "import";
readonly names: readonly string[];
readonly from: string;
}
/** Any top-level statement of a module. */
export type Statement = ViewStatement | WakeStatement | PlanStatement | GradeStatement | PodStatement | BookStatement;
/** A Kestrel document: imports + optional USING defaults + statements + provenance. */
export interface Module {
readonly kind: "module";
readonly imports: readonly ImportDecl[];
readonly using?: Using;
readonly statements: readonly Statement[];
readonly provenance?: Provenance;
/** Byte-stable comment trivia (ADR-0033) for the module's own directive lines (IMPORT /
* PROVENANCE / USING, ordinal 0..k-1) and its tail. Comments before/on a statement live on
* that statement, not here. Absent on comment-free documents. */
readonly comments?: CommentLayer;
}
/** Any node the printer accepts at the top level. */
export type KestrelNode = Module | Statement;
//# sourceMappingURL=ast.d.ts.map