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.

337 lines (315 loc) 19.7 kB
/** * kestrel.markets/protocol — the CATALOG contract (protocol v0.2). * * A {@link CatalogEntry} is the content-addressed, reproducibility-complete * descriptor of ONE gradeable subject the managed backend (or a self-hosted * Kestrel) publishes: it pins EVERYTHING a client needs to reproduce the exact * numbers a Grade will certify — the input roots, the schema, the corpus tier, * the engine/Judge versions, the single permitted fill model, the View and * Rendering identities the subject was authored under, and the provenance of the * record. Two runs that pin the same entry are replayable to identical results * (ADR-0010 "legible text is truth; binary stores are projections"; ADR-0011 the * Blotter projector is a pure function of the bus). * * HARD CONSTRAINTS (identical to src/protocol/index.ts — see its header): * - ZERO runtime dependencies. This file imports NOTHING. It is pure types + * own constants and must typecheck with the engine/`chdb` uninstalled. * - SHAPES ONLY. Every content-addressed artifact is an opaque HANDLE * ({@link ContentRoot}); never a key, a signing routine, or an inlined blob. * - GENERIC ONLY. No founding-app tickers, strategy names, or private corpus * identities appear in types, doc comments, or examples. Product identities * ride INSIDE these fields at runtime; they are not part of the contract. */ /* ─────────────────────────── content-addressed handles ─────────────────────── */ /** * Opaque content-addressed handle (a `sha256:…` digest, resolved server-side). * Same opaque-`*Ref` discipline as {@link import("./index.ts").EnvelopeRef} — the * contract ships the ADDRESS of a replayable artifact, never its bytes. Aliased * to `string` (not branded) so a catalog author writes the digest directly. */ export type ContentRoot = string; /** * The corpus tier a catalog entry draws on — an OPAQUE string, deliberately not a * closed enum. It reuses the bus `corpus_tier` vocabulary already landed on the * runtime line (e.g. `"public-baseline"`), so the catalog names the SAME concept * the recorded bus stamps rather than coining a parallel tier axis. */ export type CorpusTier = string; /* ─────────────────────────────── requirements ──────────────────────────────── */ /** * The engine and Judge versions a subject was produced under. A client whose * engine/Judge do not satisfy these MUST fail closed rather than grade against a * drifted implementation — reproducibility is pinned, never approximated. Values * are opaque version tags (the client compares, the protocol does not interpret). */ export interface CatalogRequirements { /** Engine (runtime) version the entry was produced under. */ readonly engineVersion: string; /** Judge version whose certified numbers the entry pins. */ readonly judgeVersion: string; } /* ─────────────────────────────── provenance ────────────────────────────────── */ /** * Where a catalog entry came from and when it was recorded. The `source` is an * opaque content-addressed handle to the originating record (never a private * upstream path); `recordedAt` is an ISO-8601 timestamp. Provenance is a stated * FACT on the entry, not something the protocol computes. */ export interface CatalogProvenance { /** Opaque handle to the originating record. */ readonly source: ContentRoot; /** ISO-8601 time the entry was recorded. */ readonly recordedAt: string; } /* ───────────────────────────── input pin (kestrel-m9i.27) ───────────────────── */ /** * The reproducible AUTHORING INPUT a benchmark cell pins (kestrel-m9i.27) — content-addressed, so the * cell's exact input is recoverable and a plan-doc difference is VISIBLE (unlike a name-only id, which * collides two different docs authored under one plan name): * * - `fixed-plan` a cell with a FIXED authored plan doc pins the DOC by its OWN content hash * (`planDoc = "sha256:" + sha256(doc)`); the doc itself is stored content-addressed * alongside, so the exact authored plan is recoverable BYTE-FOR-BYTE from the manifest. * - `agent-authored` a cell whose agent authors the plan LIVE has no fixed doc, so it pins the CONFIG / * prompt-profile (`config`, a content root) — the reproducible input there is the * config, not a doc. * * Each handle is an opaque {@link ContentRoot} (`sha256:<hex>`), the same address discipline as the rest of * the entry. A discriminated union so the two cases never conflate. A cell whose pinned fixed-plan doc is * missing or does not hash to its pin is REFUSED by the loader (fail-closed) — never silently reproducible. */ export type InputPin = | { readonly kind: "fixed-plan"; readonly planDoc: ContentRoot } | { readonly kind: "agent-authored"; readonly config: ContentRoot }; /* ─────────────────────────────── catalog entry ─────────────────────────────── */ /** * One content-addressed, reproducibility-complete catalog entry (djm.2 AC2). * Pins the full surface required to replay a subject to identical certified * numbers. Every field is a stated, content-addressed fact: * * - `contentRoots` the input roots (e.g. recorded bus + input tape) the entry * is projected from — ADR-0010/0011 projections pin here. * - `schema` the wire schema this entry conforms to (e.g. * `"kestrel.session/0.2"`); its minor tracks PROTOCOL_VERSION. * - `corpusTier` the opaque {@link CorpusTier}, aligned with bus `corpus_tier`. * - `requirements` the engine/Judge versions a replay must satisfy. * - `permittedFillModel` the SINGLE fill model this entry is graded under * (the two-bus rule: one graded bus, one named model). * - `viewIdentity` the View identity the subject perceived under (handle). * - `renderingIdentity` the Rendering identity — how the Frame was rendered to * tokens — the subject was authored under (handle). * - `provenance` where the entry came from and when it was recorded. * - `inputPin` the reproducible AUTHORING INPUT (kestrel-m9i.27): a fixed-plan cell pins its * authored plan doc by content hash (recoverable byte-for-byte); an agent-authored * cell pins its config. OPTIONAL — additive to this frozen contract. */ export interface CatalogEntry { /** * The stable, human-readable HANDLE a client passes to `openSession({ kind: "catalog", id })` — the ONE * field that is a NAME, not a `sha256:` digest (a generic regime label like `"choppy-1101-strict"`; never a * founding-app ticker or strategy). It rides here, on the wire contract, so the discover→open loop CLOSES * off the public catalog projection ALONE: a client `catalog()`s, reads `entry.id`, and opens that exact * subject — with no reach past the SDK barrel into any host-internal catalog to recover the addressing key. */ readonly id: string; /** Content-addressed input roots the entry projects from (ADR-0010/0011). */ readonly contentRoots: readonly ContentRoot[]; /** Wire schema tag this entry conforms to (e.g. `"kestrel.session/0.2"`). */ readonly schema: string; /** Opaque corpus tier, aligned with the bus `corpus_tier` vocabulary. */ readonly corpusTier: CorpusTier; /** Engine/Judge versions a faithful replay must satisfy. */ readonly requirements: CatalogRequirements; /** The single permitted fill model this entry is graded under. */ readonly permittedFillModel: string; /** The View identity the subject perceived under (content-addressed handle). */ readonly viewIdentity: ContentRoot; /** The Rendering identity the subject was authored under (content-addressed handle). */ readonly renderingIdentity: ContentRoot; /** Where the entry came from and when it was recorded. */ readonly provenance: CatalogProvenance; /** * The reproducible AUTHORING INPUT this cell pins (kestrel-m9i.27): a fixed-plan cell pins its authored * plan DOC by content hash (the doc recoverable byte-for-byte); an agent-authored cell pins its CONFIG. * OPTIONAL — additive to this frozen contract, so a pre-m9i.27 entry still conforms; the catalog LOADER * fails closed on a benchmark cell whose fixed-plan doc is missing or does not hash to its pin (never * silently reproducible). {@link InputPin}. */ readonly inputPin?: InputPin; } /* ─────────────────────────────── catalog LISTING (discovery) ────────────────── */ /** * A calendar period a listed subject covers — `start`/`end` are ISO-8601 date strings * (`YYYY-MM-DD`). The sub-key order (`start` then `end`) is part of the canonical * {@link CatalogListing} byte shape. */ export interface CatalogPeriod { /** ISO-8601 date the subject's tape begins (`YYYY-MM-DD`). */ readonly start: string; /** ISO-8601 date the subject's tape ends (`YYYY-MM-DD`). */ readonly end: string; } /** * The DISCOVERY projection the `catalog` op returns — the ONE shape BOTH transports serve, byte-identical * (kestrel-dnxq). It is distinct from {@link CatalogEntry}, the reproducibility-COMPLETE surface (content * roots, engine/Judge versions, View/Rendering identities, the permitted fill model) resolved at * `openSession`/grade time: a browsing client choosing WHAT to open needs the human-legible menu * (title / description / instrument / period / frame count / free), never the replay pins — and the managed * backend deliberately does NOT ship the pins in its `GET /catalog` listing (they resolve server-side at * open). `CatalogListing` is therefore the honest INTERSECTION both a self-hosted mirror and the managed * backend can emit for the same subject; it is the camelCase projection of the deployed platform's listing * row (`artifact_id → id`, `frame_count → frameCount`; the optional `content_hash` reproducibility pin is * NOT part of discovery and is dropped). * * INVARIANT (kestrel-dnxq): the catalog op's protocol OBJECTS are byte-identical across transports — the * SHAPE and key order below are the same on LOCAL and HTTP. The catalog CONTENTS are transport-specific and * legitimately differ: the LOCAL transport lists its offline in-process sample; the managed transport lists * the live catalog. Shape parity is the guarantee; content parity is NOT claimed (and a cross-transport id * addresses a subject only on the transport that listed it — the two catalogs are different catalogs). * * Canonical key order: `id`, `title`, `description`, `instrument`, `period`, `frameCount`, `free`. */ export interface CatalogListing { /** The stable, addressable catalog HANDLE. The self-hosted LOCAL transport opens it as an incremental * `openSession({ kind: "catalog", id })`; the managed transport serves it as a BATCH simulation (`sim`) — * managed incremental `openSession` over a catalog subject is not yet served (it refuses fail-closed with a * typed hint naming `sim`). (The managed backend's `artifact_id`, surfaced under the protocol `id` name.) */ readonly id: string; /** The human name of the subject (the discovery menu label). */ readonly title: string; /** A one-line human description of the scenario. */ readonly description: string; /** The generic instrument the subject is authored over (e.g. `"QQQ"`). */ readonly instrument: string; /** The calendar period the subject's tape covers. */ readonly period: CatalogPeriod; /** The number of decision Wake frames the subject presents (a discovery hint; the replay pins resolve at * `openSession`). */ readonly frameCount: number; /** `true` ⇒ the subject opens with no payment (a free/trial subject). */ readonly free: boolean; } /* ─────────────────────────── ex-ante PRICING (kestrel-adge) ─────────────────── */ /** * A priced COMMERCIAL TERM on a catalog line — the JSON projection of the offer-terms * vocabulary (a `kind`-discriminated object; cf. {@link import("./offer.ts").OfferTerms}). * Carried OPAQUELY on the catalog contract: `kind` is stated, but the remaining fields are * PRODUCER-DEFINED and this contract does NOT close them (a period may be an ISO-8601 * duration or a word; an amount may be a decimal string or a structured Money) — a * budget-holding brain reads the term, the wire does not interpret it. Same pass-through * discipline as {@link CatalogSettlementMethod.params}, so a producer's exact term shape is * surfaced without a second, divergence-prone `OfferTerms` fork living on the catalog. */ export interface CatalogTerm { /** The term discriminant (e.g. `"one_shot"`, `"recurring"`). */ readonly kind: string; /** Producer-defined term fields, carried verbatim (not interpreted by the contract). */ readonly [field: string]: unknown; } /** * An accepted machine SETTLEMENT RAIL advertised alongside the price sheet — the wire * projection a minted 402 Offer settles on (mirrors the platform's served * `settlement_methods`). Distinct from {@link import("./index.ts").SettlementMethod} (the * Offer's `kind`-discriminated rail): this is the price-sheet's discovery view, keyed by * `method`, so the catalog surfaces EXACTLY what the platform publishes. `method` is an * OPAQUE string (e.g. `"account-credit"`, `"stripe-mpp"`, `"x402"`), never closed here. */ export interface CatalogSettlementMethod { /** The settlement rail identifier (opaque; e.g. `"account-credit"` | `"stripe-mpp"` | `"x402"`). */ readonly method: string; /** Optional settlement network (e.g. `"base"`, `"tempo"`). */ readonly network?: string; /** Optional accepted settlement asset codes, in preference order. */ readonly accepts?: readonly string[]; /** Optional rail parameters, carried verbatim (not interpreted by the contract). */ readonly params?: Readonly<Record<string, unknown>>; } /** * One PURCHASABLE line on the ex-ante price sheet — the exact line item a 402 Offer is * priced against, projected into the catalog so a budget-holding agent can PRICE a paid * boundary BEFORE it provokes a short-lived Offer. Mirrors the platform's served price * line (snake_case, verbatim): `amount`/`asset` is the price, `label` is what-you-get, * `scope`/`ceiling`/`requires_human` bound the authority, and an optional `artifact_id` is * a concrete requestable paid subject. `scope` is an OPAQUE string (the same deliberate * non-closure as {@link CorpusTier}), so the catalog never forks the authority vocabulary. */ export interface CatalogPriceLineItem { /** Stable machine SKU an Offer's intent is priced against. */ readonly sku: string; /** The authority scope this line admits into a child Envelope (opaque string). */ readonly scope: string; /** Human-facing label — what the buyer gets. */ readonly label: string; /** The metered unit the amount is charged per. */ readonly unit: string; /** Price per unit, a decimal string (to avoid float error). */ readonly amount: string; /** Settlement asset code the amount is quoted in. */ readonly asset: string; /** Worst-case dollar ceiling of the admitted Envelope (the bounded-risk bound). */ readonly ceiling: number; /** A concrete, requestable paid `artifact_id` for THIS line (optional; most lines price a surface). */ readonly artifact_id?: string; /** True when the scope carries agreements only a human signer can establish. */ readonly requires_human: boolean; /** The base pricing TERM (optional; absent ⇒ the implicit one-shot exact settlement). */ readonly terms?: CatalogTerm; /** Additional priced TERMS for the same capability (optional). */ readonly alternatives?: readonly CatalogPriceAlternative[]; } /** An alternative priced TERM for a line item — the same capability under a second pricing * structure (e.g. a recurring plan alongside the one-shot line). Mirrors the platform's * served alternative shape verbatim. */ export interface CatalogPriceAlternative { /** The pricing term this alternative is priced under. */ readonly terms: CatalogTerm; /** Human-facing label (e.g. `"Monthly data plan"`). */ readonly label: string; /** Price for this alternative, a decimal string. */ readonly amount: string; /** Settlement asset code. */ readonly asset: string; /** Worst-case dollar ceiling of the Envelope this alternative admits. */ readonly ceiling: number; } /** * The EX-ANTE PRICING block (kestrel-adge / platform bead z3is, "The Perch") — the * machine-readable answer to "what can I BUY, and for how much" a budget-holding agent * reads off the FREE catalog BEFORE it provokes a 402. It is the CANONICAL projection of * the SAME price line items a 402 Offer is minted against (estimate ≡ meter), so it can * never drift from what an Offer actually quotes. Mirrors the platform's served `pricing` * shape VERBATIM (snake_case), so the platform is a CONSUMER of this contract, not a * diverger. OPTIONAL on {@link CatalogPage} — absent ⇒ today's bare listing, fully * backward-compatible. */ export interface CatalogPricing { /** True iff at least one line item is machine-purchasable today (a positive amount, no human signer). */ readonly paid_available: boolean; /** The canonical HTTP path the full, authoritative price sheet is served at. */ readonly price_sheet_url: string; /** The sheet's content version (a pricing experiment bumps this). */ readonly price_sheet_version: string; /** Settlement currency the amounts are quoted in. */ readonly currency: string; /** Every purchasable SKU, verbatim from the price sheet the Offer mint reads. */ readonly skus: readonly CatalogPriceLineItem[]; /** The accepted settlement rails a minted Offer settles on, in preference order. */ readonly settlement_methods: readonly CatalogSettlementMethod[]; /** A human-readable note describing the sheet (machine-legible guidance). */ readonly note: string; } /** * The catalog DISCOVERY BODY (kestrel-adge) — the wrapped shape `GET /catalog` serves: * the {@link CatalogListing}[] menu PLUS the optional ex-ante {@link CatalogPricing} block. * This is the CANONICAL cross-face catalog wire shape (HTTP/SDK/MCP): the SDK's * `catalog()` returns this exact page, so the return TYPE matches the served runtime shape * (resolving the class where a bare-array type dropped `pricing`). `pricing` is OPTIONAL — * a body without it decodes to a page whose `entries` are today's bare listing, so existing * consumers are unaffected and a self-hosted mirror that serves no price sheet is honest. */ export interface CatalogPage { /** The canonical discovery menu — the byte-identical {@link CatalogListing}[] across transports. */ readonly entries: readonly CatalogListing[]; /** The ex-ante pricing block, when the producer serves one (absent ⇒ a bare listing). */ readonly pricing?: CatalogPricing; }