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
JavaScript
/**
* # 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;
}