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
text/typescript
/**
* # 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() };
}