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.
178 lines (165 loc) • 10.3 kB
text/typescript
/**
* # sdk/types — the ONE typed Kestrel SDK contract (kestrel-djm.5)
*
* The transport-neutral surface a caller writes ONCE and runs LOCAL or REMOTE. Every method returns a
* djm.2 PROTOCOL object (never a face-local success/failure state): catalog discovery yields
* {@link CatalogEntry}[]; validation yields the client's {@link ValidateOutcome}; a Session interaction
* yields the djm.4 {@link Delivery} / {@link SessionResponse} / {@link SessionTranscript}; finalize yields a
* protocol {@link Blotter} + tip + conformance root + artifact refs; grade yields the portable
* {@link GradeOutcome}. The 402/Offer boundary is DATA on {@link Gated} (the client's exact shape), and an
* unknown/off-contract subject is a TYPED refusal (fail-closed) — the SDK speaks the protocol, it does not
* invent a vocabulary.
*
* ZERO-WEIGHT: this module carries NO value import of the local runtime — every djm.2/djm.4 shape is a
* TYPE-ONLY import (erased at build), so `src/sdk/types.ts`, `src/sdk/facade.ts`, and the HTTP transport
* (`src/sdk/remote.ts`) that consume it stay light (the djm.3 light/heavy seam, pinned by
* tests/package.boundary.test.ts). {@link Gated}/{@link GradeOutcome}/{@link ValidateOutcome} are RE-EXPORTED
* from the 109 client — one definition, never a fork.
*
* ── DESIGN FORK LOGGED (hard rules forbid .beads writes — recorded here like djm.4's 7dv.4 note) ──
* (1) ONE client interface, TRANSPORT via factory. `createSdk(transport)` (facade.ts) wires a low-level
* {@link Transport} seam into the {@link KestrelSdk} facade; `localTransport()` (local.ts, heavy) binds
* the djm.4 controller + djm.8 catalog + engine grade in-process; `remoteTransport({ baseUrl, fetch })`
* (remote.ts, light) binds the 109 client over the 5rb wire. Minimal shape consistent with djm.4's
* `openSession(spec)` factory and 109's `new KestrelClient(opts)` — no third contract.
* (2) PROTOCOL OBJECTS, no face-local states (this file's whole point).
* (3) SdkFinalized ADDS `conformanceRoot` (= `blotter.session.bus.sha256` = the catalog's pinned root) and
* `artifacts` to djm.4's `SessionFinalized`, and narrows `blotter` to the djm.2 PROTOCOL {@link Blotter}
* (the generic public view) — the SESSION's rich engine Blotter stays on the local path. Minimal shape
* consistent with protocol/session.ts's `FinalizeMessage` (`tipHash` + `artifacts`).
*/
import type { CatalogEntry, CatalogListing, CatalogPage } from "../protocol/catalog.ts";
import type { Blotter, EventCursor } from "../protocol/index.ts";
import type { SessionId } from "../protocol/session.ts";
import type {
AuthoredResponse,
BoundResponse,
Delivery,
SessionResponse,
SessionTranscript,
} from "../session/controller-types.ts";
// The 402/Offer + verdict + validation shapes the SDK speaks — RE-EXPORTED from the 109 client so both
// transports return the SAME types (never forked). These are type-only edges (erased): they add no weight.
export type { Gated, GradeOutcome, ValidateOutcome } from "../client/index.ts";
// The djm.4 Session shapes the SDK REUSES verbatim on the incremental surface (also type-only).
export type { Delivery, SessionResponse, SessionTranscript, AuthoredResponse, BoundResponse } from "../session/controller-types.ts";
import type { Gated, GradeOutcome, ValidateOutcome } from "../client/index.ts";
/**
* WHAT to run: a content-addressed CATALOG subject (the djm.8 pinned entry, resolved by the LOCAL loader or
* the server's dataset artifact) or a RAW dataset (a plan `source` over a `dataset` artifact — the paid
* boundary rides here). A discriminated union so an unknown subject is a typed refusal, never a silent load.
*/
export type SessionRef =
| { readonly kind: "catalog"; readonly id: string }
| { readonly kind: "dataset"; readonly source: string; readonly dataset: string };
/**
* The sealed Session (djm.5 finalize): the protocol {@link Blotter} + the `tipHash` (tail of the pentad
* chain) + the `conformanceRoot` (= the graded-bus sha256 = `SimRunId`, the catalog-pinned digest) + the
* content-addressed `artifacts`. Narrower than djm.4's `SessionFinalized` (which carries the rich engine
* Blotter + captured turns): the SDK surface is the PROTOCOL view.
*/
export interface SdkFinalized {
readonly blotter: Blotter;
readonly sessionId: SessionId;
readonly tipHash: string;
/** The graded-bus sha256 (`blotter.session.bus.sha256` = `SimRunId`); the catalog's pinned root. */
readonly conformanceRoot: string;
/** Content-addressed handles to the delivered Blotter/Grade artifacts (djm.2 FinalizeMessage.artifacts). */
readonly artifacts: readonly string[];
}
/**
* The incremental Session surface (djm.4 verbs, transport-neutral): `start` → `advance`/`revise`/`submit`
* → `resume` → `finalize`. Every verb returns a djm.2/djm.4 protocol object. Identical shape on LOCAL (wraps
* the djm.4 controller) and HTTP (replays the server-computed, catalog-pinned transcript).
*/
export interface KestrelSession {
/** The deterministic Session identity (= genesis hash). Available immediately. */
readonly sessionId: SessionId;
/** START: deliver the date-blind OPEN Frame (ordinal OPEN_ORDINAL). */
start(): Promise<Delivery>;
/** AUTHORED-OR-STAND-DOWN: commit the pending slot, deliver the next eligible Wake (`next: null` at settle). */
advance(response: AuthoredResponse): Promise<SessionResponse>;
/** REVISION: author a superseding document at the current slot (behaviourally identical to advance). */
revise(response: AuthoredResponse): Promise<SessionResponse>;
/** EXPLICIT-BINDING turn: reconcile a fully-bound response against the recorded slot (idempotent/typed-refusal). */
submit(response: BoundResponse): Promise<SessionResponse>;
/** RESUME: re-derive the committed pentad-chained transcript (nothing to restore). */
resume(after?: EventCursor): Promise<SessionTranscript>;
/** FINALIZE: seal the chain → protocol Blotter + tip + conformance root + artifacts. */
finalize(): Promise<SdkFinalized>;
}
/** A grade over one or more Blotter artifacts (a Blotter's `sessionId` is its artifact id). */
export interface GradeRequest {
readonly blotters: readonly string[];
}
/** A resolved artifact reference: its `ref`, its `kind`, and the resolved protocol payload. */
export interface ArtifactResult {
readonly ref: string;
readonly kind: string;
readonly value: unknown;
}
/** Address a durable, resumable control-plane Operation (ADR-0004) for {@link KestrelSdk.resumeOperation}. */
export interface OperationRef {
readonly operationId: string;
/** Resume strictly after this opaque cursor (absent ⇒ from the beginning). */
readonly after?: EventCursor;
}
/** The re-derived tail of an Operation: the terminal cursor + the accumulated protocol payload. */
export interface OperationResumption {
readonly operationId: string;
readonly cursor: EventCursor | null;
readonly payload: Readonly<Record<string, unknown>>;
}
/**
* The ONE typed Kestrel client. Covers catalog discovery, validation, incremental Session interaction,
* Grade, artifacts, and Operation resume. Transport-agnostic — the SAME object shape whether it wraps the
* LOCAL controller or the HTTP client. Its `transportKind` / `version` / `transportVersion` are exposed for
* experimental evidence (djm.5 AC5).
*/
export interface KestrelSdk {
/** Which transport backs this client. */
readonly transportKind: Transport["kind"];
/** The SDK contract version (protocol-anchored). */
readonly version: string;
/** The bound transport's version tag. */
readonly transportVersion: string;
/** Catalog discovery — the canonical {@link CatalogPage} (kestrel-adge): the byte-identical
* {@link CatalogListing}[] menu (kestrel-dnxq) PLUS the optional ex-ante {@link CatalogPricing} block.
* Returning the WRAPPED page (not a bare array) makes this return type match the served `{ entries,
* pricing }` runtime shape — `pricing` is surfaced when the producer serves one, else absent. The listing
* CONTENTS are transport-specific (offline sample vs managed catalog); the reproducibility-complete
* {@link CatalogEntry} surface resolves at `openSession`/grade, not here. */
catalog(): Promise<CatalogPage>;
/** Pure validation → `{ ok, diagnostics }` (no face-local verdict). */
validate(document: string): Promise<ValidateOutcome>;
/**
* Open an incremental Session over a subject; the 402/Offer boundary is DATA even here. An optional
* `document` supplies the customer strategy (Kestrel plan text) the managed backend's author-no-strategy
* fence requires (ADR-0012, kestrel-88u2) — the same role the sim verb's `--plans` plays. On the HTTP
* transport a catalog subject WITH a document sends it as the `POST /sim` source; a bare openSession keeps
* today's behavior (the platform's fence answers — now a legible refusal). The LOCAL transport already
* carries the catalog recipe's strategy, so it ignores the argument.
*/
openSession(subject: SessionRef, document?: string): Promise<Gated<KestrelSession>>;
/** Grade one or more Blotter artifacts → the portable {@link GradeOutcome} (may gate). */
grade(request: GradeRequest): Promise<Gated<GradeOutcome>>;
/** Resolve a content-addressed artifact reference. */
artifact(ref: string): Promise<ArtifactResult>;
/** Resume a durable Operation from its cursor. */
resumeOperation(ref: OperationRef): Promise<OperationResumption>;
}
/**
* The low-level TRANSPORT seam a concrete transport implements and `createSdk` wraps. Identical method set
* to {@link KestrelSdk} minus the cross-cutting version fields — the facade adds those. LOCAL and HTTP
* transports are behaviourally indistinguishable at this seam (same protocol objects, same digests).
*/
export interface Transport {
readonly kind: "local" | "http";
readonly version: string;
catalog(): Promise<CatalogPage>;
validate(document: string): Promise<ValidateOutcome>;
openSession(subject: SessionRef, document?: string): Promise<Gated<KestrelSession>>;
grade(request: GradeRequest): Promise<Gated<GradeOutcome>>;
artifact(ref: string): Promise<ArtifactResult>;
resumeOperation(ref: OperationRef): Promise<OperationResumption>;
}