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.

88 lines (72 loc) 5.7 kB
# OSS-ADR-0049 — The `because` clause pre-registers a Plan or Wake against a thesis by content hash Status: accepted (2026-07-16, owner-ratified canon reconcile — reverse-direction boundary-split of PLAT-ADR-0026; kestrel-wlw9). Implemented on `main` as kestrel-rtf. Related: PLAT-ADR-0026 (platform seam — the Thesis artifact and its pre-registration ledger live platform-side; the cross-repo consumer is `kestrel-markets-rku.7`). OSS-ADR-0001 (four statement kinds), OSS-ADR-0004 (the typed object model IS the language; byte-stable round-trip), OSS-ADR-0033 (byte-stable trivia that travels WITH the action). ## Context PLAT-ADR-0026 introduced a `because` citation clause on Plan and Wake statements so an agent can **pre-register** a document against a platform-side Thesis: the clause carries the Thesis's content hash, and because a Kestrel document's armed identity is `sha256(print(module))`, the citation binds into the armed-document hash — pre-registration becomes tamper-evident the moment the plan is armed. But `because` is a change to the **Kestrel language grammar** — a new optional field on two of the four statement nodes, a new keyword, new fail-closed refusals, and a temporal predicate envelope. PLAT-ADR-0026 recorded it in a **platform** file only. The decision has no OSS home, even though the AST (`src/lang/ast.ts` `Citation`), parser, printer, and golden contract (`tests/golden/{accept,rejects}/because-*`) all live in this repo and shipped under kestrel-rtf. This ADR is that OSS home: it records the grammar decision on the OSS side and companions PLAT-ADR-0026, which retains the platform-side Thesis/ledger concerns. ## Decision 1. **`because` is an optional AST field, NOT a fifth statement kind.** The statement algebra stays exactly {View, Wake, Plan, Grade} (OSS-ADR-0001). `because?: Citation` is added to `PlanStatement` and `WakeStatement` only. A `Citation` is `{ kind: "citation"; algo: "sha256"; hash: <64 lowercase hex> }` — never a `StatementKind`, never a `PlanClause`. 2. **Content-hash ONLY — `sha256:<64 hex>`, no inline body.** The token reuses the repo's ubiquitous sha256 digest shape (the same 64-lowercase-hex form as `bus_sha256` / `plans_sha256` / `ConfigId`) — no second citation scheme. The Thesis *prose* lives platform-side; the clause carries only the digest that references it. 3. **The citation binds into the armed-document hash automatically.** It prints byte-stably like any other canonical line, so `sha256(print(module))` covers it with no special casing — pre-registration is tamper-evident once armed. This is why the clause is content-hash-only: the hash is the whole point, and an inline body would be un-hashable-into-identity noise. 4. **The thesis temporal envelope `{within, until, at}` reuses the Wake predicate surface — no second predicate language.** `within` already existed; `until <HH:MM>` and `at <HH:MM>` are added as **postfix combinator siblings** of `within` over the SAME `Trigger` algebra, each carrying a `TimeOfDay`. An expectation like `expects` / `invalidated_when` is therefore an ordinary Trigger wrapped in a temporal window, not a bespoke thesis grammar. Runtime *firing* of `until` / `at` is deferred: a session that cannot evaluate them reads UNKNOWN and de-arms with a logged reason (fail-closed), pending a runtime ADR. 5. **Three fail-closed refusals, enforced symmetrically on every surface** (parser rejects the text with a positional `KestrelParseError`; the builder refuses to construct it; the printer throws on it — the OSS-ADR-0004 round-trip discipline, so a hand-built citation throws exactly where the parser does): - **(a) bad/absent digest** — anything not of the fixed shape `sha256:<64 lowercase hex>` is refused (`because sha256:deadbeef` → refused). - **(b) inline body** — any token trailing the digest (a quoted thesis body, more prose) is refused; the body lives platform-side. - **(c) `because` on a View or Grade** — a citation may pre-register only a Plan or Wake (never a fifth statement kind, OSS-ADR-0001); pre-registration binds an *executable expectation* to a thesis, which a View and a Grade do not author. ## Consequences - **Pre-registration is tamper-evident by construction**, riding the existing armed-document hash — no new integrity mechanism, no runtime semantics for the citation itself (like a comment under OSS-ADR-0033, it is authored reasoning that travels with the action; unlike a comment, its digest is load-bearing for identity). - **The golden corpus is the grammar contract** (OSS-ADR-0004): `plan-because.kestrel` and `wake-because-envelope.kestrel` (accept) and `because-{bad-hash,inline-body,on-grade,on-view}.kestrel` (rejects) lock the round-trip and the three refusals. `print(parse(text))` stays byte-stable. - **This is the single cross-repo grammar edge.** The platform (`kestrel-markets-rku.7`) consumes the clause to look the Thesis up by hash and check `expects` / `invalidated_when` against the realized tape; the split keeps the *grammar* decision in OSS and the *Thesis artifact + ledger* decision in PLAT-ADR-0026, each in its own repo's ADR line. - **The statement algebra is unchanged.** Every existing builder, deep-equality test, and the four-kind invariant hold; the field is simply absent on documents that do not pre-register. ## Concrete syntax See `docs/spec/SURFACES.md` (Citation section + the fail-closed doctrine checks) for the printed form, fixed clause placement (Plan: after `WHEN`; Wake: after `WHEN`, before `DELIVER`), and the `until` / `at` temporal postfixes.