proposal/16-syntax.md

Committed-Core Surface Syntax

The complete surface for Strata's committed core, designed against 15-syntax-requirements.md R1–R36 and reconciled from the syntax probe wave: probes/syntax-grammar/ (resilient-LL + tree-sitter grammar, 26 GAP designs), probes/syntax-corpus-core/ (hardware corpus, 31-entry gap ledger), probes/syntax-corpus-verif/ (verification/protocol corpus, G-S1–G-S38). Charter picks (D-077) are SETTLED; reg forms are SETTLED (D-079); the grammar-evidence decisions are SETTLED (D-080). The whole document is DRAFT-COMPLETE (D-078) pending the fmt dry-run and blind-regeneration passes. Standing tiebreaker: Rust style when unsure.

The 2026-07-21 systems-safety wave added requirements R37–R50 but did not run a syntax/corpus probe for them. This document therefore does not pretend to settle spellings for health contracts, mapping leases, progress summaries, memory events, observers, power/reconfiguration, physical certificate binding, numeric budgets, DFT authority, or assurance cases. Their architectural homes and syntax constraints are settled provisionally; their surface forms require a dedicated corpus, grammar, formatter, and blind-regeneration wave before joining this specification (D-091).

1. Design rules

  1. Ride the Rust distribution (P-11): agents are the first users; ~55% of LLM Verilog failures are syntax-class errors concentrated where Verilog diverges from software convention — divergence from Rust is spent only on hardware semantics (component, inst, reg, domains, events, protocols).
  2. Keyword-anchored, locally resolvable: every major form opens with a unique keyword — resilient-LL recovery points, tree-sitter fidelity, and generation commitment points are the same property. No symbol-operator polysemy (Chisel's :=/<> zoo is the cautionary tale).
  3. Redundancy that checks: named-only connection, named invariants, mandatory reasons — every redundant name is a slot where a hallucinated connection fails loudly. Never make anyone write the same fact twice unchecked: infer it or check the duplicate (R2).
  4. No layout semantics: explicit terminators and braces survive generation truncation and keep D-052 structural identity trivial.
  5. One way to write each thing: where sugar exists (punning, elided clocks, T @ 'E) it is the fmt-canonical form, so corpus and formatter agree.

2. Settled charter (Veronica, 2026-07-17) + D-079

ForkPick
Component flavorValue-flow: inputs as parameters, outputs as return type; no in/out/inout keywords; bidirectionality via protocol endpoint types
GenericsRust split: bare <> in type position, mandatory ::<> turbofish in expression position; lexer emits single >, parser glues
Bindingslet / reg / inst triad, comptime/ghost prefixes — binder form makes phase syntactically recoverable per token (14 §19)
TerminatorsSemicolons for statements (including requires); comma-lists for all data-shaped declaration bodies (fmt trailing-comma steering, 14 §2)
Registers (D-079)reg name: T reset(v); declares; name <- expr; is the next-cycle write. No = expr next-value form at the declaration. One meaning per token: = binds combinational values, <- is the visible Later-boundary write; multi-arm updates stay natural (sequential last-write-wins per body, elaborating to a priority mux)
TiebreakerRust when unsure — resolves: #[attr], where, :: paths, //+/// comments, ../..= ranges, match/pattern syntax, snake_case/CamelCase

3. Lexical structure

Identifiers. [A-Za-z_][A-Za-z0-9_]*. Convention (lint-enforced, not grammar): CamelCase types/variants/evidence constructors, snake_case values/functions/schemas, lowercase keywords.

Apostrophe labels. 'ident is one token: temporal validity — clock domains ('core) and events ('Exec) share the grammar, deliberately resonant with Rust lifetimes. There are no char literals; 'x is always a label.

Keywords (hard-reserved). Every form-opening word plus the four soft keywords the grammar probe flagged as recovery hazards (state, on, preserve, at_most — D-080):

1accept await bundle by claim clock_domain comptime component
2const contract counter differential_test discharge div domain
3elastic else enum experiment expect false fn for
4from ghost halt handshake if impl in inst
5interface invariant is latch let loop match mem
6mod msg mut no_reset on opaque ordering port
7preserve process property protocol prove pub quiesce recv
8reg repeat require requires resource retain return role
9rule sealed send sequence session solve stage stamped
10state state_machine static strategy stream struct to
11true type unsafe use where while* with
12at_most cycleobs

(while reserved unused, Rust-lean.) Soft (position-only) words, ordinary identifiers elsewhere: param, fixed, reset, related_by, key_set, binds, scope, over, under, evidence, assume, guarantee, liveness, arbitrate, sweep, as, at, cycle and the unit idents. narrow, weighted, config are special only as ident!.

Integer literals. Unsuffixed integers are static-phase polymorphic naturals checked to fit exactly (too wide = error, never truncation — D-030); 0x/0b/decimal with _ separators. No tick-literals ever (8'hFF needs context-sensitive lexing and truncates silently — D-048). Explicit form where inference is absent: UInt::<8>(0xFF).

Quantity literals (verif corpus, G-S5): NUMBER unit where unit comes from the closed, edition-versioned unit table, legal only in quantity positions (evidence arguments, flow entries, #[schedule], experiment entries): 17.2e9 txns, 842 MHz, 1/cycle. N/cycle is a grammatical Rate, not division — / exists nowhere else in the language. Decimal-fraction/exponent numerals (17.2e9, 0.3, 1.0e-6) are legal only inside quantity and metadata positions; there are no hardware floats.

Unit classv1 closed table
rate denominatorcycle
counttxns, evals
frequencyMHz, GHz
timens, us, ms

String literals. Metadata positions only (reason =, attribute args, tool versions like "vivado-2026.1" — bare vivado-2026.1 is lexer poison). No hardware string type exists.

Comments. //, /// doc (item-anchored), //! module doc — anchored trivia per D-053; never column-significant.

The > discipline (D-080). The lexer never emits >> or >=: it emits single > (and =); the parser glues byte-adjacent > > into shift and > = into >= in expression-operator position only. Consumer<ready_valid<T>> needs no splitting — each > closes one level. <, <=, <<, ::< lex directly. ..=/.. are single tokens. No context-sensitive lexing anywhere (the tree-sitter co-grammar, D-066, depends on this).

4. Items

Every item form opens with a unique keyword (design rule 2). Items take #[attributes], /// docs, and visibility pub / pub(package) (module-private default; pub(crate) etc. deferred).

component — value-flow: inputs are parameters, the output is the return type. Multi-output components use named tuple returns (G-S35: contracts and require guarantee must name outputs):

1pub component MemoryController<const OUTSTANDING: Nat1, const BANKS: Nat1 = 1>(
2 requests: Consumer<ready_valid<MemRequest>>,
3) -> (resp: Producer<ready_valid<MemResponse>>, dram_cmd: Producer<ready_valid<DramCmd>>)
4where OUTSTANDING <= 64
5{ ... }

Statefulness is visible at the use site: components instantiate with inst, functions call bare. A component's operation surface (port items, §5) is reachable through a borrowed handle parameter table: &TxnTable; an inst value coerces to its value-flow result where one is expected (core G19). Item-position unsafe surface: pub unsafe(clock_crossing) component AsyncFifo<'src, 'dst, T, const DEPTH: Nat1>(...).

fn / comptime fn — pure/combinational functions and generators. ghost fn declares monitor-phase functions (relations, predicates); refinement/ghost integers are mathematical, so bare - is legal there (never on hardware integers, R8).

interface / sealed interface / impl — Rust-trait-shaped (D-038): member fn ...; signatures, associated type Grant;, impl Path for Type { }. sealed interface is the protocol-catalog mechanism.

enum — Rust bodies plus explicit encodings (G-S1): the ascription makes it Encoded<Opcode, Bits<7>> — encoding is semantics, not a #[repr] layout hint. Discriminants checked to fit exactly.

1pub enum Opcode: Bits<7> {
2 Load = 0b000_0011,
3 Store = 0b010_0011,
4}

struct — Rust bodies, comma-lists, unit structs struct Active;, grade-0 phantom type params legal unused.

opaque type / type alias — pub(package) opaque type TxnId = Handle<...>; (declaring module owns mint/open); plain pub type Addr = UInt<44>;.

const — const TXN_ENTRIES: Nat1 = 16; — item-position, implicitly static-phase (comptime let is the body-local form).

clock_domain — declared once, referenced by name (R2): clock_domain core { clock: CoreClock, reset: CoreReset { polarity: active_low, deassert: sync }, }; no_reset as a marker entry declares a reset-free (unit-epoch) domain.

resource — resource adder: Unit<Latency<0>, II<1>> = share(wrapping_add); — abstract, = share(f) bound, or arrays [Unit<...>; N] = replicate(f) (replication always explicit).

protocol / bundle / stream — §10. state_machine / strategy fn / property / differential_test / experiment — §11.

use / mod — Rust use-trees (use a::b::{C, D};, ::*, as); pkg:: is the current package root; pub mod x; + pub use re-exports define the package surface (13 §6).

discharge — statement-positioned, §5.

Instantiation is named-only with punning — positional connection does not exist:

1let fifo = inst Fifo::<LoadReq, 8> { push }; // pun connects `push`
2let out = fifo; // value-flow result

One protocol-connect form, checked by duality (04 §3): passing a producer value to a Consumer<P> parameter is the connection. A standalone connect statement was drafted in the core corpus and never needed — rejected (§15).

5. Statements

Component/fn bodies are statement lists terminated by ;; block-like expressions (match/if/for/domain/blocks) in statement position take no ;. Comma-lists are reserved for data-shaped braces (struct/enum/contract/arbitrate/protocol/bundle/stream/clock_domain bodies) — the two disciplines never share a brace pair.

1let decoded = decode(instr); // combinational; IMMUTABLE (R1)
2let mut acc = init; // mutation is marked, rare
3let (a, b, c) = triple; // n-ary destructuring (R34)
4if let Some(item) = pushed { ... } // rust if-let
5
6reg rd_ptr: Index<DEPTH> reset(0); // D-079: declaration, reset mandatory*
7rd_ptr <- rd_ptr.incr_wrap(); // next-cycle write; last-write-wins per body
8buf[wr_ptr] <- item; // element write (same statement form)
9retain reg saved: ConfigState reset(dflt);// survives warm reset; reset() = POR only
10mem buf: [T; DEPTH]; // un-reset storage; reads need an
11 // initializedness argument or unsafe(uninitialized_use)
12
13inst — expression form only: `let x = inst C { ... };` (bare `inst x = ...;` rejected, §15)
14comptime let LANES = cfg.lanes; // static phase
15comptime for i in 0..LANES { ... } // static loop; also an expression (array per iteration)
16comptime param STAGES: Nat in 1..=6; // design-space knob (07 §7)
17comptime fixed DEPTH: Nat = cfg.depth; // user-owned config
18ghost let trace = observe(bus); // ghost phase, erased

* reset(v) is mandatory unless the ambient domain is no_reset or the register is explicitly uninit (02 §6). Unwritten paths hold. reg is never inferred from code shape (kills latch inference and blocking/nonblocking outright).

Obligations and discharge (named forms — verif G-S2/G-S3; ; canonical, D-080/GAP-19):

1requires solve mem_has_base_reg(opcode: Bits<7>) {
2 if entry_for_encoding(opcode).class is Mem { entry_for_encoding(opcode).has_rs1 }
3};
4discharge mem_has_base_reg by comptime_exhaustive(opcode);
5requires static { DEPTH mod 2 == 0 };
6requires prove home_serves_all { every_request_eventually_responds(requests, resp) };

Names are mandatory on solve/prove (discharge and diagnostics address by name), optional on static. The binder on the requires is the finite domain the discharge strategy enumerates. narrow!(cond); at dataflow boundaries only (R4); the weakened-guard diagnostic quotes the dropped conjunct. require guarantee mem.no_duplicate_response with evidence >= BoundedProved; states a consumer's evidence floor (§11).

Pipeline and time. stage; is the bare separator; stage 'Name; additionally binds the event label used by .at('Name) claims. Values crossing a stage; are auto-piped; inserted registers are named in the structure report. Region markers: elastic { ... } (TransactionEquivalent bound) and cycleobs { ... } (CycleExact), R35.

Multi-cycle control (core G24): sequence { ... } elaborates to an FSM; await bool_expr; waits; await process_expr runs a Process to completion yielding Result<S, F>; halt diverges (types as !); loop { } as in Rust. Typestate transitions construct Process values with the process { step:, done:, ok:, err:, } block (core G23).

Handshake endpoints (core G11/G12): the value-flow introduction/elimination forms for protocol endpoints —

1let (out, popped) = handshake ready_valid::<T> { valid: !empty, payload: head };
2let (out, _sent) = handshake ready_valid::<T> from opt_value; // Option drives valid/payload
3let pushed = push.accept(ready = !full); // drives ready, yields Option<T>

accept returning Some implies the ready expression held that cycle (a stated protocol-semantics fact feeding guard inference).

Other statements: port name(args) -> Ret { body } (component-body only; elaborates to a request_response endpoint; body-less port log(pkt: stamped TracePkt); declares); domain 'core { ... } blocks (expressions; escaping values carry their domain tag); unsafe(category, reason = "...") { expr } granular unsafe; return expr;; assert expr; (ghost/test phase only); contract { ... } blocks (§11); resource declarations; expect exists only as the #[expect] attribute (§9), never a statement.

6. Expressions

match/if are expressions (R3/R5); blocks yield their trailing expression. Variant names resolve against the scrutinee's declared enum type (R2).

Precedence (low → high; comparisons non-associative, Rust-lean):

LevelOperators
assign= (to let mut targets; lvalue-checked)
range.., ..=
or||
and&&
compare / test== != < <= > >= · is variant-set
concat++ (bit concat; widths sum in the D-037 algebra)
shift<<, >> (glued)
additive+, - (grammatical; hardware integers reject bare - — R8)
multiplicative* · div/mod with ConstAtom right operand by grammar (the R7 Presburger fence)
unary!, -
postfixcall, .method, ::<T> turbofish, [i] index/slice, ?
  • Variant-set tests: op is Load | Store, closed under &&/||/! (R3); diagnostics render op ∈ {Load, Store} (R5). Idiom the docs and lints teach: bind then match — let d = decode(raw); match d { ... } — let-bound scrutinees and predicate aliases are what guard inference traces.
  • Arithmetic is named methods: wrapping_add, full_add, checked_add, saturating_add; subtraction forces the choice — checked_sub (Option) vs monus (floors at zero). Conversions: wrap_to::<N>(), saturate_to, checked_to, sign_extend_to (no as). No compound assignment (+=) — exception: session counters (§10), which are math naturals.
  • Bit slicing: little-endian bit weight, half-open ranges — x[0..8] is the low byte, x[msb] sugar; never Verilog [7:0]. x[0..k] ++ x[k..N] widths sum. Named Encoded fields displace raw slicing (lint on raw slices of typed values).
  • ? propagates stall/fault through the canonical Bypass/Poison node: a Bubble<T> unwraps or kills the dominated region; Bubble::of(x) re-wraps (core G28). Also queue.reserve()?.
  • Turbofish is mandatory in expression position (UInt::<8>(0xFF), x.wrap_to::<4>(), overlaps::<W> inside relation args — F15's fix-it: "expression position: write ::<"). Domain labels ride along: AsyncFifo::<'core, 'dbg, T, 8>.
  • Event expressions: 'Exec, 'G + 2; resource claims adder.at('Exec)(a, b) — guards on conditional claims are never written (R2), they are inferred from if/match path conditions and rendered in set form with the harvested path condition (R5/R6).
  • Struct literals are FORBIDDEN at the top level of if/match/for headers and match-arm guards (D-080; Rust's rule): if x == S { } parses S as a path and { } as the body; parenthesize to opt in — if a == (Grant { slot: 0 }) { b }. Functional-record-update ..rest is legal (CacheAddress { offset: 0, ..arbitrary() }).
  • arbitrate is an expression — a named architectural node whose output flows on (G-S23, resolving both 15 §3 flags: guarantee/liveness join the comma-list with mandatory names; eligibility/keys are explicit closures, never a magic r):
1let granted = arbitrate dram_issue {
2 inputs: [read_q, write_q],
3 eligibility: |r| r.ready,
4 policy: oldest_first(key = |r| r.age, tie_break = round_robin),
5 resource: dram_cmd_port,
6 guarantee exclusive_grant: at_most_one_grant_per_cycle(),
7 liveness weak_fairness: fair_grants() under continuously_ready(dram_cmd_port),
8};
  • Predicate position shares the expression grammar with two deltas: implication sugar if cond { pred } means !cond || pred (R11; predicate position only — the expression-side error must say so), and div/mod keep the syntactic fence. Interpreted predicate symbols (closed set): clog2, max, min, prev (R9 stability), accepted, transferred, outstanding, same_epoch.
  • Phase operators are plain calls: reflect(x), reify(x), symbolize(x), observe(x).

7. Types

1Type = ["stamped"] ['domain] TypeCore ["@" 'Event]
2TypeCore = Path<GenericArgs> | (T1, T2) | [T; N] | fn(A) -> B // fn types comptime-phase only
  • Generic parameters: type params with Rust bounds T: Ord + Encode; const params const DEPTH: Nat1 with optional ranges const N: Nat in 1..=6; label params 'src, 'dst (domains/events — the Rust-lifetime grammar accepts all four positions unchanged: params, type args Epoch<'core>, signal prefixes x: 'core Bool, turbofish).
  • where takes both interface bounds and Tier-1 infix predicates: where DEPTH <= 1024, T: Encode (D-080: bound-vs-predicate needs LL(2), committed). Where-lists take no trailing comma (not a braced body).
  • Positivity is a type (R19): Nat1 is the standard depth/count parameter type (where N >= 1 also legal; Nat1 is the path of least resistance). Width expressions in <> follow the D-037 closed algebra (clog2/max/min spelled exactly so, R21); leaving it triggers the "left Presburger" fix-it (R20).
  • The angle-bracket sub-language (D-080): inside <...> the grammar excludes comparisons, shifts, &&/||, and struct literals, so > always closes and , always separates — bare <> in type position needs no symbol table and no brace fallback. A bare path argument is type-or-const until name resolution (as in Rust). Trailing commas are legal in <> lists (fmt wrap rule, §13). Schema config blocks are type-position only: burst_dyn<Flit> { max_len: 8 } (expression position would collide with struct literals — same class as the header rule).
  • Refinement types: Occupancy<0..=ENTRIES> (state refinement, 06 §1), Index<N>, Bits<N>, UInt<N>, OneHot<N>, Gray<N>, Encoded<T,E>, Handle<R,Slot,Gen,Epoch>.
  • stamped T — the one epoch keyword users write (§12). T @ 'E availability sugar for At<T, 'E> is adopted (§15): it parsed cleanly through both grammar implementations and is the fmt-canonical form; annotation only for deviation from the ambient stage.
  • Endpoints: Consumer<P> / Producer<P> for binary protocols; View<Bundle, Role> is the per-role projection for ≥3-role bundles (G-S22; binary duality is provably no participant's view of a 3-role body).

8. Patterns

Rust patterns: _, literals, ranges 0..=3 =>, or-patterns A | B, tuple (n-ary, R34), Path(p1, p2), Path { field, .. }, mut x, if let. A bare lowercase path binds; a bare CamelCase path is a unit variant, resolved against the scrutinee's declared enum (R2 — wave-1's only bugs were hand-written variant-index typos). Match arms: Pattern [if guard] => expr, — exhaustive over enums; arm attributes legal.

9. Attributes and the Meta micro-grammar

#[attr] (F6, tiebreaker), declaration-position only. The Meta micro-grammar is committed (D-080 — attribute/expect metadata is structured, never a balanced-token soup):

1Attribute = "#" "[" Meta "]"
2Meta = Path [ "(" CommaList(MetaItem) ")" ]
3MetaItem = Meta | IDENT "=" MetaValue | Predicate | Literal
4MetaValue = Literal | QuantityLit | Rate | Path | "derived" | IDENT "(" CommaList(MetaValue) ")"

The canonical inhabitants:

  • #[budget(comptime_steps = 20_000_000, per_eval = derived)] — budgets are attributes and only attributes (R22); no raise API exists syntactically; per_eval = <n> is the declared-path override (R23).
  • #[schedule(latency <= 2, throughput >= 1/cycle)] — on architectural nodes.
  • #[expect(lint = ..., reason = "...", evidence = ..., review_by = edition(2028))] — suppression (D-057); on any item, statement, or declaration entry; never an inner attribute; one lint per attribute (G-S30).
  • #[derive(Pack, Arbitrary, Shrink, Waveform, Symbolic)] — the five artifact families (08 §1).
  • #[metamorphic(relation = f)], #[symmetric] (checked, R15), #[encoding(auto where preserves_debug_identity)].

10. Protocols, sessions, ordering

Two protocol dialects, one item (G-S10 rule; never mixed in one body): role-less binary bodies use the sequential dialect; bodies declaring roles use the msg + state session dialect.

Sequential dialect (catalog-style):

1protocol Burst<const N: Nat1, T> {
2 const granule = 64, // R16 shared constants
3 send Header { length: N },
4 repeat N { send Beat<T> }, // R10: no hand-rolled beat counters
5 if broadcast { send Snoop }, // R11 conditional refinement
6 recv Completion,
7 quiesce, // R17 acceptance point
8 invariant beats_bounded: accepted(Beat) <= N, // names mandatory (R12)
9}

Session dialect (D-027's first customer — roles, families, ghost state, guarded FSM):

1protocol ChiReadSharedTxn<const N_RN: Nat1> {
2 role Requester,
3 role Home,
4 role Peer[N_RN - 1], // role family; cardinality monomorphized
5 msg ReadShared: ReqFlit<N_RN> from Requester to Home,
6 msg Snoop: SnpFlit<N_RN> from Home to Peer[tgt], // indexed by a payload field
7 counter snp_sent: 0..=N_RN - 1, // session-scoped ghost naturals
8 counter snp_pend[peer: Index<N_RN>]: 0..=1, // keyed family
9 latch requester: Index<N_RN>, // open-time binding
10 invariant resp_bounded: snp_rcvd <= snp_sent,
11 accept state Idle { // `accept` marks retirement states
12 on ReadShared(req) => Snooping { requester = req.src, snp_sent = 0 },
13 },
14 state Snooping {
15 on SnpResp(r) if snp_pend[r.src] == 1 => Snooping { snp_rcvd += 1, snp_pend[r.src] = 0 },
16 on CompData(_) if snp_rcvd == snp_sent && if bcast { snp_sent == N_RN - 1 } => AwaitAck,
17 },
18}

Counter updates after the target state are name = e / name += e (-= never arises: math naturals, total assignments or increments). from/to tags are mandatory once ≥2 roles exist (R18).

bundle — channels with roles, shared constants, cross-channel invariants (named, R12), session attachments, and quiesce points:

1bundle ChiPort<const N_RN: Nat1> {
2 const granule = 64,
3 role Rn, role Hn, role Sn,
4 req: credit<ReqFlit<N_RN>, 4> from Rn to Hn,
5 dat: layer<burst<4, DatFlit>, credit<4>> from Hn to Rn, // layer combinator (type-level)
6 vc: [layer<burst_dyn<Flit> { max_len: 8 }, credit<4>>; VCS] from Sn to Hn, // array channel;
7 // invariants apply per element
8 invariant comp_bounded_by_req: accepted(dat) <= accepted(req),
9
10 session<txn = txn> lifecycle: ChiReadSharedTxn<N_RN> over { // spawn-per-key + projection
11 ReadShared: req, Snoop: snp, CompData: dat, CompAck: ack,
12 },
13 session<line = addr div granule> line_serial: hazard_lock { // R13; div fence applies
14 binds: txn, // per-message key bindings (addr-less messages inherit at open)
15 scope: lifecycle, // lock acquire/release rides the named session's open/retire
16 },
17 quiesce maintenance_barrier: at(maint.Accept), // R17; end-of-trace implicit
18}

The session<key = expr> clause is its own mini-grammar (a diagnostic label binding, not generic args — tree-sitter uses a dedicated session_key node).

Ordering — a bundle/stream clause: PreservesOrder<A.Accept, B.Transfer, key = TxnId>, group = id, related_by = overlaps::<W> (relations #[symmetric]-checked, R15), key_set = lines(addr, len) (R14 — one event joins every keyed order; start-line-only keying is machine-refuted), CommitsIn<ProgramOrder>. Relation args are the most heterogeneous bracket context in the language; they get a dedicated relation_args production (F12).

stream — every entry name: value, (G-S36): payload:, domain: Core (a clock_domain reference), protocol: ready_valid { payload_stable_while_blocked }, flow: { acceptance: at_most(1/cycle), outstanding: 0..=8, latency: dynamic }, identity:, ordering:, authority: { speculative: BranchEpoch, cancellation: BranchRecovery }.

11. Verification surface

contract blocks (core G13 + verif G-S4 merged) — component-body position, comma-list entries, three entry kinds plus flavors:

1contract {
2 assume payload_stable: requests.payload_stable_while_blocked(),
3 guarantee no_duplicate_response: exactly_one_response_per_request(requests, resp)
4 with evidence [
5 Tested<17.2e9 txns>,
6 BoundedProved<depth = 256>,
7 MutationValidated<duplicate_response_mutants>,
8 ],
9 claim frequency: Measured<842 MHz, vu19p, "vivado-2026.1">,
10}
11contract cdc { ordering: preserved, capacity: DEPTH, ... }
12contract death { drain_within_cycles: 64 } // or drain_within_responses: N

claim carries physical facts — evidence about an artifact, never a guarantee of the term (D-075). Evidence literals: Ctor<args> with quantity literals, idents, name = literal pairs, strings (G-S5); evidence is a set (D-017), bracketed. Contract predicates take the ports/outputs they constrain — which is what forces named returns (G-S35). Inside contracts and requires, result names a single anonymous output (core G14).

Consumer evidence floor: require guarantee mem.no_duplicate_response with evidence >= BoundedProved; — >= compares against the set's best entry on the relevant axis.

Obligations: requires static/solve/prove + named discharge (§5). State refinements over runtime values are 06 §1's second sort; "monitors watch wires, they don't take tokens" (R33) fires on linear values in bounds. Failure-arm disposition diagnostics point at the arm (R32 — checker duty).

Generators: strategy fn name(state: &Model) -> T { ... } with weighted! { 25 => e, ... } arms (G-S24); generator binders param in strategy_expr in rule/property parameter lists, bare name: Type = the type's derived default strategy (G-S25).

state_machine (rule-based lockstep testing, G-S26 — distinct from session FSMs):

1state_machine CacheLockstep {
2 state { model: CacheModel, dut: DutHandle<L1Cache>, }
3 rule load(addr in cache_address(&model)) if dut.can_accept() { ...; }
4 invariant committed_loads_match_model { assert_eq(dut.x(), model.x()); }
5}

property — #[metamorphic(relation = f)] property name(binders) { stmts; assert rel(a, b); }. assert is the ghost/test-phase assertion (hardware claims stay requires/narrow!).

differential_test — comma-list block: dut: RvCore::<Rv32iMinCfg>, reference:, interface:, lockstep:, strategies: [..], coverage: [..], on_divergence: [..],.

experiment (G-S29 — designed from nothing):

1experiment mem_ctrl_depth_sweep on MemoryController {
2 target: vu19p,
3 variants: sweep { OUTSTANDING: [8, 16, 32], BANKS: 1..=2 }, // or any comptime Sweep<C> expr
4 workloads: [stream_copy(bytes = 1_048_576, seed = 7)], // explicit seeds (P-6)
5 measures: [fmax, luts, occupancy(of = self.occupancy)], // self = the DUT instance
6 faults: [dropped_response(rate = 1.0e-6, seed = 23)],
7 budget: { builds: 24, board_hours: 8 },
8 evidence: Measured<vu19p, "vivado-2026.1">,
9}

Every sweep point must satisfy the DUT's where bounds up front (D-042).

12. Epoch surface

The entire user-facing epoch vocabulary is three forms (R24–R26) plus the reset witness:

1port log(pkt: stamped TracePkt); // R24: the ONE epoch keyword users write
2let sealed = stamp(head); // introduction — legal only where the surrounding
3 // port/return type says `stamped`
4match rebind(pkt) { // R25: bind-epoch as match; whole coordinate
5 Fresh(p) => consume(p),
6 Stale(p) => { stale_drops <- stale_drops.wrapping_add(1); drop_it(p) },
7}
8requires solve diff_same(a, b) { same_epoch(a, b) }; // R26
  • Everything else is inferred (pinning inference is local+principal, 05 §4): no epoch annotations in user signatures; Stamped<T,D> and Crosses are elaboration-owned (R27); the CDC mirror chain is synthesized from rebind over a stamped stream (R30); token states carry facets structurally (R29).
  • pkt.map(|p| ...) transforms the payload under a sealed coordinate — transport stays epoch-oblivious (core G22).
  • Unit-epoch normalization rule (core F8, committed): stamped over a no_reset (unit-epoch) domain is the identity — so a multi-hop crossing that would produce stamped (stamped T) through a unit-epoch fabric collapses, and the inner coordinate is the one that survives.
  • ResetWitness (R28): ResetWitness::mint() at the lifecycle transition, &w ω-borrows at flush sites, w.close() linear consumption — one death event per assertion, unified with the squash contract. retain reg reads elaborate as stamped (the register legally outlives reset, core G30).

13. fmt interactions (pointers into 14)

Additions the corpus waves demanded; normative text lives in 14:

  1. Decode-table row idiom: fixed-arity row constructors (entry(...)) keep table rows one-per-line under no-alignment; raw struct-literal rows explode 7×-vertical. Style-guide idiom + pedantic lint (verif verdict 1).
  2. on-arm wrap rule: session-FSM guards that exceed 100 cols break before =>, indent one level (verif verdict 4).
  3. Generic-arg wrap rule: over-long <...> lists break after <, one argument per line, trailing comma legal in <> lists (verif verdict 5 — new relative to the charter).
  4. Manifest proof sub-tables: [proofs.<name>] one-key-per-line is the canonical form (inline tables can't fit a content hash in 100 cols) — D-054 spec addition (verif verdict 8).
  5. Where-lists take no trailing comma (not braced bodies); requires normalizes to ;; data bodies steer via trailing comma, statement bodies are immune — the disciplines never collide in one brace pair.
  6. Known-awkward but accepted: the three-line guarantee/predicate/with evidence stanza; positional booleans in row constructors (style guide: prefer small enums).

14. Requirement traceability

Committed core (R1–R36 → section)

RSectionRSectionRSection
R1 immutability§5R13 session keys§10R25 rebind§12
R2 checked names / inferred guards§4 §6 §8R14 key_set§10R26 same_epoch§12
R3 is-sets, bind-then-match§6R15 related_by symmetric§10 §9R27 Crosses elab-owned§12
R4 narrow!§5R16 protocol consts§10R28 ResetWitness§12
R5 set rendering§6R17 quiesce§10R29 structural facets§12
R6 path-condition identity§6 §11R18 role tags§10R30 mirror chain§12
R7 infix Presburger + fence§6 §7R19 Nat1§7R31 CommitProof wordinglands in 05
R8 monus vs checked_sub§6R20 left-Presburger fix-it§7R32 arm-anchored diagschecker
R9 prev(x)§6R21 clog2, closed symbols§3 §7R33 monitor diagnostic§11
R10 repeat N§10R22 budget attrs only§9R34 n-ary destructuring§5 §8
R11 conditional refinement§6 §10R23 no overrun ctor§9R35 region markers§5
R12 named invariants§10R24 stamped§12R36 pass/semver/pinning07/13 formats; §13.4

Fence caveat (grammar probe): the grammar fences out compound div/mod right operands (the non-linearity); const-ness of a bare identifier is a trivial name-resolution check — non-constant expressions are grammatically impossible; non-const names are a resolution error.

Systems-safety extension (R37–R50)

R37–R50 remain requirements, not syntax. The extension wave must demonstrate all of the following before adding keywords or grammar productions here:

  1. Sparse opt-in surface: a FIFO using no advanced contract family has an unchanged AST and summary.
  2. Separation checks: occurrence/detection/containment, eventual/bounded progress, implementation-equivalence/noninterference, intended/bound physical constraints, and evidence/assurance cannot collapse into ambiguous forms.
  3. One contract envelope: families reuse contract, named entries, assumptions/guarantees, requires, evidence sets, and metadata rather than each inventing a top-level mini-language unless a corpus proves that impossible.
  4. Stable artifact references: content hash, tool/target/mode/design pins, owner, validity, and expiry fit the existing Meta micro-grammar or force one explicit revision.
  5. Parser and formatter evidence: resilient-LL and tree-sitter agree; mutation recovery remains local; representative MSHR, CHI/NoC, DMA–IOMMU, power/reconfiguration, DFT, numeric, and assurance examples survive blind regeneration.

15. Rejected and deferred constructs

Rejected (recorded so they are not re-invented):

  • Standalone connect a => b; — drafted in the core corpus, never needed: named-argument instantiation is the duality-checked connect (G6; one way to write each thing).
  • reg ... = next declaration-site next-value form — D-079: one meaning per token; <- is the only Later-boundary write. (Supersedes core G7's optional inline form and verif G-S34.)
  • Brace fallback for generic args — the restricted angle-bracket algebra suffices (D-080).
  • Always-allow struct literals in headers — measured ambiguous (GLR-only); forbid + parenthesize (D-080).
  • Bare inst name = expr; binder (grammar GAP-18 sugar) — corpus never used it; let x = inst ... is the one form.
  • Bare discharge by ... without a target name (core F5) and unnamed guarantee {}/liveness {} blocks in arbitrate — named entries are mandatory (G-S23).
  • Implicit-r arbitrate fields (key = r.age) — explicit closures only (F6/F10).
  • session<txn> shorthand — label ≠ field in the computed case (verif F5).
  • Named call arguments — Rust has none; the divergence budget is spent on hardware semantics (verif F9).
  • Statement-form expect(...) — #[expect] attribute only (G-S30). Tick-literals, as casts, bare - on hardware integers, += outside session counters, vertical alignment — per charter and 14.

Resolved this pass: T @ 'E availability sugar is ADOPTED — it parsed cleanly in both the resilient-LL and tree-sitter implementations (GAP-24) and is the fmt-canonical spelling of At<T, 'E>.

Deferred, with reasons:

  • is_pow2 in the width algebra — candidate interpreted symbol awaiting its D-037 confluence-interaction argument; until then the blessed idiom is clog2(DEPTH + 1) == clog2(DEPTH) + 1 (core F9).
  • pub(crate)-style finer visibility beyond pub(package) — no corpus demand (GAP-04, G-S33).
  • at_most 1 per cycle alternative rate spelling — only needed if / ever wants to mean division at comptime (grammar note).
  • Conservation-justified state-refinement discharge (core F4) and event provenance in purely combinational components (core F11) — real gaps, semantics-side; tracked for 03/06, not syntax.

16. Appendix: the complete grammar

The reconciled surface grammar. This EBNF is normative and matches the prose above; it updates the probe's GRAMMAR.ebnf for D-079 (reg/<-), D-080 hard-reservations, and every corpus-won construct. Notation: ISO-flavored EBNF; CommaList(X) = [ X { "," X } [ "," ] ].

ebnf
1(* ---------- 0. Lexical ---------- *)
2IDENT = ident_start { ident_continue } ;
3LABEL = "'" IDENT ; (* domains and events; never a char literal *)
4INT_LITERAL = dec_literal | hex_literal | bin_literal ; (* "_" separators legal *)
5NUM_LITERAL = dec_literal [ "." dec_literal ] [ ("e"|"E") ["-"] dec_literal ] ;
6 (* fraction/exponent forms legal ONLY inside QuantityLit / MetaValue *)
7STRING_LITERAL = '"' { string_char | escape } '"' ; (* metadata positions only *)
8BOOL_LITERAL = "true" | "false" ;
9QuantityLit = NUM_LITERAL UnitIdent ; (* 842 MHz, 17.2e9 txns — closed unit table *)
10Rate = ConstExpr "/" "cycle" ; (* the only "/" in the language *)
11COMMENT = "//" rest | "///" rest | "//!" rest ; (* anchored trivia *)
12(* Lexer: never emits ">>" or ">="; parser glues adjacent ">" ">" / ">" "=" in
13 expression-operator position only. "..="/".." are single tokens.
14 Hard-reserved keywords per 16 §3; soft words are keywords only in the
15 positions shown below. No context-sensitive lexing. *)
16
17(* ---------- 1. Source file and items ---------- *)
18SourceFile = { Item } ;
19Item = { Attribute } { DOC_COMMENT } [ Visibility ] [ UnsafeQual ] ItemKind ;
20Visibility = "pub" [ "(" "package" ")" ] ;
21UnsafeQual = "unsafe" "(" IDENT ")" ; (* item-position unsafe surface *)
22ItemKind = Component | FnItem | GhostFn | StrategyFn | Protocol | Bundle | Stream
23 | Interface | ImplItem | EnumItem | StructItem | OpaqueType | TypeAlias
24 | ClockDomain | ResourceItem | Experiment | StateMachine | Property
25 | DifferentialTest | UseItem | ModItem | ConstItem ;
26
27Component = "component" IDENT [ GenericParams ] ParamClause
28 [ "->" ReturnType ] [ WhereClause ] Block ;
29ReturnType = Type | "(" CommaList( IDENT ":" Type ) ")" ; (* named tuple returns, G-S35 *)
30FnItem = [ "comptime" ] "fn" IDENT [ GenericParams ] ParamClause
31 [ "->" Type ] [ WhereClause ] ( Block | ";" ) ; (* ";" in interfaces only *)
32GhostFn = "ghost" FnItem ;
33StrategyFn = "strategy" FnItem ; (* G-S24; body may use weighted! *)
34ParamClause = "(" CommaList( Param ) ")" ;
35Param = { Attribute } IDENT ( ":" Type | "in" Expr ) ;
36 (* "in strategy_expr" generator binders: rule/property/strategy params only *)
37GenericParams = "<" CommaList( GenericParam ) ">" ;
38GenericParam = IDENT [ ":" Bound ]
39 | "const" IDENT ":" Type [ "=" ConstExpr ] [ "in" RangeConst ]
40 | LABEL ; (* domain/event params: <'src, 'dst, T> *)
41Bound = Path { "+" Path } ;
42WhereClause = "where" WherePred { "," WherePred } ; (* NO trailing comma *)
43WherePred = IDENT ":" Bound | Predicate ;
44
45Interface = [ "sealed" ] "interface" IDENT [ GenericParams ] [ WhereClause ]
46 "{" { InterfaceMember } "}" ;
47InterfaceMember= { Attribute } ( FnItem | ConstItem | "type" IDENT ";" ) ;
48ImplItem = "impl" [ GenericParams ] Path [ GenericArgs ] [ "for" Type ]
49 [ WhereClause ] "{" { Item } "}" ;
50
51EnumItem = "enum" IDENT [ GenericParams ] [ ":" Type ] (* encoding ascription, G-S1 *)
52 "{" CommaList( EnumVariant ) "}" ;
53EnumVariant = { Attribute } IDENT [ "(" CommaList( Type ) ")"
54 | "{" CommaList( Field ) "}"
55 | "=" INT_LITERAL ] ; (* checked to fit exactly *)
56StructItem = "struct" IDENT [ GenericParams ] [ WhereClause ]
57 ( ";" | "{" CommaList( { Attribute } [ Visibility ] Field ) "}" ) ;
58Field = IDENT ":" Type ;
59OpaqueType = "opaque" "type" IDENT [ GenericParams ] "=" Type ";" ;
60TypeAlias = "type" IDENT [ GenericParams ] "=" Type ";" ;
61ConstItem = "const" IDENT [ ":" Type ] "=" Expr ";" ;
62UseItem = "use" UseTree ";" ;
63UseTree = Path [ "::" ( "*" | "{" CommaList( UseTree ) "}" ) ] [ "as" IDENT ] ;
64ModItem = "mod" IDENT ( ";" | "{" { Item } "}" ) ;
65
66ClockDomain = "clock_domain" IDENT "{" CommaList( DomainEntry ) "}" ;
67DomainEntry = "clock" ":" Type
68 | "reset" ":" Type [ "{" CommaList( IDENT ":" IDENT ) "}" ]
69 | "no_reset" ;
70ResourceItem = "resource" IDENT ":" Type [ "=" Expr ] ";" ; (* share(f) / replicate(f) *)
71
72(* ---------- 2. Protocols / bundles / streams (16 §10) ---------- *)
73Protocol = "protocol" IDENT [ GenericParams ] [ WhereClause ]
74 "{" CommaList( SeqEntry ) "}" (* role-less: sequential *)
75 | "protocol" IDENT [ GenericParams ] [ WhereClause ]
76 "{" CommaList( SessEntry ) "}" ; (* roles present: session *)
77 (* one node kind, two dialects; never mixed (G-S10) *)
78SeqEntry = ("send" | "recv") IDENT [ GenericArgs ] [ "{" CommaList( Field | FieldBind ) "}" ]
79 | "repeat" ConstExpr "{" CommaList( SeqEntry ) "}"
80 | "if" Predicate "{" CommaList( SeqEntry ) "}"
81 | "const" IDENT "=" ConstExpr
82 | "quiesce"
83 | "invariant" IDENT ":" Predicate ;
84FieldBind = IDENT ":" ConstExpr ;
85SessEntry = "role" IDENT [ "[" ConstExpr "]" ] (* role family *)
86 | "msg" IDENT ":" Type "from" RoleRef "to" RoleRef
87 | "counter" IDENT [ "[" IDENT ":" Type "]" ] ":" RangeConst
88 | "latch" IDENT ":" Type
89 | "invariant" IDENT ":" Predicate
90 | "const" IDENT "=" ConstExpr
91 | [ "accept" ] "state" IDENT [ "{" CommaList( OnArm ) "}" ] ;
92RoleRef = IDENT [ "[" IDENT "]" ] ; (* Peer[tgt]: payload field *)
93OnArm = "on" IDENT "(" Pattern ")" [ "if" Predicate ] "=>" IDENT
94 [ "{" CommaList( CounterUpdate ) "}" ] ;
95CounterUpdate = CounterRef ( "=" | "+=" ) Expr ;
96CounterRef = IDENT [ "[" Expr "]" ] ;
97
98Bundle = "bundle" IDENT [ GenericParams ] "{" CommaList( BundleEntry ) "}" ;
99BundleEntry = "const" IDENT "=" ConstExpr
100 | "role" IDENT [ "[" ConstExpr "]" ]
101 | IDENT ":" ChannelType [ "from" RoleRef "to" RoleRef ]
102 | "invariant" IDENT ":" Predicate
103 | SessionAttach
104 | "quiesce" IDENT ":" "at" "(" Expr ")"
105 | "ordering" ":" "{" CommaList( IDENT ":" OrderingRel ) "}" ;
106ChannelType = Type | "[" Type ";" ConstExpr "]" ; (* array channels, G-S38 *)
107SessionAttach = "session" "<" IDENT "=" KeyExpr ">" IDENT ":" Type
108 ( "over" "{" CommaList( IDENT ":" IDENT ) "}"
109 | "{" CommaList( IDENT ":" Expr ) "}" ) ; (* hazard_lock config *)
110KeyExpr = ConstExpr ; (* div fence applies (R13) *)
111OrderingRel = Path "<" CommaList( RelationArg ) ">" ; (* dedicated node kind (F12) *)
112RelationArg = EventPath | IDENT "=" ( Expr | Path Turbofish ) ;
113EventPath = IDENT { "." IDENT } ;
114
115Stream = "stream" IDENT [ GenericParams ] "{" CommaList( StreamEntry ) "}" ;
116StreamEntry = IDENT ":" ( Type | Expr | "{" CommaList( StreamEntry ) "}" ) ;
117 (* payload/domain/protocol/flow/identity/ordering/authority;
118 flow entries: acceptance: at_most(Rate), outstanding: range *)
119
120(* ---------- 3. Verification items (16 §11) ---------- *)
121StateMachine = "state_machine" IDENT [ GenericParams ] "{"
122 "state" "{" CommaList( Field ) "}"
123 { RuleItem | SmInvariant }
124 "}" ;
125RuleItem = "rule" IDENT ParamClause [ "if" Expr ] Block ;
126SmInvariant = "invariant" IDENT Block ;
127Property = "property" IDENT ParamClause Block ; (* + #[metamorphic] attr *)
128DifferentialTest = "differential_test" IDENT "{" CommaList( IDENT ":" Expr ) "}" ;
129Experiment = "experiment" IDENT "on" Path "{" CommaList( ExpEntry ) "}" ;
130ExpEntry = IDENT ":" ( Expr | SweepLit | "{" CommaList( IDENT ":" Expr ) "}"
131 | "[" CommaList( Expr ) "]" | EvidenceLit ) ;
132SweepLit = "sweep" "{" CommaList( IDENT ":" ( "[" CommaList(ConstExpr) "]" | RangeConst ) ) "}" ;
133EvidenceLit = Path "<" CommaList( EvidenceArg ) ">" ;
134EvidenceArg = QuantityLit | IDENT | IDENT "=" Literal | STRING_LITERAL ;
135
136(* ---------- 4. Attributes: the committed Meta micro-grammar (16 §9) ---------- *)
137Attribute = "#" "[" Meta "]" ;
138Meta = Path [ "(" CommaList( MetaItem ) ")" ] ;
139MetaItem = Meta | IDENT "=" MetaValue | Predicate | Literal ;
140MetaValue = Literal | QuantityLit | Rate | Path | "derived"
141 | IDENT "(" CommaList( MetaValue ) ")" ;
142
143(* ---------- 5. Statements (16 §5) ---------- *)
144Block = "{" { Stmt } [ Expr ] "}" ;
145Stmt = LetStmt | RegStmt | RegUpdate | MemStmt | ComptimeStmt | GhostStmt
146 | RequiresStmt | DischargeStmt | RequireGuarantee | NarrowStmt | AssertStmt
147 | StageStmt | PortStmt | SequenceStmt | ForStmt | ReturnStmt | RegionStmt
148 | ContractBlock | ResourceItem | Item | ExprStmt | ";" ;
149LetStmt = "let" [ "mut" ] Pattern [ ":" Type ] "=" Expr ";" ;
150RegStmt = [ "retain" ] "reg" IDENT ":" Type [ "reset" "(" Expr ")" ] [ "uninit" ] ";" ;
151 (* D-079: reset mandatory unless no_reset domain or uninit; NO "=" form *)
152RegUpdate = LValue "<-" Expr ";" ; (* next-cycle write; last-write-wins per body *)
153LValue = IDENT { "[" Expr "]" | "." IDENT } ;
154MemStmt = "mem" IDENT ":" "[" Type ";" ConstExpr "]" ";" ;
155ComptimeStmt = "comptime" ( LetStmt | ForStmt | Block
156 | "param" IDENT ":" Type "in" RangeConst ";"
157 | "fixed" IDENT ":" Type "=" Expr ";" ) ;
158GhostStmt = "ghost" LetStmt ;
159RequiresStmt = "requires" ("static" | "solve" | "prove") [ IDENT [ ParamClause ] ]
160 "{" Predicate "}" ";" ; (* name mandatory on solve/prove;
161 ";" canonical, "," accepted + fmt-normalized *)
162DischargeStmt = "discharge" IDENT "by" IDENT "(" CommaList( IDENT | Expr ) ")" ";" ;
163RequireGuarantee = "require" "guarantee" Path "with" "evidence" ">=" Path ";" ;
164NarrowStmt = "narrow" "!" "(" Predicate ")" ";" ;
165AssertStmt = "assert" Expr ";" ; (* ghost/test phase only *)
166StageStmt = "stage" [ LABEL ] ";" ;
167PortStmt = "port" IDENT ParamClause [ "->" Type ] ( Block | ";" ) ;
168SequenceStmt = "sequence" Block ; (* await/halt/loop legal inside *)
169ForStmt = "for" Pattern "in" Expr Block ;
170ReturnStmt = "return" [ Expr ] ";" ;
171RegionStmt = ( "elastic" | "cycleobs" ) Block ;
172ContractBlock = "contract" [ IDENT ] "{" CommaList( ContractEntry ) "}" ;
173ContractEntry = "assume" IDENT ":" Predicate
174 | "guarantee" IDENT ":" Predicate [ "with" "evidence" "[" CommaList(EvidenceLit) "]" ]
175 | "claim" IDENT ":" EvidenceLit
176 | IDENT ":" ( Expr | EvidenceLit ) ; (* flavored: cdc / death entries *)
177ExprStmt = Expr ";" | BlockLikeExpr ; (* match/if/for/domain/blocks need no ";" *)
178
179(* ---------- 6. Expressions (16 §6) ---------- *)
180Expr = AssignExpr ;
181AssignExpr = RangeExpr [ "=" AssignExpr ] ; (* lvalue-checked post-parse *)
182RangeExpr = OrExpr [ ( ".." | "..=" ) OrExpr ] | ( ".." | "..=" ) OrExpr ;
183OrExpr = AndExpr { "||" AndExpr } ;
184AndExpr = CmpExpr { "&&" CmpExpr } ;
185CmpExpr = ConcatExpr [ CmpOp ConcatExpr ] | ConcatExpr "is" VariantSet ;
186CmpOp = "==" | "!=" | "<" | "<=" | ">" | ">=" ; (* ">"/" >=" glued *)
187VariantSet = Path { "|" Path } ;
188ConcatExpr = ShiftExpr { "++" ShiftExpr } ;
189ShiftExpr = AddExpr { ( "<<" | ">>" ) AddExpr } ;
190AddExpr = MulExpr { ( "+" | "-" ) MulExpr } ;
191MulExpr = UnaryExpr { "*" UnaryExpr | ( "div" | "mod" ) ConstAtom } ;
192 (* THE PRESBURGER FENCE (R7): div/mod right operand is a ConstAtom
193 by grammar; const-ness of a Path is a resolution check *)
194ConstAtom = INT_LITERAL | Path ;
195UnaryExpr = ( "!" | "-" ) UnaryExpr | PostfixExpr ;
196PostfixExpr = PrimaryExpr { Postfix } ;
197Postfix = "." IDENT [ Turbofish ] [ CallArgs ] | Turbofish CallArgs | CallArgs
198 | "[" Expr "]" | "?" ;
199Turbofish = "::" "<" CommaList( GenericArg ) ">" ; (* mandatory in expr position *)
200CallArgs = "(" CommaList( Expr | ClosureExpr | IDENT "=" Expr ) ")" ;
201 (* IDENT "=" only in builtin forms: accept(ready = e), policy keys, measures *)
202PrimaryExpr = Literal | QuantityLit (* quantity positions only *)
203 | Path | Path StructLiteralBody (* restricted: see CondExpr *)
204 | "(" CommaList( Expr ) ")" | "[" CommaList( Expr ) "]"
205 | Block | IfExpr | IfLetExpr | MatchExpr | LoopExpr
206 | InstExpr | HandshakeExpr | ArbitrateExpr | DomainExpr | ProcessExpr
207 | AwaitExpr | "halt" | ClosureExpr | UnsafeExpr | MacroExpr
208 | LABEL [ ("+"|"-") ConstAtom ] ; (* event expr: 'G + 2 *)
209Path = IDENT { "::" IDENT } ;
210StructLiteralBody = "{" CommaList( IDENT [ ":" Expr ] | ".." Expr ) "}" ; (* punning + FRU *)
211InstExpr = "inst" Path [ Turbofish ] StructLiteralBody ; (* named-only + punning *)
212HandshakeExpr = "handshake" Path Turbofish
213 ( StructLiteralBody | "from" Expr ) ; (* core G11 *)
214ArbitrateExpr = "arbitrate" [ IDENT ] "{" CommaList( ArbEntry ) "}" ;
215ArbEntry = "inputs" ":" Expr | "eligibility" ":" ClosureExpr
216 | "policy" ":" Expr | "resource" ":" Expr
217 | "guarantee" IDENT ":" Predicate
218 | "liveness" IDENT ":" Predicate [ "under" Expr ] ; (* names mandatory, G-S23 *)
219DomainExpr = "domain" LABEL Block ; (* an expression *)
220ProcessExpr = "process" "{" CommaList( IDENT ":" ( Stmt-fragment | Expr ) ) "}" ;
221 (* step: <reg-update>, done: Bool, ok: S, err: Option<F> — core G23 *)
222AwaitExpr = "await" Expr ; (* inside sequence only *)
223LoopExpr = "loop" Block ;
224IfExpr = "if" CondExpr Block [ "else" ( IfExpr | Block ) ] ;
225IfLetExpr = "if" "let" Pattern "=" CondExpr Block [ "else" Block ] ;
226MatchExpr = "match" CondExpr "{" CommaList( MatchArm ) "}" ;
227MatchArm = { Attribute } Pattern [ "if" CondExpr ] "=>" Expr ;
228CondExpr = Expr ; (* StructLiteralBody FORBIDDEN at top level (D-080);
229 transitively through operators, not through parens/args *)
230ClosureExpr = "|" CommaList( Pattern [ ":" Type ] ) "|" Expr ;
231UnsafeExpr = "unsafe" "(" IDENT [ "," "reason" "=" STRING_LITERAL ] ")" Block ;
232MacroExpr = IDENT "!" "{" CommaList( MacroArm | IDENT ":" Expr ) "}" ;
233MacroArm = INT_LITERAL "=>" Expr ; (* weighted! / config! *)
234Literal = INT_LITERAL | BOOL_LITERAL | STRING_LITERAL ;
235
236(* ---------- 7. Predicates ---------- *)
237Predicate = Expr | "if" Expr "{" Predicate "}" ;
238 (* implication sugar, predicate position only (R11): !cond || pred.
239 Predicates share the Expr grammar (one precedence table); tier
240 membership is semantic EXCEPT the div/mod fence, which is syntactic.
241 Interpreted symbols (closed): clog2 max min prev accepted
242 transferred outstanding same_epoch. *)
243
244(* ---------- 8. Types (16 §7) ---------- *)
245Type = [ "stamped" ] [ LABEL ] TypeCore [ "@" EventExpr ] (* T @ 'E adopted *)
246 | "&" Type ; (* port-surface borrows, ghost borrows *)
247TypeCore = Path [ GenericArgs ] [ TypeConfigBlock ]
248 | "(" CommaList( Type ) ")"
249 | "[" Type ";" ConstExpr "]"
250 | "fn" "(" CommaList( Type ) ")" [ "->" Type ] ; (* comptime-phase only *)
251TypeConfigBlock= "{" CommaList( IDENT [ ":" ConstExpr ] ) "}" ; (* schema config; TYPE POSITION ONLY *)
252EventExpr = LABEL [ ("+"|"-") ConstAtom ] ;
253GenericArgs = "<" CommaList( GenericArg ) ">" ;
254GenericArg = IDENT "=" ConstExpr | RangeConst | TypeOrConst | LABEL ;
255TypeOrConst = Type | ConstExpr ; (* unified on Path prefix; elaboration sorts *)
256RangeConst = [ ConstExpr ] ( ".." | "..=" ) ConstExpr ;
257(* Angle-bracket sub-language: NO comparisons, shifts, "&&"/"||", struct literals
258 inside "<...>" — ">" always closes, "," always separates; no symbol table,
259 no brace fallback (D-080). *)
260ConstExpr = ConstMul { ("+" | "-") ConstMul } ; (* "-" = monus at type level *)
261ConstMul = ConstPrimary { "*" ConstPrimary | ("div" | "mod") ConstAtom } ;
262ConstPrimary = INT_LITERAL | Path [ GenericArgs ]
263 | ("clog2" | "max" | "min") "(" CommaList( ConstExpr ) ")"
264 | "(" ConstExpr ")" ;
265
266(* ---------- 9. Patterns (16 §8) ---------- *)
267Pattern = PatternNoAlt { "|" PatternNoAlt } ;
268PatternNoAlt = "_" | Literal
269 | INT_LITERAL ("..=" | "..") INT_LITERAL
270 | Path [ "(" CommaList( Pattern ) ")" | "{" CommaList( IDENT [ ":" Pattern ] | ".." ) "}" ]
271 | "(" CommaList( Pattern ) ")" | "mut" IDENT ;
272
273(* ---------- 10. Resilient-LL anchors (informative) ---------- *)
274(* Every ItemKind and most statements open with a unique hard keyword; the
275 item-recovery set is FIRST(Item), the statement set adds the statement
276 openers + ";" "}". ERROR nodes wrap skipped tokens; MISSING tokens are
277 zero-width; text(tree) == input on ALL inputs (D-062). *)

Register

D-077 (charter, SETTLED — Veronica), D-078 (this spec, DRAFT-COMPLETE), D-079 (reg forms, SETTLED), D-080 (grammar-evidence decisions, SETTLED) in decisions.md. Remaining gates before D-078 settles: the fmt dry-run (D-049–D-053 over both corpora, §13 rules) and the blind-regeneration pass (a fresh agent rewrites the corpora from this document alone; divergence measures spec completeness).