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.

302 lines (301 loc) 22 kB
/** * # frame/paradigm-ledger — the FOUR-STATE paradigm cell ledger (ADR-0041 §3, kestrel-wa0j.30) * * ADR-0041 §3 makes every `(pane × sort-value-bundle)` cell exactly one of four states: * • **ATTESTED** — materializes byte-identically today (and is monotone forever — the ratchet). * • **LATENT** — not yet, but a NAMED train will land it (a LATENT refusal names its train). * • **DEFECTIVE** — never, for a WRITTEN reason, with a SANCTIONED PERIPHRASIS. * • **UNATTESTED** — outside the closed roster (the closed-world default; never enumerated). * * This module is the SINGLE SOURCE of that table. It ships in `src/` (the package's `files` * excludes `tests/`), so production refusal constructors ({@link ./refusals.ts}) can read it — a * LATENT refusal pulls its train from HERE, never a second hand-kept list. The committed sidecar * `tests/golden/pane-paradigm.json` is this data SERIALIZED (generated by * `scripts/gen-paradigm-ledger.ts`, staleness-checked in `tests/frame.paradigm-ledger.test.ts`) — * the repo's generate-golden-from-source pattern (cf. `gen-detmath-golden.ts`), so the JSON can * never drift from this module. * * ## Totality (ADR-0041 §3: "a total function over a known grid") * {@link resolveCellState} is a TOTAL function over `pane × {@link PARADIGM_ROSTER}`: every cell * resolves to exactly one state. The listed {@link PARADIGM_CELLS} carry the non-UNATTESTED states; * every unlisted cell is UNATTESTED by the closed-world rule. `tests/frame.paradigm-ledger.test.ts` * asserts totality (no cell unclassified) and binds the ATTESTED rows to the live catalog signatures. * * ## Forward-compatibility with scheme conditioning (wa0j.44 — NOT implemented here) * A cell's state may later be CONDITIONED on a registry attribute (instrumentClass / SessionScheme): * `prior-context` is DEFECTIVE under a SessionScheme with no close boundary but ATTESTED under one * with a close — ONE paradigm, two conditioned cells (ADR-0041 §3). The cell KEY carries an OPTIONAL * {@link CellConditioning}; every current cell OMITS it (= unconditioned, applies to all schemes). * wa0j.44 adds scheme-conditioned cells by ADDING a more-specific conditioned cell — it NEVER * re-keys an existing unconditioned one. Pinning the optional field into the key shape NOW (while the * ledger is small) is the empty-ledger lesson (wa0j.52 / instrumentClass): an axis added to an * established key is an evidence-splitting migration; an optional axis reserved up front is additive. */ import { SESSION_SCHEMES, isSessionScheme, schemeHasBoundary, } from "./session-scheme.js"; /** The four paradigm states (ADR-0041 §3). */ export const PARADIGM_STATES = ["ATTESTED", "LATENT", "DEFECTIVE", "UNATTESTED"]; /** * The CLOSED sort roster (ADR-0041 §1/§3) — the ground index sorts a pane argument may address. It * is closed at any instant and opened ONLY by the sort-introduction ceremony (five artifacts in one * commit). Train 1A's LIVE subset is {@link LIVE_SORTS}; the rest are pinned into the roster but * DEFERRED (their slots arrive with later trains). A value OUTSIDE this roster is UNATTESTED and * refused by name. */ export const PARADIGM_ROSTER = [ "Instrument", "LevelName", "Window", "Count", "SessionOrdinal", "ExpiryOrdinal", "Band", ]; /** The LIVE subset of {@link PARADIGM_ROSTER} the catalog materializes (has a catalog slot for). * Train 1A opened `LevelName` / `Window` / `Count`; Train 1B opened `SessionOrdinal` (`tape d-N`) and * `ExpiryOrdinal` (`chain e-N`) by the sort-introduction ceremony (ADR-0041 §3). */ export const LIVE_SORTS = ["LevelName", "Window", "Count", "SessionOrdinal", "ExpiryOrdinal"]; // ───────────────────────────────────────────────────────────────────────────── // The listed cells — every non-UNATTESTED (pane × sort) cell, SEEDED FROM REALITY // ───────────────────────────────────────────────────────────────────────────── /** * The ATTESTED cells (kestrel-wa0j.24) — each mirrors a LIVE catalog signature slot, cross-checked * against `PANE_CATALOG` by the ledger test (the biconditional that makes flipping one to LATENT a * red). These are the seed of the conformance ratchet (monotone forever, ADR-0041 §3). Train 1A * seeded the first three; Train 1B added `tape × SessionOrdinal` and `chain × ExpiryOrdinal`. * * ## LEDGER RECONCILIATION — why `tape × SessionOrdinal` / `chain × ExpiryOrdinal` are ATTESTED, not * LATENT-naming-wa0j.20 (Train 1B decision, ADR-0041 §3) * RENDERABLE is a TOTAL judgment (ADR-0041 §3): absence is a VALUE, never stuckness. After Train 1B * each sort has a LIVE catalog slot and materialization is TOTAL — `tape d-0` renders the current * session's real bytes (byte-identical to no-arg tape, the ratchet anchor `form` below), `tape d-N` * (N≥1) renders a deterministic absent-with-reason value, and a no-session-boundary scheme renders the * sanctioned periphrasis. The cell MATERIALIZES (byte-identically for its d-0/e-0 anchor form) — the * ADR's definition of ATTESTED. Leaving it LATENT ("not yet materializable") would be FALSE: the * address binds and is served today. The ratchet pins ONLY the d-0/e-0 anchor bytes (`tape d-0` / * `chain e-0`), which are monotone forever regardless of wa0j.20 — the d-N/e-N (N≥1) ABSENCE bytes are * a renderable-rung total value that is deliberately NOT pinned, so kestrel-wa0j.20 can thread the * multiday/multi-expiry DATA and change what those addresses RENDER (absence → real prior bytes) * WITHOUT touching the ratchet. AUTHORITY ARROW (ADR-0041 §3: materialization never consults the * ledger): the builders read the SessionScheme/boundary DATA (not the ledger STATE) to decide the * scheme-conditioned vs frame-conditioned absence; this table merely DESCRIBES, consistently, what * materialization independently does — the two are kept in agreement by both reading * {@link PANE_BOUNDARY_REQUIREMENT} + the scheme boundary declarations, never by materialization * branching on this state. THE DIVISION: Train 1B lands the ADDRESS (the d-N/e-N morphology + binding + * honest absence); kestrel-wa0j.20 lands the DATA (multiday rows, axis re-anchoring, gap-respecting * aggregation, multi-expiry chain). */ const ATTESTED_CELLS = [ { pane: "levels", sort: "LevelName", state: "ATTESTED", form: "levels vwap hod" }, { pane: "tape", sort: "Window", state: "ATTESTED", form: "tape 5m" }, { pane: "chain", sort: "Count", state: "ATTESTED", form: "chain 12" }, { pane: "tape", sort: "SessionOrdinal", state: "ATTESTED", form: "tape d-0" }, { pane: "chain", sort: "ExpiryOrdinal", state: "ATTESTED", form: "chain e-0" }, ]; /** * Roster-sort cells a NAMED train will land (ADR-0041 §3: a LATENT refusal names its train). Grounded * in the ADR-0041 §1 Train-1 roadmap ("`chain` gains `Instrument` + `Count` + `ExpiryOrdinal`"; * "`tape` gains bucket + `SessionOrdinal` + anchor"). Train 1A migrated the Window arg (tape) + Count * (chain); Train 1B added the SessionOrdinal (tape) + ExpiryOrdinal (chain) ADDRESSES — so those flipped * to ATTESTED (see {@link ATTESTED_CELLS}). What REMAINS LATENT is `chain × Instrument` (the marked * cross-instrument leg source), which Train 1B did NOT land — it arrives with the CALC cross-instrument * lexeme, a later train. This is what keeps the four-state grid genuinely LATENT-bearing. */ const LATENT_CELLS = [ { pane: "chain", sort: "Instrument", state: "LATENT", train: "chain Instrument (cross-instrument leg source) — a LATER train, NOT Train 1B (ADR-0041 §1: the " + "marked cross-instrument read is a CALC lexeme taking two Instrument args; Train 1A added Count, " + "Train 1B added ExpiryOrdinal, neither added Instrument)", }, ]; /** * DEFECTIVE cells — cells that NEVER materialize, for a written reason, with a sanctioned periphrasis * (ADR-0041 §3). EMPTY today: no cell in the current pane/sort space is unconditionally DEFECTIVE. * The state is fully SUPPORTED — {@link validateParadigmCell} enforces its shape and * `tests/frame.paradigm-ledger.test.ts` exercises a schema-validated DEFECTIVE example — but no fake * entry is invented ({@link DEFECTIVE_FIRST_ARRIVAL}). The first PRODUCTION DEFECTIVE is * SCHEME-CONDITIONED and arrives with kestrel-wa0j.44 (`prior-context` under a no-close SessionScheme) * and the content-pane discipline of kestrel-wa0j.42 (social/sentiment as a principled DEFECTIVE source). */ const DEFECTIVE_CELLS = []; /** Where the first production DEFECTIVE cell arrives (documented — ADR-0041 §3). It has LANDED with * kestrel-wa0j.44 as a SCHEME-CONDITIONED cell (see {@link SCHEME_CONDITIONED_CELLS}); no cell in the * UNCONDITIONED `(pane × sort)` space is DEFECTIVE (the ratchet's equity-rth bytes stay pinned). */ export const DEFECTIVE_FIRST_ARRIVAL = "No UNCONDITIONED (pane × sort) cell is DEFECTIVE. The first production DEFECTIVE is " + "SCHEME-CONDITIONED and LANDED with kestrel-wa0j.44 (prior-context × SessionOrdinal under a " + "SessionScheme with no close boundary: reason 'no close under this SessionScheme', periphrasis " + "'anchor to the declared rolling extreme' — see schemeConditioned). A further DEFECTIVE source " + "arrives with kestrel-wa0j.42 (social/sentiment — no as-of source, determinism unachievable at the feed)."; // ───────────────────────────────────────────────────────────────────────────── // Scheme-conditioned cells (ADR-0041 §3, kestrel-wa0j.44) — ONE paradigm, conditioned on the registry // ───────────────────────────────────────────────────────────────────────────── /** * The SessionOrdinal boundary each pane's read REQUIRES (ADR-0041 §3 conditioning). A pane listed here * is DEFECTIVE under any {@link ../frame/session-scheme.ts SessionScheme} that does NOT carry the * required boundary, and ATTESTED-as-today under one that does — ONE paradigm, conditioned on the * registry attribute (never two paradigms). * • `prior-context` anchors to the prior CLOSE (the `d-1` close boundary of the SessionOrdinal * stream), so it requires `close`. * • `tape` (Train 1B) addresses a PRIOR SESSION (`tape d-N`, N≥1); a prior session must be delimited * by a session `close`, so a scheme with no close (a perp's rolling UTC day) has no prior-session * address at all — `tape × SessionOrdinal` is scheme-conditioned DEFECTIVE there (the builder * renders the sanctioned periphrasis, anchor to the declared rolling extreme, never an invented * session), and ATTESTED-as-today under a close-bearing scheme (equity-rth, the default). Note this * is the CONDITIONED overlay on the UNCONDITIONED ATTESTED `tape × SessionOrdinal` cell — exactly * the prior-context pattern (one paradigm, two conditioned cells). * Flipping a declaration is the red-capability lever: retarget the required boundary and the conditioned * cell + the production refusal follow. This is the SINGLE SOURCE of the requirement — * {@link resolveCellState}, {@link schemeConditionedDefect}, and the census all read it. */ export const PANE_BOUNDARY_REQUIREMENT = { "prior-context": "close", tape: "close", }; /** The roster sort a boundary-conditioned cell is keyed on: `SessionOrdinal` — the monotone boundary * stream `d-N` indexes (ADR-0041 §1). A close/open boundary IS a SessionOrdinal address. */ export const CONDITIONED_SORT = "SessionOrdinal"; /** The written reason a boundary-conditioned cell is DEFECTIVE (ADR-0041 §3 exemplar, verbatim for * `close`). */ function conditionedReason(kind) { return `no ${kind} under this SessionScheme`; } /** The sanctioned periphrasis for a boundary-conditioned DEFECTIVE cell (ADR-0041 §3 exemplar). */ export const CONDITIONED_PERIPHRASIS = "anchor to the declared rolling extreme"; /** * The scheme-conditioned DEFECTIVE cell for `(pane × scheme)`, or `undefined` when the pane has no * boundary requirement OR the scheme CARRIES the required boundary (then the pane is ATTESTED-as-today, * not DEFECTIVE). Generated from {@link PANE_BOUNDARY_REQUIREMENT} × the scheme's boundary declaration — * the production refusal ({@link ./refusals.ts}) and the census both read this one function, so they * can never quote a different reason/periphrasis than the cell states. */ export function schemeConditionedDefect(pane, scheme) { const req = PANE_BOUNDARY_REQUIREMENT[pane]; if (req === undefined) return undefined; if (schemeHasBoundary(scheme, req)) return undefined; // the scheme carries the boundary ⇒ ATTESTED-as-today return { pane, sort: CONDITIONED_SORT, state: "DEFECTIVE", reason: conditionedReason(req), periphrasis: CONDITIONED_PERIPHRASIS, conditioning: { sessionScheme: scheme }, }; } /** * Every scheme-conditioned DEFECTIVE cell in the v1 grid (ADR-0041 §3) — the Cartesian product of the * boundary-requiring panes × the schemes that LACK their required boundary. This is the production * DEFECTIVE set (the census surfaces it); it is DELIBERATELY separate from {@link PARADIGM_CELLS} (the * unconditioned grid) so the unconditioned census + conformance ratchet keep the equity-rth bytes * pinned. Regenerating the sidecar re-derives this from the tables — it can never drift. */ export const SCHEME_CONDITIONED_CELLS = Object.keys(PANE_BOUNDARY_REQUIREMENT) .flatMap((pane) => SESSION_SCHEMES.map((scheme) => schemeConditionedDefect(pane, scheme))) .filter((c) => c !== undefined); /** Every LISTED cell (ATTESTED + LATENT + DEFECTIVE). Unlisted cells are UNATTESTED (closed-world). */ export const PARADIGM_CELLS = [...ATTESTED_CELLS, ...LATENT_CELLS, ...DEFECTIVE_CELLS]; // ───────────────────────────────────────────────────────────────────────────── // The destination backlog — the burn-down's per-pane views.kestrel LATENT trains (single source) // ───────────────────────────────────────────────────────────────────────────── /** * The LATENT train each `views.kestrel` destination pane form is waiting on (ADR-0041 §3), keyed by * pane id. This is the SINGLE SOURCE that `tests/frame.inflection-burndown.test.ts` reads — the * burn-down no longer keeps its own copy. Distinct projection from {@link PARADIGM_CELLS}: those * classify `(pane × roster-sort)`; this names the train for the destination-corpus FORMS (mostly * new, not-yet-admitted sorts — named level-SETs, tape style/overlay/strip, macro/vol sub-selectors). */ export const DESTINATION_BACKLOG = { macro: "macro period/context sub-selectors (a later train — outside Train 1A/1B)", vol: "vol straddle / expected-move sub-selectors (a later train)", tape: "multi-arg tape — style/overlay/strip position classes (deferred; Train 1A migrated the Window arg, Train 1B added the SessionOrdinal address — style/overlay/strip remain LATENT)", levels: "named / band-keyed level-set idents (e.g. `registry`) — deferred; Train 1A serves individual-level idents only", chain: "chain sub-selectors (fair/realness) + Instrument — deferred to a LATER train (Train 1A added Count, Train 1B added ExpiryOrdinal; Instrument + fair/realness sub-selectors remain LATENT)", }; // ───────────────────────────────────────────────────────────────────────────── // Resolution + accessors // ───────────────────────────────────────────────────────────────────────────── /** True iff `sort` is a member of the closed {@link PARADIGM_ROSTER}. */ export function isRosterSort(sort) { return PARADIGM_ROSTER.includes(sort); } /** * The TOTAL four-state resolution (ADR-0041 §3). A listed unconditioned cell carries its state; every * other `(pane × sort)` is UNATTESTED by the closed-world rule. Total by construction: it returns a * {@link ParadigmState} for EVERY input, never throws, never `undefined` — the property the ledger * test asserts over the whole `pane × roster` grid. * * **Scheme conditioning (kestrel-wa0j.44).** When a {@link CellConditioning} supplying a * `sessionScheme` is passed AND the pane declares a {@link PANE_BOUNDARY_REQUIREMENT} on the * {@link CONDITIONED_SORT}, the state is conditioned on the registry attribute — ONE paradigm, two * conditioned cells: DEFECTIVE when the scheme LACKS the required boundary (a no-close perp), ATTESTED * (as-today) when it carries it (equity-rth). The UNCONDITIONED call (no `conditioning`) is unchanged — * the pane's conditioned axis is invisible to the unconditioned census + the conformance ratchet, so * the equity-rth bytes stay pinned. */ export function resolveCellState(pane, sort, conditioning) { if (!isRosterSort(sort)) return "UNATTESTED"; // a value outside the closed roster (closed-world) // Scheme-conditioned path: a boundary-requiring pane on the conditioned sort resolves against the // supplied scheme's boundary declaration (ADR-0041 §3) — DEFECTIVE if the boundary is absent, else // ATTESTED-as-today. Only reached when a sessionScheme is explicitly supplied (else the unconditioned // grid answers, keeping the ratchet's equity-rth bytes pinned). const scheme = conditioning?.sessionScheme; if (scheme !== undefined && isSessionScheme(scheme) && sort === CONDITIONED_SORT) { const req = PANE_BOUNDARY_REQUIREMENT[pane]; if (req !== undefined) { return schemeHasBoundary(scheme, req) ? "ATTESTED" : "DEFECTIVE"; } } const cell = PARADIGM_CELLS.find((c) => c.pane === pane && c.sort === sort && c.conditioning === undefined); return cell ? cell.state : "UNATTESTED"; } /** The named LATENT train for a destination-corpus pane form, or `undefined` if the pane is not in * the backlog (ADR-0041 §3: a LATENT refusal names its train — pulled from HERE by the constructor). */ export function latentTrainFor(pane) { return DESTINATION_BACKLOG[pane]; } /** The ATTESTED cells (the conformance-ratchet seed — monotone forever, ADR-0041 §3). */ export function attestedCells() { return PARADIGM_CELLS.filter((c) => c.state === "ATTESTED"); } // ───────────────────────────────────────────────────────────────────────────── // The executable schema (mirrors tests/golden/pane-paradigm.schema.json) // ───────────────────────────────────────────────────────────────────────────── /** * Validate ONE cell against the paradigm-cell schema, returning a list of human-readable errors * (empty ⇒ valid). Enforces the per-state required fields (ADR-0041 §3) so a DEFECTIVE cell can * never omit its reason/periphrasis, an ATTESTED cell can never omit its form, etc. Executable mirror * of `tests/golden/pane-paradigm.schema.json`; used by the totality test AND the DEFECTIVE * schema-example test (which proves the state is supported without a fake production entry). */ export function validateParadigmCell(cell) { const errs = []; if (typeof cell !== "object" || cell === null) return ["cell is not an object"]; const c = cell; if (typeof c.pane !== "string" || c.pane.length === 0) errs.push("pane must be a non-empty string"); if (typeof c.sort !== "string" || !isRosterSort(c.sort)) errs.push(`sort must be one of the roster {${PARADIGM_ROSTER.join(", ")}}`); if (c.state !== "ATTESTED" && c.state !== "LATENT" && c.state !== "DEFECTIVE") { errs.push("state must be one of ATTESTED | LATENT | DEFECTIVE (UNATTESTED is the closed-world default, never listed)"); } if (c.state === "ATTESTED" && (typeof c.form !== "string" || c.form.length === 0)) errs.push("an ATTESTED cell requires a non-empty `form`"); if (c.state === "LATENT" && (typeof c.train !== "string" || c.train.length === 0)) errs.push("a LATENT cell requires a non-empty `train`"); if (c.state === "DEFECTIVE") { if (typeof c.reason !== "string" || c.reason.length === 0) errs.push("a DEFECTIVE cell requires a non-empty `reason`"); if (typeof c.periphrasis !== "string" || c.periphrasis.length === 0) errs.push("a DEFECTIVE cell requires a non-empty `periphrasis`"); } if (c.conditioning !== undefined) { if (typeof c.conditioning !== "object" || c.conditioning === null) errs.push("`conditioning`, when present, must be an object"); } return errs; }