Skip to content

Core Concepts

listenrightmeow edited this page Jul 17, 2026 · 2 revisions

Core Concepts

The ideas Feature is built on, spelled out. If the Language Guide is how to write a spec, this page is why the language is shaped the way it is and what the toolchain guarantees in return.


1. Prediction inversion

Traditional assertions check what you thought to check. A prediction declares the complete observable footprint of an operation across every configured surface — and the test fails in both directions:

  • Missing — you predicted it, it didn't happen.
  • Unpredicted — it happened, you didn't predict it.

Worked example. Given this prediction:

predict success:
  response 201 FlowCreatedResponse
  eventStore has [ FlowCreated with FlowCreatedEvent ]
  projectionStore has []
  eventBroker has []

…an implementation that also writes a FlowAuditLogged event fails with:

SPEC-AUT-001 › "successful flow creation" › eventStore[1]: UNPREDICTED record FlowAuditLogged

That failure is the point. Either the audit write is intended — then the spec gains a line and the behavior becomes contractual — or it isn't, and a stray side effect just got caught before it shipped. Nothing observable is allowed to be unspecified. Three rules make this airtight:

  1. Every configured service appears in every prediction (INCOMPLETE_PREDICTION otherwise). has [] is a real assertion — "nothing happens here" — not an omission.
  2. Rejections assert zero effects everywhere. A business rule that refuses must leave no trace on any surface.
  3. Queries cannot express writes at all (QUERY_SIDE_EFFECT is a parse error). Side-effect freedom for reads is grammar, not team discipline.

2. The dual zones

A spec serves two readers with opposite needs. The builder (an AI agent or a person) needs expressive instructions; the compiler needs unambiguous declarations. One file, two zones:

Agent zone Compiler zone
Sections construct:, enforce: contract:, scenario, predict
Grammar Freeform natural language; recognized keywords add structure; everything else is captured verbatim Strict and typed; malformation is a parse error
Can it fail to parse? Never — any instruction is valid Yes — loudly, with a hint

The boundary is the design. Instructions can say anything ("never call the payment provider synchronously"); measurements can say only things a machine can check. A Then clause in prose needs a human to interpret it — that interpretation step is where every spec-to-test approach before this one leaked. Here there is no interpretation step: the compiler zone is the assertion set.

3. The four closed reference spaces

Nothing in a spec references anything undeclared. Each space has a declaration site and a parse-time error:

Space Declared in Error
Service keys (eventStore has …) config services UNKNOWN_SERVICE_KEY
Schema names (with FlowCreatedEvent) the spec's contract: block UNDECLARED_SCHEMA
Command names (when: CreateFlow) config response.commands UNKNOWN_COMMAND
Actor names ((as admin)) config response.actors UNKNOWN_ACTOR

Every error lists the valid names. The effect is type safety for specifications: typos, renamed schemas, and phantom services die at feat parse, not in a 3 a.m. test run. It also makes specs reviewable — a reader can trace every identifier to its declaration.

4. The agent contract

The spec is a two-party contract: the author states what must exist; the builder implements exactly that. Four mechanisms hold both sides honest:

  • Lifecycledraft → agreed → built → verified. agreed is the handshake: nothing is built before it. Tooling — never a human — flips built (suite green) and verified (green under generated tests in CI). Editing an agreed spec demotes it: changed contract, renewed handshake.
  • Change boundariestouches <glob> declares the file footprint before building, and the build may not exceed it. Prediction inversion for the working tree.
  • Rejection traceability — every rejection a scenario predicts must be justified by a rejects <ID> when <reason> instruction, and every justification must be tested. Checked in both directions by feat audit; untested rules and unexplained tests are both findings.
  • The ambiguity protocol — a builder that finds an instruction ambiguous halts and asks. It never guesses, never "interprets," never edits the spec to match what it built. Ambiguity count per spec is a quality signal for the spec itself; the target is zero.

5. Determinism

The entire CI story rests on one property: same inputs, same bytes.

Stage Guarantee
parse same spec + config → identical IR
derive same IR + config → identical topology
emit same topology + schemas → byte-identical test file

To keep emit byte-stable, generated files contain no timestamps and no environment residue; the header carries a sha256 of the inputs (spec text + contracts + the derivation-relevant config slice) plus the language and emitter versions. feat verify is therefore a plain byte comparison — cheap enough to run on every push, strict enough that any drift between specs and committed tests fails the build. Upgrading the emitter intentionally changes the hash: a toolchain upgrade forces a regenerate, which is correct, because a new emitter may change output.

6. The capture window

Every test executes the same choreography:

reset adapters (fresh namespaces)
   │
   ├─ given: execute / seed / clock       ← BEFORE the window; effects never captured
   │
   ├─ window opens
   │     stimulus: when-command or deliver-events   (delivered events excluded from capture)
   │     …one shared wait for the scenario's eventual-consistency services…
   │     capture sweep across every service
   ├─ window closes
   │
   └─ diff: captured reality vs prediction (response, records, state)

Two decisions worth knowing:

  • Preconditions are input, not output. execute/seed effects land before measurement so the prediction covers exactly the operation under test.
  • One shared wait, not per-service waits. The runtime waits once for the longest convergenceTimeout among the scenario's eventual services, then sweeps. Absence proofs (has [] on an eventual service) ride the same wait; absenceTimeout can shorten it as a tuning knob for rejection-heavy suites.

Consistency models set capture timing per service: acid (immediate), strong (post-replication), eventual (after the shared wait) — and set the default ordering semantics (acid/strong ordered, eventual multiset).

7. Anchors — failures point at the spec

Scenario names are unique per spec and become failure coordinates. Every assertion the runtime makes carries one:

SPEC-USR-002 › "unknown users are rejected" › row[2] › response › code:
  expected "UNKNOWN_USER", got "NOT_FOUND"

Spec ID → scenario → outline row → surface → field. A red test is a pointer into the contract, not a stack trace to reverse-engineer. This is what makes the red→fix loop precise for a human and tractable for an agent.

8. Golden fixtures and the corpus discipline

equals fixture "<path>" asserts deep structural equality against a JSON document — validated against the position's schema at compile time (type-safe goldens) and inlined into the generated test (self-containment). Use it when the whole document is the assertion.

The repository applies the same idea to the language itself: corpus/ pairs each exemplar .feat with its expected IR, and the parser's own spec asserts every pairing as a golden. The corpus is simultaneously documentation, test data, and the conformance suite any future parser must pass. The discipline that keeps it honest: a language change is incomplete until the reference, a corpus exemplar, the implementation, and a full corpus re-validation land together. That rule was earned — twice, features were documented but unexercised, and both gaps surfaced only when something finally consumed them.

9. Generated tests are committed artifacts

Generated suites are written next to their specs and committed, not produced at build time. Three reasons: feat verify needs a committed artifact to compare against; reviewers see exactly what will be asserted; and the test file is a durable record tying a spec version to its assertions. Generated files are self-contained — schemas, goldens, and seed data inlined, one runtime import — so they survive refactors of everything around them and run anywhere the runner runs. The corollary: never edit one. Change the spec; regenerate.

Clone this wiki locally