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.
1,025 lines (940 loc) • 54.9 kB
text/typescript
/**
* kestrel.markets/protocol — the CANONICAL wire-protocol types (v0.1).
*
* This module is the open, versioned contract that the managed backend
* implements (ADR-0010): the platform is the SERVER and imports these SHAPES.
*
* HARD CONSTRAINTS (do not violate):
* - ZERO runtime dependencies. This file imports NOTHING. It must typecheck
* with `chdb` uninstalled and pull in no engine / language / series / fill
* code. Keep it that way — it is the core side of the core-vs-local split.
* - SHAPES ONLY. No signing keys, no Stripe/commerce logic, no corpus/presence.
* Every signed, replayable artifact carries an opaque `*Ref` HANDLE, never a
* key or a signing routine (ADR-0010 open/closed seam).
* - GENERIC INSTRUMENTS ONLY. Order/Fill/Position/Blotter model instruments
* generically (opaque `symbol: string`). No founding-app tickers or strategy
* names appear in types, doc comments, or examples. Product tickers ride
* INSIDE these fields at runtime; they are not part of the published contract.
*
* Scope: v0.1 covered the trial→conversion (M1) surface — authority-continuation,
* purchase, and grade receipts. v0.2 ADDS, non-breakingly, the content-addressed
* catalog contract (./catalog.ts), the incremental Session transcript
* (./session.ts) — both re-exported below — and the M2–M4 receipt roots
* (input-admission, resource-value, promotion, live-activation), which the
* CertifiedReceipt umbrella admits as non-breaking additions to AnyReceipt. No
* existing v0.1 shape changed, so 0.2 stays a purely additive minor. v0.3 ADDS,
* non-breakingly, the oversight contract (./oversight.ts) — the cross-repo seam
* between the CLI and Web Renderings of the human PM/Pod oversight surface
* (ADR-0035), re-exported below. No existing shape changed, so 0.3 stays a purely
* additive minor.
*
* Under 0.3, further ADDITIVE-OPTIONAL shapes have landed without a minor bump —
* the same rule `inputPin` (kestrel-m9i.27), the OfferTerms vocabulary
* (PLAT-ADR-0028), the OSS-ADR-0046 wire shapes, and the h314 certified-evidence
* fields all rode under. The ex-ante catalog PRICING block (kestrel-adge —
* {@link import("./catalog.ts").CatalogPricing} on the optional
* {@link import("./catalog.ts").CatalogPage}) is another: it adds an OPTIONAL field
* to a wrapped catalog body (absent ⇒ today's bare listing) and touches no existing
* shape, so it rides additive under 0.3. The MINOR moves only for a deliberate
* milestone surface (as at 0.2 and 0.3), never for each additive-optional field.
*
* Version: `major.minor`. Bump the MAJOR on any breaking shape change; bump the
* MINOR for a purely additive milestone (v0.2 = catalog + Session, additive to
* every v0.1 shape — no existing field changed).
*/
import type { OfferTerms } from "./offer.ts";
import type { FillSupport } from "./support.ts";
/* ────────────────────────────── version ────────────────────────────────── */
/**
* Canonical protocol version (`major.minor`). Bump the major on any breaking
* shape change; bump the minor for a purely additive milestone. v0.2 adds the
* catalog + incremental Session surface without touching any v0.1 shape.
*/
export const PROTOCOL_VERSION = "0.4" as const;
/**
* @deprecated Alias of {@link PROTOCOL_VERSION} kept so existing consumers that
* import `CONTRACT_VERSION` swap module specifiers without a rename. Remove once
* all consumers migrate to PROTOCOL_VERSION.
*/
export const CONTRACT_VERSION = PROTOCOL_VERSION;
/* ─────────────────────────────── faces ─────────────────────────────────── */
/** The four faces every operation must project identically (ADR-0004). */
export const FACES = ["http", "sdk", "cli", "mcp"] as const;
export type Face = (typeof FACES)[number];
/* ──────────────────────────── scope + signers ──────────────────────────── */
/**
* Authorization scopes (ADR-0003 two-signer rule). Ordered least→most
* privileged. Treat as extensible: new scopes may be added, so consumers must
* not exhaustively switch without a default. (Attestation is modeled separately
* as {@link AttestationRole}, not as a Scope.)
*
* The trailing three are the FLEET/PM-tier scopes (OSS-ADR-0035 / PLAT-ADR-0031 §2),
* neither wallet- nor per-act human-signable but granted as a whole PM/trader
* ENVELOPE via the delegation handshake (PLAT-ADR-0031 §1):
* - `fleet-evidence:self` — a trader reads its OWN blotter/journal/mandate/brief
* (PLAT-ADR-0026 §5's own-scope `fleet-evidence read`, adopted here by name).
* - `fleet-evidence:subtree` — a PM reads every child trader's evidence across its
* workspace subtree (the new PM reach; supersedes 0026's own-only term, split into
* `:self` == own and `:subtree` == subtree).
* - `fleet:write` — a PM writes the population (hire/assign/allocate/arm on
* the paper path; live-arming still suspends to a human per PLAT-ADR-0003). Widening
* stays inexpressible in the algebra — a PM cannot mint authority it does not hold.
*/
export type Scope =
| "data"
| "sim"
| "grade"
| "paper"
| "broker"
| "live"
| "fleet-evidence:self"
| "fleet-evidence:subtree"
| "fleet:write";
/**
* Every authorization scope as a runtime tuple — the closed wire vocabulary of
* {@link Scope}, ordered least→most privileged to match the type. The `satisfies`
* proves the tuple stays a SUBSET of Scope; the paired exhaustiveness guard proves
* it names EVERY member. Together they fail closed in BOTH directions: a scope
* added to (or removed from) the union without editing this tuple breaks the
* build, so the runtime vocabulary can never silently drift from the type.
*/
export const SCOPES = [
"data",
"sim",
"grade",
"paper",
"broker",
"live",
"fleet-evidence:self",
"fleet-evidence:subtree",
"fleet:write",
] as const satisfies readonly Scope[];
/** Reverse-exhaustiveness proof: any `Scope` omitted from {@link SCOPES} makes `_Missing`'s default non-`never`, which violates its `never` constraint and fails to compile. */
type _ScopesExhaustive<_Missing extends never = Exclude<Scope, (typeof SCOPES)[number]>> = true;
/** Scopes a wallet may sign (reversible, commerce-only). The rest require a human. */
export const WALLET_SIGNABLE_SCOPES = ["data", "sim", "grade", "paper"] as const satisfies readonly Scope[];
/** True iff `scope` may be authorized by a wallet signature alone. */
export function isWalletSignable(scope: Scope): boolean {
return (WALLET_SIGNABLE_SCOPES as readonly Scope[]).includes(scope);
}
/** Authority classes that can hold/sign a grant (ADR-0003). */
export type SignerClass = "wallet" | "human";
/** Runtime tuple of every {@link SignerClass}. Same both-ways closed-vocabulary guard as {@link SCOPES}. */
export const SIGNER_CLASSES = ["wallet", "human"] as const satisfies readonly SignerClass[];
type _SignerClassesExhaustive<_Missing extends never = Exclude<SignerClass, (typeof SIGNER_CLASSES)[number]>> = true;
/**
* Conjunctive attestation roles layered ON TOP of an authority signature
* (ADR-0003 refinement). A grade/executor attestation NEVER substitutes for a
* human authority signature — it is required IN ADDITION.
*/
export type AttestationRole = "compute" | "executor";
/** Runtime tuple of every {@link AttestationRole}. Same both-ways closed-vocabulary guard as {@link SCOPES}. */
export const ATTESTATION_ROLES = ["compute", "executor"] as const satisfies readonly AttestationRole[];
type _AttestationRolesExhaustive<_Missing extends never = Exclude<AttestationRole, (typeof ATTESTATION_ROLES)[number]>> = true;
/**
* Versioned result of resolving a requested scope set into the signatures and
* attestations required to mint an Envelope. Conjunctive across roles.
*/
export interface SignerRequirementSet {
readonly version: typeof PROTOCOL_VERSION;
readonly scope: readonly Scope[];
/** Authority signatures required (all must be present). */
readonly requiredSigners: readonly SignerClass[];
/** Attestation roles required in addition to the authority signatures. */
readonly requiredAttestations: readonly AttestationRole[];
}
/**
* Revocation / currentness coordinate for one Root subtree (ADR-0003). Distinct
* from the live fencing epoch. Monotonic within a subtree; a higher epoch
* supersedes lower ones.
*/
export type AuthorityEpoch = number;
/** Opaque identifier of a node in a workspace's pod/authority tree. */
export type NodeId = string;
/** Opaque handle to a revocation record; resolution is server-side. */
export type RevocationRef = string;
/**
* Immutable, versioned top of a workspace authority tree (ADR-0003). Existing
* children never widen; supersession re-derives a NARROWER grant.
*/
export interface RootGrant {
readonly node: NodeId;
readonly generation: number;
readonly signerClass: SignerClass;
readonly scope: readonly Scope[];
readonly authorityEpoch: AuthorityEpoch;
}
/**
* A signed grant bound to one node of the pod tree (ADR-0003). Narrowing-only
* downward: a derived child's scope/budget/ceiling never exceed its parent's.
* This is an invariant on how Envelopes relate, not a field.
*/
export interface Envelope {
readonly node: NodeId;
readonly scope: readonly Scope[];
/** Cumulative spend cap granted (settlement asset minor units or abstract). */
readonly budget?: number;
/** Per-operation cap. */
readonly ceiling?: number;
/** ISO-8601 expiry; absent = no time bound. */
readonly expiry?: string;
/** Opaque handle to this Envelope's revocation record, if revocable. */
readonly revocation?: RevocationRef;
}
/** Opaque handle to a minted/derived Envelope (used in receipts). */
export type EnvelopeRef = string;
/* ──────────────────────────────── offer ────────────────────────────────── */
/** How an Offer may be settled (ADR-0002/0003). Discriminated on `kind`. */
export type SettlementMethod =
| { readonly kind: "stripe-mpp" }
| { readonly kind: "x402"; readonly network?: string; readonly asset?: string }
| { readonly kind: "human-claim"; readonly url: string };
/**
* Platform-signed commercial proposal bound to exactly one Operation (ADR-0002).
* Accepting an Offer can mint ONLY its named Envelope's scope; price never
* supplies broker/live authority — those still require a human signature.
*/
export interface Offer {
readonly offerId: string;
readonly operationId: string;
/** Hash of the canonical agent intent this Offer prices. */
readonly intentHash: string;
readonly scope: readonly Scope[];
/** Spend ceiling of the Envelope this Offer would mint. */
readonly ceiling: number;
/** Amount to be settled to accept. */
readonly amount: number;
/** Settlement asset code (e.g. "usd"); opaque to the protocol. */
readonly asset: string;
/** Digest of the human-readable terms; full terms fetched by ref, not inlined. */
readonly termsDigest: string;
/** ISO-8601 expiry after which the Offer is void. */
readonly expiry: string;
/** Anti-replay nonce; one Offer accepts once. */
readonly nonce: string;
readonly settlementMethods: readonly SettlementMethod[];
/**
* The commercial terms this Offer prices ({@link OfferTerms}). Additive vocabulary
* (PLAT-ADR-0028 A1): absent = the legacy implicit `one_shot` shape. Nouns only — no
* billing/renewal/credit-burn SEMANTICS live on the protocol.
*/
readonly terms?: OfferTerms;
}
/* ─────────────────────────── proof + the 402 body ──────────────────────── */
/**
* Shareable evidence of a certified result earned under a trial capability —
* the conversion-moment artifact (ADR-0002).
*/
export interface Proof {
readonly proofUrl: string;
/** The capability under which the result was earned. */
readonly earnedUnder: string;
/** Opaque handle to the certified result the proof points at. */
readonly resultRef: string;
}
/**
* The structured HTTP 402 body (ADR-0002/0004) — part of the CONTRACT, not a
* transport error. Every face (http/sdk/cli/mcp) exposes it identically. Carries
* the free result already earned plus BOTH continuations, scope-gated. This
* wraps an {@link Offer}; it is not an Offer itself.
*/
export interface PaymentRequired {
readonly operationId: string;
/** The free result already earned, as shareable proof. */
readonly proof: Proof;
readonly offer: Offer;
/**
* The plural commercial alternatives on this 402 boundary (PLAT-ADR-0028 A1): e.g. a
* `one_shot` beside a `recurring` or `committed_use_discount`, each a fully-formed
* {@link Offer} carrying its own {@link OfferTerms}. Additive vocabulary — no
* selection SEMANTICS here. INVARIANT (unenforced at this layer): when present it
* includes the singular `offer` as its default/primary member.
*/
readonly offers?: readonly Offer[];
/**
* Machine-settleable continuations. INVARIANT: a subset of the wrapped
* `offer.settlementMethods` containing only the non-`human-claim` variants.
*/
readonly machineSettlement: readonly SettlementMethod[];
/**
* The contextual human claim-and-fund continuation. INVARIANT: its `url`
* equals the wrapped Offer's `human-claim` SettlementMethod url when present.
*/
readonly humanAction: { readonly url: string };
}
/* ───────────────────────────── capabilities ────────────────────────────── */
/**
* Ephemeral binding of an anonymous Operation's subject (ADR-0002/0009). Opaque
* commitment; the platform resolves the real principal server-side.
*/
export type SubjectCommitment = string;
/** Base grant-token shape common to all capabilities. */
export interface Capability {
readonly capabilityId: string;
readonly scopes: readonly Scope[];
/** ISO-8601 expiry. */
readonly expiry: string;
}
/**
* Anonymous, ephemeral, rate-limited capability (ADR-0002). Permits catalog
* discovery, validation, free sim instances, certified grades/blotters,
* temporary artifacts, and proof URLs. EXCLUDES commerce, paper, broker, wallet,
* and live — its `scopes` are a subset of WALLET_SIGNABLE_SCOPES. May PRESENT
* external settlement evidence but never controls a wallet until a new Envelope
* is minted.
*/
export interface TrialCapability extends Capability {
readonly kind: "trial";
readonly subjectCommitment: SubjectCommitment;
/** Requests-per-window budget; shape is server-defined, referenced opaquely. */
readonly rateLimit: { readonly limit: number; readonly windowSeconds: number };
}
/* ───────────────────────────── operation ───────────────────────────────── */
/** Durable state of an Operation across the free→settle→complete lifecycle. */
export type OperationState =
| "open"
| "awaiting-settlement"
| "awaiting-human"
| "resumed"
| "completed"
| "failed";
/** Runtime tuple of every {@link OperationState}. Same both-ways closed-vocabulary guard as {@link SCOPES}. */
export const OPERATION_STATES = [
"open",
"awaiting-settlement",
"awaiting-human",
"resumed",
"completed",
"failed",
] as const satisfies readonly OperationState[];
type _OperationStatesExhaustive<_Missing extends never = Exclude<OperationState, (typeof OPERATION_STATES)[number]>> = true;
/** Resumable SSE event position (ADR-0004: resumable event cursors are canonical). */
export interface EventCursor {
/** Monotonic event sequence number last delivered. */
readonly sequence: number;
/** Opaque resume token the server maps back to a stream offset. */
readonly token: string;
}
/**
* Durable, resumable control-plane unit for ONE canonical agent intent
* (ADR-0004). Survives the free boundary, machine settlement, human completion,
* callbacks, and retries WITHOUT changing `operationId`.
*/
export interface Operation {
readonly operationId: string;
readonly intentHash: string;
readonly state: OperationState;
readonly cursor: EventCursor;
/** Opaque handle to the latest durable checkpoint, if any. */
readonly checkpoint?: string;
readonly authorityEpoch: AuthorityEpoch;
/** Incremented each time the Operation is resumed after a boundary. */
readonly resumeGeneration: number;
}
/* ───────────────────────────── receipts ────────────────────────────────── */
/**
* Umbrella base for every platform-signed, replayable receipt (ADR-0004). The
* signature is referenced by opaque handle — never the key or routine.
* Concrete receipts compose this base.
*/
export interface CertifiedReceipt {
readonly kind: string;
/** Opaque handle to the platform's signature over this receipt. */
readonly signatureRef: string;
/** Root nodes whose authority this receipt is bound under. */
readonly boundRoots: readonly NodeId[];
/** ISO-8601 issuance time. */
readonly issuedAt: string;
}
/**
* Immutable record binding an Operation to its canonical intent, proposal,
* qualification, checkpoint, successor Root, child-Envelope derivation, authority
* epoch, resume generation, and first resumed event sequence (ADR-0004).
* Idempotent on duplicate callbacks; cannot repeat a completed semantic-effect
* slot.
*/
export interface AuthorityContinuationReceipt extends CertifiedReceipt {
readonly kind: "authority-continuation";
readonly operationId: string;
readonly intentHash: string;
readonly successorRoot: NodeId;
readonly derivedEnvelope: EnvelopeRef;
readonly authorityEpoch: AuthorityEpoch;
readonly resumeGeneration: number;
readonly firstResumedSequence: number;
}
/**
* Settlement evidence on a {@link PurchaseReceipt}: either a provider payment id
* or an on-chain transaction. Discriminated by presence.
*/
export type ProviderPayment =
| { readonly provider: string; readonly paymentId: string }
| { readonly chainTx: string };
/**
* Record that an Offer was settled and an Envelope granted (ADR-0003). Each
* provider payment id satisfies one Offer ONCE; duplicate callbacks are
* idempotent; cross-Operation replay is refused (fail-closed).
*/
export interface PurchaseReceipt extends CertifiedReceipt {
readonly kind: "purchase";
readonly offerId: string;
/** Opaque handle to the paying principal; resolution is server-side. */
readonly payerPrincipal: string;
readonly providerPayment: ProviderPayment;
readonly acceptedTerms: { readonly termsDigest: string };
readonly grantedEntitlement: EnvelopeRef;
/** Opaque handle to the delivered artifact/stream. */
readonly delivery: string;
}
/* ──────────────────────── generic instruments ──────────────────────────── */
/**
* Generic instrument descriptor. Opaque `symbol` — NO founding-app tickers in
* the contract; product symbols ride here at runtime. Covers equity/ETF and a
* generic option leg; extend via `kind` without encoding any strategy.
*/
export type Instrument =
| { readonly kind: "equity"; readonly symbol: string }
| {
readonly kind: "option";
readonly symbol: string;
readonly right: "call" | "put";
readonly strike: number;
/** ISO-8601 (date or datetime) expiry. */
readonly expiry: string;
};
export type Side = "buy" | "sell";
/** Runtime tuple of every {@link Side}. Same both-ways closed-vocabulary guard as {@link SCOPES}. */
export const SIDES = ["buy", "sell"] as const satisfies readonly Side[];
type _SidesExhaustive<_Missing extends never = Exclude<Side, (typeof SIDES)[number]>> = true;
/** A single order as recorded in a Blotter. Generic instrument fields only. */
export interface Order {
readonly orderId: string;
readonly instrument: Instrument;
readonly side: Side;
readonly qty: number;
/** Limit price in the instrument's quote units. */
readonly price: number;
/** ISO-8601 time the order was placed. */
readonly placedAt: string;
}
/** A single fill against an order. Generic instrument fields only. */
export interface Fill {
readonly fillId: string;
readonly orderId: string;
readonly instrument: Instrument;
readonly side: Side;
readonly qty: number;
readonly price: number;
/** ISO-8601 time the fill occurred. */
readonly filledAt: string;
}
/** A held position snapshot. Generic instrument fields only. */
export interface Position {
readonly instrument: Instrument;
/** Signed quantity (positive = long, negative = short). */
readonly qty: number;
/** Average entry price in quote units. */
readonly avgPrice: number;
}
/** Terminal settlement summary of a Session run. */
export interface Settlement {
/** Realized cash result in the settlement asset's minor units or abstract. */
readonly realized: number;
readonly asset: string;
/** ISO-8601 settlement time. */
readonly settledAt: string;
}
/* ─────────────────── certified evidence (kestrel-h314, additive) ─────────────────── */
/**
* Which P&L channel a Blotter's headline number came from (CONTEXT "Headline"): the conservative
* definite-fill `floor`, the probability-weighted `expected`, or a seeded causal `sampled`
* realization. Mirrors the engine's channel vocabulary — the wire copy so an outsider recomputing a
* grade reads the same channel identity without the runtime.
*/
export type PnlChannel = "floor" | "expected" | "sampled";
/**
* The sampled-channel qualification-gate verdict carried on the headline (CONTEXT "Certified evidence"):
* whether the sampled channel earned headline status and, when refused, the fail-closed `reasons`. A
* verdict-only wire shape — no study internals, just the pass/fail + why.
*/
export interface SampledQualification {
readonly qualified: boolean;
readonly reasons: readonly string[];
}
/**
* The headline record on a Blotter's totals (CONTEXT "Certified evidence"): WHICH `channel` is the
* headline and its `usd` value, the qualification `gate` verdict that selected it, and the honesty
* `tainted` bit (with `reasons`) — `true` when any non-zero expected contribution rode on extrapolated
* support or the settle mark itself was untrustworthy. The number always travels with its taint attached,
* never silently.
*/
export interface HeadlineFlag {
readonly channel: PnlChannel;
readonly usd: number;
readonly gate: SampledQualification;
readonly tainted: boolean;
readonly reasons: readonly string[];
}
/**
* The verdict-only projection of the engine's settle mark that crosses onto the public Blotter
* (CONTEXT "SettleMarkVerdict"; kestrel-h314 D3, DESIGN §1.3 exposure boundary). The engine's rich
* `SettleMarkRecord` — the raw vendor settle spot (`px`) plus its `as_of_ts` / `last_observed_ts` /
* `age_ms` observation timestamps — is LICENSED data and stays private; only the derived staleness
* VERDICT ships, so a Blotter can carry settle-mark honesty without redistributing vendor data.
*/
export interface SettleMarkVerdict {
/** Was the mark stale at the settle instant (taints {@link HeadlineFlag})? */
readonly stale: boolean;
/** The mark's provenance: a live `market` print, a `parity`-recovered spot, or a retained `stale` value. */
readonly source: "market" | "parity" | "stale";
/** `true` when the mark could not be recovered (the loudest taint) — a non-bankable settle. */
readonly mark_uncertain: boolean;
/** The honest human annotation, when the recovery/staleness layer recorded one. */
readonly note: string | null;
}
/**
* The session totals the certified risk-honest metrics recompute from (CONTEXT "Certified evidence";
* kestrel-h314 D2). Un-dropped from the rich engine Blotter — byte-stable and deterministic, NOT invented
* arithmetic. `floor` and `expected` are always present; `sampled` only on a seeded run (ABSENT — never
* `0` — when the channel is off). `settle_mark` is the verdict-only {@link SettleMarkVerdict}, absent on a
* pre-staleness-provenance bus.
*/
export interface Totals {
/** Definite-fill $ only — the conservative floor (`realized_floor_usd`). */
readonly floor: number;
/** E[$] weighted by `1 − survival` (`expected_usd`). */
readonly expected: number;
/** The seeded causal realization; ABSENT (not `0`) when the sampled channel is off. */
readonly sampled?: number;
readonly headline: HeadlineFlag;
/** The settle mark's verdict-only staleness projection, when the bus recorded settle-mark provenance. */
readonly settle_mark?: SettleMarkVerdict;
}
/**
* One support class's slice of a Blotter's expected-$ (CONTEXT "Certified evidence"; kestrel-h314 D2,
* ADR-0014). The certified `fill_claim` partitions `totals.expected` into the `calibrated` slice a grader
* banks and the `extrapolated` slice it refuses to bank. SIGNED sub-cells (`gain ≥ 0`, `loss ≤ 0`,
* `expected === gain + loss`) so a consumer that re-banks by SUBTRACTING the extrapolated slice removes
* only its `gain` — extrapolated LOSSES survive (the `bankableEv` doctrine) and can never be zeroed. The
* wire slice drops the engine record's per-slice `orders` count (not needed to recompute the metric).
*/
export interface FillClaim {
/** The support class this slice aggregates (CONTEXT "Support flag"). */
readonly support: FillSupport;
/** Σ expected-$ over this slice's orders (on the grader's 1e-8 grid). Equals `gain + loss`. */
readonly expected: number;
/** The slice's GAINS: Σ non-negative expected-$ (≥ 0). A subtracting consumer removes ONLY this. */
readonly gain: number;
/** The slice's LOSSES: Σ negative expected-$ (≤ 0) — KEPT for extrapolated cells (never forgiven). */
readonly loss: number;
}
/**
* Certified execution record of a Session run (CONTEXT "Blotter") — the WHAT
* HAPPENED, distinct from a Grade's judgment. Generic instruments only.
*
* CERTIFIED EVIDENCE (kestrel-h314, D2 — additive-optional): `totals` and `fill_claim` un-drop the
* risk-honest accounting the rich engine Blotter already carries, so the risk-honest metric family
* (bankable EV, realized floor, expected-$) is OUTSIDER-RECOMPUTABLE from the published artifact — not
* only inside the private engine. Their ABSENCE on a pre-widening Blotter reads as UNKNOWN and fails the
* risk-honest family CLOSED (never a silent zero — D6); the {@link ./grade.ts grade} Judge refuses to
* certify those metrics for a Blotter that lacks the evidence.
*/
export interface Blotter {
readonly sessionId: string;
readonly orders: readonly Order[];
readonly fills: readonly Fill[];
readonly positions: readonly Position[];
readonly settlement: Settlement;
/**
* The session totals — the certified risk-honest accounting (kestrel-h314 D2). ADDITIVE-OPTIONAL:
* ABSENT on a pre-widening Blotter, where its absence reads as UNKNOWN and fails the risk-honest
* metrics closed (D6) — never a silent zero.
*/
readonly totals?: Totals;
/**
* The session's expected-$ partitioned by fill support (kestrel-h314 D2): the `calibrated` slice a
* grader banks and the `extrapolated` slice it refuses to bank. When present, always two entries in the
* fixed order `[calibrated, extrapolated]` (deterministic, byte-stable). ADDITIVE-OPTIONAL: ABSENT on a
* pre-widening Blotter (UNKNOWN, fails the risk-honest family closed — D6). Part of the certified
* evidence alongside {@link totals} (the totals alone are not enough — CONTEXT "Certified evidence").
*/
readonly fill_claim?: readonly FillClaim[];
}
/* ───────────────────────────── grade ───────────────────────────────────── */
/**
* The judged verdict over one or more Blotters (CONTEXT "Certified grade"). The
* COMPUTED numbers — portable: a self-hosted Kestrel produces the same values;
* only the platform mints the trusted, signed receipt form ({@link CertifiedGrade}).
* Metric keys are open (a string→number map) so new dimensions need no shape bump.
*/
export interface GradeResult {
readonly subjectSessionId: string;
/** Open metric map, e.g. { "bankable_ev": ..., "fill_support": ... }. */
readonly metrics: Readonly<Record<string, number>>;
/** Optional discrete verdict labels, e.g. flags an honest-fill judge sets. */
readonly labels?: Readonly<Record<string, string>>;
}
/**
* The signed, replayable receipt form of a {@link GradeResult}. Pins the data and
* fill-model versions so anyone can replay to the identical numbers.
*/
export interface CertifiedGrade extends CertifiedReceipt {
readonly kind: "grade";
readonly result: GradeResult;
/** Opaque handle to the pinned data snapshot the grade ran over. */
readonly pinnedDataRef: string;
/** Version tag of the fill model used. */
readonly pinnedFillModelVersion: string;
/** Opaque handle to the judge's signature. */
readonly judgeSignatureRef: string;
readonly replayable: true;
}
/* ─────────────────────── M2–M4 receipt roots (additive) ────────────────────── */
/**
* Admission verdict for a purchased/delivered input (M2) — the fail-closed
* trichotomy on the wire (18-operation-promotion-protocols.md):
* - `known` a verified, complete delivery within scope — the ONLY admittable state.
* - `unknown` qualified, but delivery/schema/watermark/freshness/content/use could
* not be established — NOT presented as a known zero or neutral default.
* - `omitted` not acquired under any entitlement — NOT silently substituted.
* This is the Kestrel KNOWN | UNKNOWN | OMITTED doctrine (fail closed) on the wire.
*/
export type AdmissionState = "known" | "unknown" | "omitted";
/** Runtime tuple of every {@link AdmissionState}. Same both-ways closed-vocabulary guard as {@link SCOPES}. */
export const ADMISSION_STATES = ["known", "unknown", "omitted"] as const satisfies readonly AdmissionState[];
type _AdmissionStatesExhaustive<_Missing extends never = Exclude<AdmissionState, (typeof ADMISSION_STATES)[number]>> = true;
/**
* Receipt-gated admission of a purchased/delivered input into an Operation (M2).
* Verifies the qualification receipt (entitlement + accepted terms) AND the
* delivery receipt (what bytes/blocks/result were delivered), then binds the exact
* Field identity, content root, source watermark, and consumer use scope. The
* {@link AdmissionState} trichotomy fails closed: only a verified complete delivery
* within scope is `known`. Source: 18-operation-promotion-protocols.md
* `InputAdmissionReceipt`; the schema_version / receipt_id / canonical_digest /
* signature patent fields fold into the {@link CertifiedReceipt} base.
*/
export interface InputAdmissionReceipt extends CertifiedReceipt {
readonly kind: "input-admission";
/** Opaque root of the purchased-input manifest. */
readonly inputManifestRoot: string;
/** Opaque root of the qualification receipt proving entitlement + accepted terms. */
readonly qualificationReceiptRoot: string;
/** Opaque root of the delivery receipt proving what was delivered. */
readonly deliveryReceiptRoot: string;
/** Opaque identity of the admitted Field. */
readonly admittedFieldId: string;
/** Opaque handle to the admitted Field's declared type/schema. */
readonly admittedType: string;
/** Opaque content root of the admitted value/content. */
readonly admittedContentRoot: string;
/** Opaque source-watermark handle bounding freshness/provenance. */
readonly admittedSourceWatermark: string;
/** Opaque handle to the consumer use scope the admission authorizes. */
readonly admittedUseScope: string;
/** Opaque root of the consumer artifact the input is admitted into. */
readonly consumerArtifactRoot: string;
/** The fail-closed admission verdict (KNOWN | UNKNOWN | OMITTED). */
readonly resultingState: AdmissionState;
/** Stated reason when `unknown`/`omitted`; absent on `known`. */
readonly reason?: string;
/** Opaque coordinate of the entitlement consumed, when one was. */
readonly consumedEntitlementAt?: string;
}
/**
* Metered marginal technical value of an admitted input (M3), measured by a
* one-intervention baseline↔variant fork under a SHARED environment: the same
* runtime/binding roots and named Judge grade both branches, isolating the single
* admitted Field as the only change. The open `technicalEffectVector` carries the
* named machine measures (decision agreement, indeterminate-decision count, token
* consumption, latency, …) as a GradeResult-style string→number map, so a new
* metric needs no shape bump. Binds acquisition cost WITHOUT claiming that a
* favorable financial return proves technical validity. Source:
* 18-operation-promotion-protocols.md `ResourceValueReceipt`.
*/
export interface ResourceValueReceipt extends CertifiedReceipt {
readonly kind: "resource-value";
/** Opaque root of the purchased-input manifest under valuation. */
readonly purchasedInputManifestRoot: string;
/** Opaque root of the qualification receipt for the purchased input. */
readonly qualificationReceiptRoot: string;
/** Opaque root of the {@link InputAdmissionReceipt} that admitted the input. */
readonly inputAdmissionReceiptRoot: string;
/** Opaque root of the one-intervention branch descriptor. */
readonly branchDescriptorRoot: string;
/** Opaque root of the baseline branch's output. */
readonly baselineOutputRoot: string;
/** Opaque root of the variant branch's output. */
readonly variantOutputRoot: string;
/** Opaque root of the baseline branch's grade receipt. */
readonly baselineGradeReceiptRoot: string;
/** Opaque root of the variant branch's grade receipt. */
readonly variantGradeReceiptRoot: string;
/** Opaque root of the environment inputs held identical across both branches. */
readonly sharedEnvironmentInputRoot: string;
/** Opaque root of the conserved semantic artifact under test. */
readonly semanticArtifactRoot: string;
/** Opaque roots pinning the runtime + binding held constant across branches. */
readonly runtimeAndBindingRoots: readonly string[];
/** Opaque identity of the named Judge that graded both branches. */
readonly namedJudgeIdentity: string;
/** Opaque handle to the acquisition amount / compute-cost measure. */
readonly acquisitionMeasure: string;
/** Open named machine-measure map (GradeResult-style); a new metric needs no shape bump. */
readonly technicalEffectVector: Readonly<Record<string, number>>;
/** The computed baseline↔variant comparison result. */
readonly comparisonResult: string;
/** Opaque root of the confidence / repetition manifest, when present. */
readonly confidenceManifestRoot?: string;
}
/**
* A candidate promoted per the promotion policy (M3/M4): the conserved semantic
* artifact is carried unchanged while ONLY enumerated deployment dimensions change.
* The policy verifies engine/Judge identities, input/output roots, receipt classes,
* evidence freshness, semantic-root EQUALITY, thresholds, and approvals; it refuses
* a rebuilt artifact lacking a semantic-equivalence proof or a report produced under
* another Judge. Source: 18-operation-promotion-protocols.md `PromotionReceipt`.
*/
export interface PromotionReceipt extends CertifiedReceipt {
readonly kind: "promotion";
/** Opaque root of the promotion policy the promotion was verified against. */
readonly promotionPolicyRoot: string;
/** Opaque root of the evidence set the policy verified. */
readonly promotionEvidenceSetRoot: string;
/** Opaque root of the semantic artifact conserved across the promotion. */
readonly conservedSemanticArtifactRoot: string;
/** Opaque root of the prior deployment, when superseding one. */
readonly priorDeploymentRoot?: string;
/** Opaque root of the successor deployment. */
readonly successorDeploymentRoot: string;
/** The enumerated deployment dimensions permitted to change (mode/envelope/fence/…). */
readonly changedDeploymentDimensions: readonly string[];
/** Opaque handle to the supersession barrier, when one gates the promotion. */
readonly supersessionBarrierRef?: string;
/** Preconditions that must hold before live activation (e.g. human approval). */
readonly activationPreconditions: readonly string[];
/** The policy verification result. */
readonly verificationResult: string;
}
/**
* Live-activation state under the staged fencing protocol (M4). No live initiation
* occurs before `active`:
* - `preparing` a next fencing epoch is reserved.
* - `fence-confirmed` the epoch is installed and confirmed at the live Gate.
* - `active` the candidate actuates; the prior holder is fenced out.
* - `refused` activation was refused; the candidate stays non-actuating.
* A failed fence leaves the candidate non-actuating — fail closed.
*/
export type ActivationState = "preparing" | "fence-confirmed" | "active" | "refused";
/** Runtime tuple of every {@link ActivationState}, in staged-fencing order. Same both-ways closed-vocabulary guard as {@link SCOPES}. */
export const ACTIVATION_STATES = ["preparing", "fence-confirmed", "active", "refused"] as const satisfies readonly ActivationState[];
type _ActivationStatesExhaustive<_Missing extends never = Exclude<ActivationState, (typeof ACTIVATION_STATES)[number]>> = true;
/**
* A config/agent activated live (M4). The live execution module verifies the
* control-plane signature, capsule digest, target-executor binding, Authority Head,
* narrowing certificate, Envelope, approvals, expiry, and fencing epoch, then
* follows the staged fencing protocol via {@link ActivationState}. Fencing epochs
* are monotonic: the reserved epoch supersedes the prior. Possession of an old
* capsule/artifact/credential reference is insufficient. Source:
* 18-operation-promotion-protocols.md `LiveActivationReceipt`.
*/
export interface LiveActivationReceipt extends CertifiedReceipt {
readonly kind: "live-activation";
/** Opaque root of the Deployment Capsule being activated. */
readonly deploymentCapsuleRoot: string;
/** Opaque identity of the target executor bound to the capsule. */
readonly targetExecutorIdentity: string;
/** Opaque root of the ownership record for the activation. */
readonly ownershipRecordRoot: string;
/** The prior holder's fencing epoch (monotonic; superseded by the reserved one). */
readonly priorFencingEpoch: number;
/** The reserved successor fencing epoch. */
readonly reservedFencingEpoch: number;
/** Opaque root of the fence confirmation from the live Gate. */
readonly fenceConfirmationRoot: string;
/** The staged-fencing activation state (no live initiation before `active`). */
readonly activationState: ActivationState;
/** Opaque digest of the current Authority Head at activation. */
readonly activeAuthorityHeadDigest: string;
/** Event sequence at which the activation took effect. */
readonly activatedAtSequence: number;
}
/**
* Discriminated union of every concrete certified receipt. Switch on `.kind` for
* exhaustive server-side dispatch. v0.1 minted the M1 trial→conversion roots
* (authority-continuation, purchase, grade); v0.2 additively admits the M2–M4 roots
* (input-admission, resource-value, promotion, live-activation) — the
* CertifiedReceipt umbrella makes each a non-breaking addition.
*/
export type AnyReceipt =
| AuthorityContinuationReceipt
| PurchaseReceipt
| CertifiedGrade
| InputAdmissionReceipt
| ResourceValueReceipt
| PromotionReceipt
| LiveActivationReceipt;
/**
* Every concrete receipt discriminant as a runtime tuple — the closed `.kind`
* wire vocabulary of {@link AnyReceipt}. Driven by the union itself (`satisfies
* readonly AnyReceipt["kind"][]`) so extending {@link AnyReceipt} with a new
* receipt root (M2–M4) forces this tuple to grow in lockstep. Same both-ways
* closed-vocabulary guard as {@link SCOPES}: a `.kind` added to (or removed from)
* the union without editing this tuple breaks the build. Fail-closed, no drift.
*/
export const RECEIPT_KINDS = [
"authority-continuation",
"purchase",
"grade",
"input-admission",
"resource-value",
"promotion",
"live-activation",
] as const satisfies readonly AnyReceipt["kind"][];
type _ReceiptKindsExhaustive<_Missing extends never = Exclude<AnyReceipt["kind"], (typeof RECEIPT_KINDS)[number]>> = true;
/* ─────────────────────────── arm refusals (OSS-ADR-0045) ─────────────────────────── */
/**
* The closed, wire-stable vocabulary of reasons the plan engine REFUSES to arm a
* statement (OSS-ADR-0045). A refusal is DATA, never a matched message string: a
* consumer routes on `code`, and the human-readable `message`/`repair` are DISPLAY
* only (a wording change is never a breaking change). Extensible — add a code here
* and to {@link ARM_REFUSAL_CODES} in lockstep; consumers must not exhaustively
* switch without a default. Two codes today:
* - `unknown-series` — a trigger names a series the FROZEN phonebook does not know:
* a case-variant of a known market fact (`VWAP` for `vwap`, `VELOCITY` for the
* windowed `velocity`), refused with a did-you-mean {@link ArmRefusal.repair}. The
* offending statement is refused AS DATA; its healthy siblings still arm. This
* reverts the 0.4.5 case-insensitive forgiveness AND the silent runtime de-arm (a
* plan that would have armed-then-never-fired): an unknown series is a LOUD,
* repairable refusal at arm, never a silent default (RUNTIME §8).
* - `plan-name-in-use` — a revision re-declares a plan NAME still owned by a LIVE
* (managing/inventory-holding) prior record. This one REMAINS a throw (an
* `ArmError`, whole-document): arming would clobber the by-name ledger and garble
* the merged lifecycle — names are lineage; author a new name.
* - `manages-nothing` — a plan carries a covered sell (`EXIT`/`TP`, both of which can
* only ever sell inventory the plan HOLDS) but NO way to ever hold inventory: no
* `DO`/`ALSO`/`RELOAD` entry to acquire, no `ARM … foreach|any held leg` to adopt a
* superseded plan's held leg, and no `foreach|any held leg` on the cover itself. Its
* exit can never cover, so the plan would arm and sit inert forever — the silent
* one-way door r866 fixed, one dropped keyword away (an EXIT-only revision that forgot
* the adoption binding). Refused AS DATA at arm — the offending statement never
* registers, its healthy siblings still arm — so `parse --arm`, `/validate`, and a
* real run all fail closed identically (kestrel-b4wx/kestrel-hcnj, RUNTIME §4/§8).
* - `opening-short-uncovered` — a MULTI-LEG opening structure: an ENTRY (`DO`/`ALSO`) SELL
* leg CO-EXISTS with an opening BUY leg on a plan that adopts NO inventory (no `ARM …
* foreach|any held leg`, no `EXIT`/`TP … foreach|any held leg`). An entry sell is covered
* ONLY by inventory the plan HOLDS at fire — and at the one entry fire nothing has filled
* yet (a same-fire entry BUY is still resting) and cross-strike structural coverage (a
* debit spread's long leg covering its short) needs ATOMIC combo execution the engine does
* not yet do (the AST `atomic` keyword is reserved-but-refused). So such a sell is DEAD ON
* ARRIVAL: never-naked refuses it at every fire while the plan's BUY leg fires ALONE,
* silently degrading a debit spread to a naked long whose proof is BYTE-IDENTICAL to the
* single long leg (kestrel-53gw). Refused AS DATA at arm — LOUD, never a silent per-leg
* drop — until atomic multi-leg structures land (kestrel-psn5). Scoped to that misleading-
* proof shape: a PURE naked opening sell (no opening buy) is NOT caught here — its proof is
* order_count=0 (distinguishable) and it keeps its fire-time never-naked diagnostic (ADR-0017).
*/
export type ArmRefusalCode = "unknown-series" | "plan-name-in-use" | "manages-nothing" | "opening-short-uncovered";
/**
* Runtime tuple of every {@link ArmRefusalCode} — the closed wire vocabulary. Same
* both-ways closed-vocabulary guard as {@link SCOPES}: a code added to (or removed
* from) the union without editing this tuple breaks the build, so the runtime
* vocabulary can never silently drift from the type.
*/
export const ARM_REFUSAL_CODES = ["unknown-series", "plan-name-in-use", "manages-nothing", "opening-short-uncovered"] as const satisfies readonly ArmRefusalCode[];
/** Reverse-exhaustiveness proof: any `ArmRefusalCode` omitted from {@link ARM_REFUSAL_CODES} makes `_Missing`'s default non-`never`, which violates its `never` constraint and fails to compile. */
type _ArmRefusalCodesExhaustive<_Missing extends never = Exclude<ArmRefusalCode, (typeof ARM_REFUSAL_CODES)[number]>> = true;
/**
* One coded arm-time refusal (OSS-ADR-0045). `statement` is the offending plan's
* NAME; `code` is the wire-stable reason ({@link ArmRefusalCode}); `message` is the
* human-readable diagnostic (DISPLAY only — NEVER matched as an interface); `repair`,
* when present, is a concrete did-you-mean fix (e.g. the canonical lowercase series
* name a case-variant meant).
*/
export interface ArmRefusal {
readonly statement: string;
readonly code: ArmRefusalCode;
readonly message: string;
readonly repair?: string;
}
/**
* Every arm-time NOTICE code (kestrel-hk9u). A notice is NON-FATAL: unlike an
* {@link ArmRefusal}, the statement still arms (it is a legal, integrity-clean
* document) — the notice reports a condition that will keep the plan from ever
* FIRING on the arming context, so an author is not silently handed a plan that
* no-ops. Keep this union and {@link ARM_NOTICE_CODES} in lockstep. One code today:
* - `regime-unsatisfiable` — the plan carries a `regime {scope: value}` gate whose
* scope no REGIME writer on the arming context's tape will ever write, so the plan
* stays `authored (blocked)` for the whole session and never fires. This is the
* silent-gate the docs mistaught as an opaque author tag (the plan parses, arms,
* and then no-ops on every regime-less tape); the notice makes the dead-on-arrival
* verdict LEGIBLE at arm, identically across `parse --arm`, the hosted /validate
* route, and a real batch run (RUNTIME §8).
* - `adoption-bound-nothing` — the plan carries a valid `ARM … foreach|any held leg`
* adoption binding, but at arm it resolved to ZERO held legs (no superseded,
* inventory-holding sibling to adopt from). It arms and expires without ever covering,
* so its exit protection is inert — indistinguishable on the run report from a working
* adoption until this notice names it (kestrel-c25w; the run-tier sibling of the b4wx
* parse-tier manages-nothing refusal).
*/
export type ArmNoticeCode = "regime-unsatisfiable" | "adoption-bound-nothing";
/**
* Runtime tuple of every {@link ArmNoticeCode} — the closed wire vocabulary, guarded
* both ways exactly like {@link ARM_REFUSAL_CODES}: adding a code to (or removing one
* from) the union without editing this tuple breaks the build.
*/
export const ARM_NOTICE_CODES = ["regime-unsatisfiable", "adoption-bound-nothing"] as const satisfies readonly ArmNoticeCode[];
/** Reverse-exhaustiveness proof: any `ArmNoticeCode` omitted from {@link ARM_NOTICE_CODES} makes `_Missing`'s default non-`never`, which violates its `never` constraint and fails to compile. */
type _ArmNoticeCodesExhaustive<_Missing extends never = Exclude<ArmNoticeCode, (typeof ARM_NOTICE_CODES)[number]>> = true;
/**
* One coded arm-time NOTICE (kestrel-hk9u). `statement` is the plan's NAME; `code` is
* the wire-stable reason ({@link ArmNoticeCode}); `message` is the human-readable
* diagnostic (DISPLAY only — NEVER matched as an interface). A notice never sets the
* plan un-armed: it is surfaced ALONGSIDE a successful arm.
*/
export interface ArmNotice {
readonly statement: string;
readonly code: ArmNoticeCode;
readonly message: string;
}
/**
* The result of arming a document (OSS-ADR-0045): the NAMES of the plans that armed,
* the coded {@link ArmRefusal}s for the statements that did not — nothing healthy
* is aborted by a bad sibling, and nothing is silently dropped — and the non-fatal
* {@link ArmNotice}s for plans that armed but can never FIRE on this tape
* (kestrel-hk9u). A refusal that MUST halt the WHOLE document (a live-name collision,
* `plan-name-in-use`) is thrown as an `ArmError` carrying its `.code` instead of being
* returned here. `notices` is additive and optional — a report without regime notices
* omits it, so existing consumers are unaffected.
*/
export interface ArmReport {
readonly armed: readonly string[];
readonly refusals: readonly ArmRefusal[];
readonly notices?: readonly ArmNotice[];
}
/* ─────────────────────────── v0.2 additive surface ─────────────────────────── */
/**
* The v0.2 additions, re-exported so the published `kestrel.markets/protocol`
* entrypoint carries the whole contract. Both modules are intra-protocol and
* dependency-free (asserted by tests/protocol.boundary.test.ts):
* - ./catalog.ts — the content-addressed {@link import("./catalog.ts").CatalogEntry}.
* - ./session.ts — the incremental Session transcript + Operation/Session
* identity split + the idempotency/fail-closed reconciler.
*/
export * from "./catalog.ts";
export * from "./session.ts";
/* ─────────────────────────── v0.3 additive surface (oversight) ─────────────────────────── */
/**
* The v0.3 addition: the OVERSIGHT contract — the cross-repo seam between the CLI
* (`ascii`/Ink) and Web (`html`) Renderings of the human PM/Pod oversight surface
* (ADR-0035). Re-exported so the published `kestrel.markets/protocol` entrypoint
* carries the whole contract. Intra-protocol and dependency-free (asserted by
* tests/protocol.boundary.test.ts): the acting Kernel is a STRUCTURAL MIRROR of
* `src/frame/types.ts`, never an import. PURELY ADDITIVE — 0.2 → 0.3 touches no
* existing shape (same rule catalog.ts and session.ts landed under).
* - ./oversight.ts — Caller / OversightIdentity, the acting Kernel as a protocol
* shape, the Book/Pod org, OversightFrame, the turn-stream, typed owner acts,
* the human-message channel, input resolution, the closed OversightEvent enum,
* and the OversightBackend seam.
*/
export * from "./oversight.ts";
/* ─────────────────────────── offer terms (PLAT-ADR-0028 A1) ────────