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.

705 lines (676 loc) 38.2 kB
/** * # capability catalog — the four-axis, receipt-backed source of truth (kestrel-gsh.2) * * A public claim a reader cannot verify is a lie the repo tells (AGENTS.md). This module makes * every public capability claim *typed, receipt-backed, and independently graded on four axes* so * the generated status page (`docs/public/status.md`) can never overstate what Kestrel does. * * The load-bearing idea is **axis independence**: a feature can *parse* (SYNTAX) yet have no * *executable* behavior (RUNTIME), no accumulated *graded* evidence (EVIDENCE), and be reachable * on only some *entry surfaces* (ACCESS). A single boolean `supported` flag would collapse those * four distinct facts into one flattering lie; the schema below forbids that by making each axis a * separate, enumerated field with its own vocabulary: * * - **SYNTAX** — does `src/lang` (parser + printer) accept it? `supported | proposed | rejected`. * - **RUNTIME** — is the executable behavior implemented? `implemented | partial | none`. * - **EVIDENCE** — accumulated *graded trading* evidence and its TIER (never unit tests — those are * receipts): `none | mechanical | certified`. The certified honest-tier cohort is **0** * (kestrel-pqv.1); a mechanical/practice agent-day may never be labeled certified. * - **ACCESS** — which entry surfaces reach it: text DSL, TS SDK, CLI, MCP, adapters. * * Every capability that is claimed in the public docs (`public: true`) MUST cite at least one * SOURCE receipt (a `src/` file) *and* one TEST receipt (a `tests/` file). An unbacked public * claim is a structural error {@link validateCatalog} rejects (fail-closed). Every public example * fence (extracted by the gsh.1 checker, `./fences.ts`) must resolve to at least one capability, * and every capability's cited example must be a real fence — orphans and stale ids fail CI. * * This module is pure data + pure validation over `src/lang` and the on-disk receipt files; it is * NOT on the runtime path and mutates nothing. */ import { existsSync } from "node:fs"; import { join } from "node:path"; import type { FenceRole, ManagedFence } from "./fences.ts"; import { REPO_ROOT, collectManagedFences } from "./fences.ts"; // ── Axis vocabularies (four independent axes — never one boolean) ─────────────── /** Does the grammar accept it? */ export type SyntaxStatus = "supported" | "proposed" | "rejected"; /** Is the executable behavior implemented? */ export type RuntimeStatus = "implemented" | "partial" | "none"; /** The tier of accumulated *graded trading* evidence (never unit tests). */ export type EvidenceTier = "none" | "mechanical" | "certified"; /** The entry surfaces a capability can be reached through. */ export type AccessSurface = "text" | "sdk" | "cli" | "mcp" | "adapter"; /** Per-surface availability. */ export type AccessLevel = "available" | "partial" | "planned"; /** * The **advertised** access faces — the surfaces the public docs (README + ARCHITECTURE + ADR-0004's * "four equal faces": text DSL · TS SDK · CLI · MCP) and the status page's Access column claim Kestrel * is reachable through. The fail-closed invariant in {@link validateCatalog} requires every one of * these to be exposed by ≥1 *public* capability: advertising a face that NO capability grades on its * Access axis is an over-claim the honesty doctrine forbids (a reader is told a surface reaches Kestrel * when nothing does). * * `adapter` is deliberately EXCLUDED. The `./adapters` seam is NOT yet a public package export * (kestrel-rki is still open), so it is a private/internal surface, not an advertised face — grading * any capability `adapter` today would itself be the over-claim this invariant exists to prevent. When * rki ships `./adapters` as a public export, add `"adapter"` here and grade the capabilities it reaches. */ export const ADVERTISED_SURFACES: readonly AccessSurface[] = ["text", "sdk", "cli", "mcp"]; /** * The certified honest-tier cohort size, repo-wide. Per kestrel-pqv.1 the agent-day-1 writeup is * PRACTICE tier (its author boundary leaked calendar identity; the contamination fence is * conjunctive), so **no result is certified** and this is 0. The validator forbids any capability from * labeling evidence `certified` while this is 0 — a mechanical agent-day may never be dressed as certified. */ export const CERTIFIED_COHORT = 0; /** The evidence writeup this catalog's `mechanical` tier points at (labeled PRACTICE tier there). */ export const EVIDENCE_SOURCE = "docs/results/agent-day-1.md"; /** A source (`src/`) + test (`tests/`) receipt pair backing a public claim. */ export interface Receipts { /** `src/` files that implement the capability (the SOURCE receipt). */ readonly source: readonly string[]; /** `tests/` files that hold it to its claim (the TEST receipt). */ readonly test: readonly string[]; } /** One catalogued capability, graded independently on all four axes. */ export interface Capability { readonly id: string; readonly title: string; readonly summary: string; /** True iff this capability is claimed in the public docs (README + docs/public). */ readonly public: boolean; readonly syntax: { readonly status: SyntaxStatus; readonly note?: string }; readonly runtime: { readonly status: RuntimeStatus; readonly note?: string }; readonly evidence: { readonly tier: EvidenceTier; readonly note?: string }; readonly access: { readonly surfaces: Partial<Record<AccessSurface, AccessLevel>>; readonly note?: string }; readonly receipts: Receipts; /** Fence ids (from the gsh.1 extractor) that demonstrate this capability in the public docs. */ readonly examples: readonly string[]; } // ── The catalog ───────────────────────────────────────────────────────────────── /** * The capability catalog. Ordering here is irrelevant — the status generator sorts by `id` so the * page is byte-stable regardless of edit order. * * ACCESS-axis / MCP grading (kestrel-82c.6): a capability earns `mcp` ONLY where a real djm.7 MCP * tool/route exercises it — `kestrel.validate` (parse + the fail-closed refusals), the incremental * `kestrel.session.*` lifecycle (arm + runtime: plans, triggers, pricing, TP/EXIT/RELOAD/INVALIDATE, * USING, the WAKE informs, the OPEN Frame from `session.start`), and `kestrel.grade`. Capabilities are * left OFF `mcp` where NO tool exercises them, on purpose, so the page never over-claims the face: * • `roundtrip` / `comments` — the MCP `validate` tool returns only `{ ok, diagnostics }` (never the * canonical printed bytes), and the session echoes the AUTHORED bytes, not `print(parse(·))`; so * byte-stable round-trip / comment preservation is not OBSERVABLE over MCP. * • `grade-counterfactuals` — `kestrel.grade` takes Blotter ids only, not a GRADE document's VS/BY * clauses, so the counterfactual is not exercised by the MCP grade route. * • `pod-book` — the session's authored document is a View/Wake/Plan; no MCP tool authors an org tree. * • `module-imports` — runtime is `none` (parse/print only); nothing to exercise over MCP. * ADAPTER is claimed by NO capability: the `./adapters` seam is not yet a public package export * (kestrel-rki open), so it is a private surface — see {@link ADVERTISED_SURFACES}. */ export const CAPABILITIES: readonly Capability[] = [ { id: "plan", title: "Plan statement", summary: "A standing, bounded-risk contingent program: trigger → actions → bracket → invalidation → TTL, " + "fired by the runtime at machine speed while the author is informed in parallel.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "PlanEngine drives authored → armed → fired → managing → done with fire-then-inform (RUNTIME §5).", }, evidence: { tier: "mechanical", note: "Exercised end-to-end in the agent-day-1 practice-tier run." }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: authored + fired/managed through the incremental Session lifecycle (kestrel.session.open → advance/revise a Plan document → finalize).", }, receipts: { source: ["src/lang/parse.ts", "src/lang/ast.ts", "src/engine/plans.ts"], test: ["tests/lang.parse.test.ts", "tests/engine.plans.test.ts", "tests/docs.fences.test.ts"], }, examples: ["core-plan-fade", "doc-breakout-ladder", "doc-buy-the-flush", "doc-clause-skeleton", "grammar-skeleton", "overview-plan", "plan-fade-flush", "plan-headline-chase", "plan-open-drive"], }, { id: "trigger-algebra", title: "Trigger algebra (WHEN)", summary: "The shared WHEN expression language — named series × predicates (compare, cross, held, within, " + "velocity) × AND/OR/NOT — used by Wake, Plan, and Grade alike.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "Tri-state, causal evaluation with per-trigger edge memory over the series provider (RUNTIME §3).", }, evidence: { tier: "mechanical", note: "Triggers gated the plans in the agent-day-1 practice-tier run." }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: WHEN parses via kestrel.validate and the runtime evaluates it across the kestrel.session lifecycle.", }, receipts: { source: ["src/series/trigger.ts", "src/lang/parse.ts"], test: ["tests/series.test.ts", "tests/engine.plans.test.ts"], }, examples: ["core-plan-fade", "core-wake", "doc-breakout-ladder", "doc-buy-the-flush", "overview-plan", "plan-fade-flush", "plan-headline-chase", "plan-open-drive", "wake-fast-move"], }, { id: "price-value-anchors", title: "VALUE price anchors", summary: "fair · intrinsic · basis — model worth, settle floor, and your own cost; a quote is never a value.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "fair = ExecutionFair with receipts; a silent mid is forbidden and every fallback is annotated (RUNTIME §4).", }, evidence: { tier: "mechanical", note: "Priced the fills graded in the agent-day-1 practice-tier run." }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: the runtime resolves VALUE anchors while pricing fills across the kestrel.session lifecycle (kestrel.validate parses them).", }, receipts: { source: ["src/engine/pricing.ts", "src/fair/index.ts"], test: ["tests/engine.pricing.test.ts", "tests/fair.test.ts"], }, examples: ["core-plan-fade", "doc-breakout-ladder", "doc-buy-the-flush", "overview-plan", "plan-fade-flush", "plan-headline-chase", "plan-open-drive"], }, { id: "price-book-anchors", title: "BOOK price anchors", summary: "bid · ask · mid · last · join · improve — queue position, not value; a dark side fails the line closed.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "Read off the leg quote; a needed dark side makes the whole line unresolvable (fail-closed, RUNTIME §4).", }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: BOOK anchors parse via kestrel.validate and price orders the runtime places across the kestrel.session lifecycle.", }, receipts: { source: ["src/engine/pricing.ts"], test: ["tests/engine.pricing.test.ts"], }, examples: ["core-plan-fade", "doc-breakout-ladder", "doc-buy-the-flush", "overview-plan", "plan-fade-flush", "plan-headline-chase", "plan-open-drive"], }, { id: "price-combinators", title: "Price combinators", summary: "Combinators on any anchor: interpolation lean(a,b,x), guards min/max, offsets (fair-3c), tracking peg, " + "bounds cap (buys) / floor (sells ≥ intrinsic), and time escalation esc.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "BUY caps compose tightest, SELL floors compose highest and always include intrinsic; peg reprices on ≥1-tick drift (RUNTIME §4).", }, evidence: { tier: "mechanical", note: "peg/cap bounded the resting orders in the agent-day-1 practice-tier run." }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: combinators parse via kestrel.validate and bound the runtime's resting orders across the kestrel.session lifecycle.", }, receipts: { source: ["src/engine/pricing.ts"], test: ["tests/engine.pricing.test.ts"], }, examples: ["core-plan-fade", "doc-breakout-ladder", "doc-buy-the-flush", "overview-plan", "plan-fade-flush", "plan-headline-chase", "plan-open-drive"], }, { id: "plan-reload", title: "RELOAD ladder", summary: "Add rungs into the thesis on further confirmation — worse price = better entry for a fade; the ladder IS the thesis.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "Reload rungs re-checked against cumulative budget at every placement (RUNTIME §5)." }, evidence: { tier: "mechanical", note: "Reload rungs armed in the agent-day-1 practice-tier run." }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: RELOAD rungs are managed by the runtime during a kestrel.session lifecycle.", }, receipts: { source: ["src/engine/plans.ts"], test: ["tests/engine.plans.test.ts"], }, examples: ["doc-breakout-ladder", "plan-fade-flush", "plan-headline-chase"], }, { id: "plan-tp", title: "TP (take-profit tiers)", summary: "Take-profit tiers on the position — bank a fraction at a multiple, resting at a value anchor.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "TP maintenance runs in the manage phase of every fired plan (RUNTIME §5). " + "The tier threshold is evaluated on fair (the value anchor, kestrel-lic3): with an `@` the sell arms once fair crosses the threshold and rests at the `@` exec anchor; without one it rests at the target-derived price immediately and fills on market crossing.", }, evidence: { tier: "mechanical", note: "TP tiers rested in the agent-day-1 practice-tier run." }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: TP tiers are maintained by the runtime during a kestrel.session lifecycle.", }, receipts: { source: ["src/engine/plans.ts"], test: ["tests/engine.plans.test.ts"], }, examples: ["doc-breakout-ladder", "doc-buy-the-flush", "plan-fade-flush", "plan-headline-chase", "plan-open-drive"], }, { id: "plan-exit", title: "EXIT (active out)", summary: "An active out on an underlying trigger (never on option marks) — giveback means the thesis is dead, get out.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "EXIT (with esc/STAND retry ordering) runs in the manage phase (RUNTIME §5)." }, evidence: { tier: "mechanical", note: "EXIT clauses armed in the agent-day-1 practice-tier run." }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: EXIT is evaluated by the runtime during a kestrel.session lifecycle.", }, receipts: { source: ["src/engine/plans.ts"], test: ["tests/engine.plans.test.ts"], }, examples: ["doc-breakout-ladder", "plan-headline-chase", "plan-open-drive"], }, { id: "plan-invalidate", title: "INVALIDATE (thesis dead)", summary: "Thesis broken: stop adding, ride the bounded remainder — distinct from EXIT, which flattens.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "INVALIDATE halts acquisition while filled inventory persists (RUNTIME §5)." }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: INVALIDATE is enforced by the runtime during a kestrel.session lifecycle.", }, receipts: { source: ["src/engine/plans.ts"], test: ["tests/engine.plans.test.ts"], }, examples: ["core-plan-fade", "plan-fade-flush"], }, { id: "plan-arm", title: "ARM (inventory adoption — the exit escape hatch)", summary: "A replacement plan adopts a superseded plan's held leg with `ARM [basis <px>] foreach held leg`, so it can manage and covered-exit inventory it did not open itself — never naked, never inferred from a matching strike.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "At the supersede→arm handshake the binding transfers management authority for each genuinely-held leg from the acquisition-halted source to the bound replacement, then promotes a pure adopter (an ARM binding with no fireable entry) armed→managing so its EXIT/TP actually run (RUNTIME §5, kestrel-22j.14/r866).", }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: the ARM inventory handshake is applied by the runtime during a kestrel.session lifecycle (across a supersede).", }, receipts: { source: ["src/engine/plans.ts", "src/lang/parse.ts"], test: ["tests/plan.inventory.test.ts", "tests/session.day-adoption.test.ts", "tests/session.day-arm-footgun.test.ts"], }, examples: ["plan-acquire-open", "plan-manage-adopt"], }, { id: "using-defaults", title: "USING scoped defaults", summary: "Scoped signal/exec defaults for a document; any leg may override — composition over repetition.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "Defaults resolve into each leg at build/arm; per-leg overrides win." }, evidence: { tier: "mechanical", note: "USING scoped the plans in the agent-day-1 practice-tier run." }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: USING resolves into each leg at arm through the kestrel.session lifecycle (kestrel.validate parses it).", }, receipts: { source: ["src/lang/parse.ts", "src/engine/plans.ts"], test: ["tests/lang.parse.test.ts", "tests/engine.plans.test.ts"], }, examples: ["doc-breakout-ladder", "grammar-skeleton", "plan-headline-chase", "plan-open-drive"], }, { id: "comments", title: "Comments (# reasoning)", summary: "`#` to end of line carries the author's thinking so the WHY travels with the strategy. Comments are " + "PRESERVED byte-stably (ADR-0004 / ADR-0033): the lexer captures them as trivia, the printer re-emits them " + "verbatim in canonical position. No runtime semantics by design — reasoning, not behavior.", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "Comments carry no executable behavior, but they round-trip byte-identically through parse→print (own-line at the statement's indent; trailing after a single canonical space) — the authored 'why' is never lost (ADR-0033).", }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available" } }, receipts: { // The lexer captures `#` as leading/trailing/tail trivia (lex.ts), the parser threads it into the // AST comment sidecar (parse.ts), and the printer re-emits it byte-identically (print.ts). source: ["src/lang/lex.ts", "src/lang/parse.ts", "src/lang/print.ts"], // lang.comments.test.ts pins byte-stable round-trip over comment-bearing docs (leading, trailing, // stacked, indented, interstitial, comment-only, unicode) + idempotency + no comment-free regression; // docs.fences.test.ts round-trips the comment-bearing example fences from the public docs. test: ["tests/lang.comments.test.ts", "tests/docs.fences.test.ts"], }, examples: ["doc-buy-the-flush", "doc-comments-roundtrip", "plan-fade-flush", "plan-headline-chase"], }, { id: "roundtrip", title: "Byte-stable round-trip", summary: "print(parse(text)) is a byte-stable fixed point; the typed object model is canonical and text is a projection of it (ADR-0004).", public: true, syntax: { status: "supported" }, runtime: { status: "implemented", note: "print ∘ parse is idempotent and canonical; golden fixtures pin the contract." }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available" } }, receipts: { source: ["src/lang/print.ts", "src/lang/parse.ts"], test: ["tests/lang.golden.test.ts", "tests/lang.comments.test.ts", "tests/docs.fences.test.ts"], }, examples: ["doc-breakout-ladder", "doc-comments-roundtrip"], }, { id: "wake", title: "Wake statement", summary: "A standing subscription over the trigger algebra that spends attention (tokens, wakes), never risk; it delivers a View.", public: true, syntax: { status: "supported", note: "Concrete WAKE statements parse and round-trip (lang.parse; see the core-wake example); the grammar skeleton is proposed notation." }, runtime: { status: "partial", note: "The runtime emits WAKE informs for fired plans (fire-then-inform); a standalone WAKE subscription scheduler is not yet wired.", }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available", mcp: "available" }, note: "MCP: a WAKE document is authored via the kestrel.session lifecycle (advance/revise supersede); the runtime emits WAKE informs for fired plans across the session.", }, receipts: { source: ["src/lang/parse.ts", "src/lang/ast.ts"], test: ["tests/lang.parse.test.ts"], }, examples: ["core-wake", "grammar-skeleton", "wake-fast-move"], }, { id: "view", title: "View statement", summary: "The standing definition of what should be seen — which panes, at what token budget — materialized as a Frame.", public: true, syntax: { status: "supported", note: "Concrete VIEW statements parse and round-trip (lang.parse; see the frame-view example); the grammar skeleton is proposed notation." }, runtime: { status: "partial", note: "The Frame renderer (keyframe + delta) is implemented; wiring a VIEW statement's pane selection into it is not complete.", }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available", mcp: "available" }, note: "MCP: the OPEN Frame is delivered by kestrel.session.start (the typed SessionFrame struct verbatim — a Rendering of it is the caller's); a VIEW document is authorable across the session.", }, receipts: { source: ["src/lang/parse.ts", "src/frame/index.ts"], test: ["tests/lang.parse.test.ts", "tests/frame.test.ts"], }, examples: ["frame-view", "grammar-skeleton"], }, { id: "grade", title: "Grade statement", summary: "An imperative replay of authored statements over recorded sessions under a named, versioned fill model, reporting honest EV.", public: true, syntax: { status: "supported" }, runtime: { status: "partial", note: "The fill model and the support-guard contract (bankableEv refuses extrapolated E[$]) exist; the full typed replay grader lands with kestrel-a57.1.", }, evidence: { tier: "mechanical", note: "The agent-day-1 practice-tier run was graded mechanically under maker-fair-v1." }, access: { surfaces: { text: "available", sdk: "available", cli: "available", mcp: "available" }, note: "MCP: kestrel.grade grades finalized Blotter artifacts and returns the Gated<GradeOutcome> verbatim.", }, receipts: { source: ["src/lang/parse.ts", "src/grade/index.ts", "src/fill/model.ts"], test: ["tests/lang.parse.test.ts", "tests/fill.test.ts", "tests/docs.fences.test.ts"], }, examples: ["core-grade", "grade-headline-chase", "grade-open-drive", "grammar-skeleton"], }, { id: "grade-counterfactuals", title: "Grade counterfactuals", summary: "Counterfactuals as syntax — VS ungated (did the gate earn its keep?), VS null (any edge at all?) — and stratified BY dimensions.", public: true, syntax: { status: "supported" }, runtime: { status: "partial", note: "Counterfactual clauses parse; the frozen-armory (VS null) counterfactual ran mechanically in agent-day-1; full stratified grading lands with kestrel-a57.1.", }, evidence: { tier: "mechanical", note: "The frozen-armory VS-null counterfactual was computed in the agent-day-1 practice-tier run." }, access: { surfaces: { text: "available", sdk: "available" } }, receipts: { source: ["src/lang/parse.ts", "src/grade/index.ts"], test: ["tests/lang.parse.test.ts"], }, examples: ["core-grade", "grade-headline-chase", "grade-open-drive"], }, { id: "pod-book", title: "Pod / Book / Risk org", summary: "The recursive org tree — an allocating Pod (PM + envelope) over Books (the only place positions live); budgets nest, authority only narrows.", public: true, syntax: { status: "supported", note: "Concrete POD/BOOK/RISK statements parse and round-trip; the public example is proposed skeleton notation." }, runtime: { status: "implemented", note: "Book envelope arbitration and org-tree budget nesting are enforced by the engine; deeper Pod recursion is still narrow.", }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available", cli: "available" } }, receipts: { source: ["src/lang/parse.ts", "src/engine/plans.ts", "src/org/index.ts"], test: ["tests/lang.parse.test.ts", "tests/engine.plans.test.ts"], }, examples: ["grammar-skeleton"], }, { id: "module-imports", title: "Modules & IMPORT", summary: "Every document is a module; its named statements are importable by other documents (import what you reuse).", public: true, syntax: { status: "supported", note: "IMPORT / module documents parse and round-trip; the public example is proposed skeleton notation." }, runtime: { status: "none", note: "Cross-document import linking (resolving IMPORT to another document's exports) is not yet implemented; parse/print only.", }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available" } }, receipts: { source: ["src/lang/parse.ts", "src/lang/ast.ts"], test: ["tests/lang.parse.test.ts"], }, examples: ["grammar-skeleton"], }, { id: "fail-closed-exit-on-mark", title: "Fail closed: no EXIT on a mark", summary: "A quote is a health signal, never a value: an active EXIT may not condition on the mark (ADR-0005) — spelled as jargon (mid/bid/ask/last) or as the words a written playbook uses (mark/premium). The parser refuses all of them.", public: true, syntax: { status: "rejected", note: "The parser raises KestrelParseError (\"marks lie\") on an EXIT conditioned on a mark — mid/bid/ask/last and their plain-English synonyms mark/premium alike." }, runtime: { status: "implemented", note: "The refusal is enforced at parse time — the illegal statement never reaches the engine." }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available", mcp: "available" }, note: "MCP: kestrel.validate parses the document and returns the refusal as an ok:false ValidateOutcome — the illegal EXIT never arms.", }, receipts: { source: ["src/lang/parse.ts"], test: ["tests/docs.fences.test.ts", "tests/lang.parse.test.ts"], }, examples: ["doc-reject-exit-on-mark"], }, { id: "fail-closed-budget", title: "Fail closed: budget is a positive risk fraction", summary: "Bounded risk is a type: a budget must be a positive risk fraction so the position is always defined-risk. A non-positive budget is refused.", public: true, syntax: { status: "rejected", note: "The parser raises KestrelParseError (\"positive risk fraction\") on a non-positive budget." }, runtime: { status: "implemented", note: "The refusal is enforced at parse time — bounded risk holds before the engine ever arms." }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available", mcp: "available" }, note: "MCP: kestrel.validate returns the refusal as an ok:false ValidateOutcome — the non-positive budget never arms.", }, receipts: { source: ["src/lang/parse.ts"], test: ["tests/docs.fences.test.ts", "tests/lang.parse.test.ts"], }, examples: ["doc-reject-budget"], }, { id: "fail-closed-atomic", title: "Fail closed: atomic is reserved and refused", summary: "The atomic keyword is reserved and refused in v1 — a multi-leg ticket may not claim all-or-none fills the runtime cannot guarantee.", public: true, syntax: { status: "rejected", note: "The parser raises KestrelParseError (\"reserved and refused\") on the atomic keyword." }, runtime: { status: "implemented", note: "The refusal is enforced at parse time — no unguaranteeable all-or-none ticket reaches the engine." }, evidence: { tier: "none" }, access: { surfaces: { text: "available", sdk: "available", mcp: "available" }, note: "MCP: kestrel.validate returns the refusal as an ok:false ValidateOutcome — the reserved keyword never arms.", }, receipts: { source: ["src/lang/parse.ts"], test: ["tests/docs.fences.test.ts", "tests/lang.parse.test.ts"], }, examples: ["doc-reject-atomic"], }, ]; // ── Validation (fails closed; accumulates every offender) ─────────────────────── /** A structural problem with the catalog — surfaced by the checker as a hard error. */ export interface CatalogError { readonly capability: string; readonly message: string; } export interface CatalogValidation { readonly errors: readonly CatalogError[]; /** Managed fence ids referenced by no capability (orphan examples — always an error). */ readonly orphanExamples: readonly string[]; /** Capabilities that demonstrate no public example fence (flagged, reported on the page). */ readonly orphanCapabilities: readonly string[]; } const isSourcePath = (p: string): boolean => p.startsWith("src/"); const isTestPath = (p: string): boolean => p.startsWith("tests/"); const exists = (root: string, rel: string): boolean => existsSync(join(root, rel)); /** * Validate the catalog against the on-disk receipts and the extracted public fences. Accumulates * every offender (never throws) so the checker can report them all at once. The rules encode the * receipt contract and axis independence: * * - ids are unique; every axis carries a legal enum value. * - Every receipt path is well-rooted (`src/` for source, `tests/` for test) and exists on disk — * a stale receipt fails CI. * - Every `public` capability cites ≥1 source receipt AND ≥1 test receipt — a missing receipt fails. * - Every cited example is a real managed fence — a stale example id fails. * - Every managed fence resolves to ≥1 capability — an orphan example fails. * - Axis honesty: a capability that cites a `role=example` fence must claim SYNTAX `supported`; one * that cites a `role=reject` fence must claim SYNTAX `rejected` — an unsupported claim fails. * - Evidence honesty: no capability may label evidence `certified` while {@link CERTIFIED_COHORT} * is 0 — a mechanical agent-day may never be dressed as certified. */ export function validateCatalog( capabilities: readonly Capability[] = CAPABILITIES, managed: readonly ManagedFence[] = collectManagedFences().managed, root: string = REPO_ROOT, ): CatalogValidation { const errors: CatalogError[] = []; const push = (capability: string, message: string): void => void errors.push({ capability, message }); const managedById = new Map<string, ManagedFence>(managed.map((f) => [f.id, f])); const referenced = new Set<string>(); const seenIds = new Map<string, Capability>(); const SYNTAX: ReadonlySet<string> = new Set(["supported", "proposed", "rejected"]); const RUNTIME: ReadonlySet<string> = new Set(["implemented", "partial", "none"]); const EVIDENCE: ReadonlySet<string> = new Set(["none", "mechanical", "certified"]); const LEVELS: ReadonlySet<string> = new Set(["available", "partial", "planned"]); const orphanCapabilities: string[] = []; // Advertised-face coverage: which of the four advertised faces is exposed by ≥1 *public* capability. const exposedAdvertised = new Set<AccessSurface>(); for (const c of capabilities) { if (seenIds.has(c.id)) { push(c.id, `duplicate capability id "${c.id}".`); continue; } seenIds.set(c.id, c); // Axis values must be legal enums — each axis is its own fact. if (!SYNTAX.has(c.syntax.status)) push(c.id, `illegal syntax status "${c.syntax.status}".`); if (!RUNTIME.has(c.runtime.status)) push(c.id, `illegal runtime status "${c.runtime.status}".`); if (!EVIDENCE.has(c.evidence.tier)) push(c.id, `illegal evidence tier "${c.evidence.tier}".`); const surfaces = Object.entries(c.access.surfaces); if (surfaces.length === 0) push(c.id, `access declares no surfaces — a capability must be reachable somewhere (or none, explicitly).`); for (const [surface, level] of surfaces) { if (level === undefined || !LEVELS.has(level)) push(c.id, `illegal access level "${level}" for surface "${surface}".`); } // Only PUBLIC capabilities count toward the advertised-face coverage invariant (the page projects public ones). if (c.public) for (const s of Object.keys(c.access.surfaces) as AccessSurface[]) exposedAdvertised.add(s); // Evidence honesty: certified is impossible while the cohort is 0. if (c.evidence.tier === "certified" && CERTIFIED_COHORT === 0) { push(c.id, `evidence labeled "certified" but the certified cohort is 0 (kestrel-pqv.1) — a mechanical agent-day may never be labeled certified.`); } // Receipts: well-rooted, on-disk, and (for public claims) both kinds present. for (const p of c.receipts.source) { if (!isSourcePath(p)) push(c.id, `source receipt "${p}" must be a src/ path.`); else if (!exists(root, p)) push(c.id, `source receipt "${p}" does not exist on disk (stale receipt).`); } for (const p of c.receipts.test) { if (!isTestPath(p)) push(c.id, `test receipt "${p}" must be a tests/ path.`); else if (!exists(root, p)) push(c.id, `test receipt "${p}" does not exist on disk (stale receipt).`); } if (c.public) { if (c.receipts.source.length === 0) push(c.id, `public capability has no SOURCE receipt (an unbacked public claim is a structural error).`); if (c.receipts.test.length === 0) push(c.id, `public capability has no TEST receipt (an unbacked public claim is a structural error).`); } // Examples resolve to real fences; collect referenced ids and check axis honesty against roles. const roles = new Set<FenceRole>(); for (const ex of c.examples) { const fence = managedById.get(ex); if (fence === undefined) { push(c.id, `example "${ex}" is not a managed fence in the public docs (stale example id).`); continue; } referenced.add(ex); roles.add(fence.role); } if (roles.has("example") && c.syntax.status !== "supported") { push(c.id, `cites an accepted (role=example) fence but claims syntax "${c.syntax.status}"; an accepted fence proves syntax=supported.`); } if (roles.has("reject") && c.syntax.status !== "rejected") { push(c.id, `cites a diagnostic (role=reject) fence but claims syntax "${c.syntax.status}"; a reject fence proves syntax=rejected.`); } if (roles.has("example") && roles.has("reject")) { push(c.id, `cites both an accepted and a reject fence — a single capability cannot be both syntax=supported and syntax=rejected.`); } if (c.examples.length === 0) orphanCapabilities.push(c.id); } // Advertised-face coverage (fail-closed): every face the public docs advertise as one of the four // equal faces MUST be graded by ≥1 public capability, else the page over-claims a surface nothing is // reachable through. `adapter` is intentionally not advertised (kestrel-rki open) — see ADVERTISED_SURFACES. for (const surface of ADVERTISED_SURFACES) { if (!exposedAdvertised.has(surface)) { push( "(catalog)", `advertised access face "${surface}" is exposed by no public capability — either grade a capability that genuinely reaches it, or remove "${surface}" from ADVERTISED_SURFACES (four-faces over-claim).`, ); } } // Every managed fence must resolve to at least one capability (no orphan examples). const orphanExamples = managed.map((f) => f.id).filter((id) => !referenced.has(id)).sort(); for (const id of orphanExamples) { const f = managedById.get(id)!; push("(orphan)", `public fence "${id}" (${f.file}:${f.startLine}, role=${f.role}) resolves to no capability.`); } return { errors, orphanExamples, orphanCapabilities: orphanCapabilities.sort() }; }