CONDITION_EXPRESSION_GRAMMAR.md

systems/CONDITION_EXPRESSION_GRAMMAR.md

Condition Expression Grammar — the ONE §9.5 conditional mini-grammar

Status: ACTIVE v1.0 · Owner: Build Pipeline (T0.11) · Reference evaluator: harness/condition_expr.py

This document specifies the single conditional expression language used everywhere a

build artifact tests state: spine Block-11 side-questline gates, Block-10b fork and beat

gate expressions, T0_Quest_Definition objective completion_condition and quest-level

completion_condition, and the Dialogue-Dataset line_variant condition_expression.

There is exactly one grammar; every surface was reconciled to it.

produce-before-consume. One mini-grammar: has(local.key), at(site_id), flag(local.key),

ws.key with ==, >=, <=, or in, read(surface), and boolean & and |. Every consumed flag or

local key must be produced by a prior beat's out:."]

line_variant condition_expression is RECONCILED to the single §9.5 grammar so one

expression language covers quest gates, objective completion_conditions, and line-variant

selection, with §9.5 as owner.]

the cross-beat produce-before-consume enforcer. This grammar's evaluator is the per-expression

half.]

1. EBNF

expr        = or_expr ;
or_expr     = and_expr , { "|" , and_expr } ;
and_expr    = unary   , { "&" , unary } ;
unary       = "(" , expr , ")" | predicate ;

predicate   = has | flag | at | read | ws_cmp ;
has         = "has"  , "(" , local_key , ")" ;
flag        = "flag" , "(" , local_key , ")" ;
at          = "at"   , "(" , site_id   , ")" ;
read        = "read" , "(" , surface   , ")" ;
ws_cmp      = ws_key , ( "==" | ">=" | "<=" ) , value
            | ws_key , "in" , "[" , value , { "," , value } , "]" ;

local_key   = "local." , ident ;
ws_key      = "ws." , ident ;
site_id     = ident ;
surface     = ident ;
value       = number | string | enum_member ;
enum_member = ident ;
ident       = letter_or_us , { letter_or_us | digit } ;
number      = [ "-" ] , digit , { digit } , [ "." , digit , { digit } ] ;
string      = '"' , { any_char_except_quote } , '"'
            | "'" , { any_char_except_quote } , "'" ;

2. The five predicate surfaces

executed corpus this reads produced narrative or item state (for example

has(local.naga_contract_offered), docs/spine/CH_03.md:186).

same local. namespace and same produce-before-consume doctrine. The contract names both;

production spine uses has() predominantly and reserves flag() for pure booleans (for

example flag(local.bargain_refused), the §9.5 completion_condition example).

at(wayang_performance_ground), docs/spine/CH_04.md:195). The site_id is NOT registry-

validated here; site resolution is check_spine_graph.py's §3b concern.

read (for example read(surface), docs/proposals/quest_spike/CH_04_quests.md:224).

T0_Worldstate_Variables variable_name; the value is domain-checked (Section 4). Example:

ws.path_bucket == EVIL (docs/spine/CH_75.md:200).

3. Boolean composition and precedence

has(local.oracle_bones_decoded) & has(local.triage_underway) (docs/spine/CH_21.md:199)

and at(meroe_pyramid_field) | at(gebel_barkal) (docs/spine/CH_14.md:200).

no precedence rule. This grammar binds & tighter than | (standard boolean algebra), so

a & b | c parses as (a & b) | c. Recommendation to Josh / schema owner: ratify this, or

ratify a parentheses-mandatory rule; do not leave it implicit, because a C++ port with a

different default would silently invert live gates.

the corpus. IMPLEMENTED here as an explicit grouping form so precedence is never ambiguous

and the port is unambiguous. Corpus authors need not use them; they exist so an author CAN

disambiguate.

recurses per ( level, so an unbounded grammar would leak an uncaught RecursionError (and

SEGFAULT the C++ port, which has no default recursion limit) on adversarial/malformed input.

Beyond the cap the evaluator raises ConditionSyntaxError("expression nesting too deep").

Real corpus expressions nest one or two levels at most; the port MUST bake the same 64-level

bound and raise the same typed error.

4. ws.key operators and domain rules

DomainError: an enum domain is unordered, so ordering has no defined meaning. (Contract-

named >=/<= are corpus-unattested to date; only == appears in production.)

Contract-named, corpus-unattested; the single-member list in [EVIL] is legal.

DomainError.

not scalar-comparable; any ws. comparison against it is a DomainError. Object state is

read through its named sub-fields at a layer above this grammar, not by a bare comparison.

5. Produce-before-consume doctrine

Every consumed local. key — every has(local.k) and flag(local.k) — must be produced by a

prior beat's out: line within the chapter, or be an explicitly promoted ws. key. This is a

CROSS-BEAT property: a single expression cannot verify it, so the reference evaluator does not.

harness/check_spine_graph.py is the enforcer (it walks beats in seq order and flags any

has()/flag() local key not yet produced). The evaluator exposes

Condition.consumed_local_keys() so a caller can obtain the consume set and perform the check.

6. Reference evaluator — harness/condition_expr.py

file, so it is deliberately boring and explicit: a hand-written tokenizer, a recursive-descent

parser, an AST of small tagged classes, and a pure (AST, state) -> bool evaluator.

value is absent.

inventory (set, for has), flags (set, for flag), location (str or set, for at),

reads (set, for read), ws (dict name -> value, for ws.key).

standalone; load_schema_from_csv(path) loads the full live 42-row

T0_Worldstate_Variables [ACTIVE v0.1] for build-time validation. --self-test --check-registry

confirms every embedded key still resolves in the live registry.

DomainError (domain-illegal value or operator), EvalStateError (runtime state gap).

7. Golden corpus

python harness/condition_expr.py --self-test runs the accept/reject corpus embedded in the

evaluator: real expressions harvested from the repo (each cited to its source line) plus

adversarial cases (operator precedence, parenthesized grouping, quoted strings with spaces, a ws

key that is a prefix of another, single-member in-lists, empty and malformed expressions,

out-of-range and wrong-type values, object-variable comparison, and namespace violations). Every

accepted expression evaluates to its expected boolean against a fixture state; every rejected one

raises its expected typed error.

8. Open items carried to the schema owner

evaluator-only convenience (Section 3).

spot-verified against this evaluator's domain rules (Section 4).

Generated by harness/site/structure_site.py — the URL path is the repo path. review root