CRUBAL

6. Rules

A rule says what must, should or probably is true, and what follows from it. Rules are pure: they read values and return verdicts or proposed values. They never act. Anything that moves values or touches the world is a contract (chapter 7).

6.1 The shape of a rule #

rule: dvdIsMpeg2
  for: OpticalDisc
  when: format is DVD
  then: videoCodec is MPEG-2
  strength: suggest
  because: DVD-Video is MPEG-2 by standard
  version: 2026-10-13
Part Holds Default
for where the rule applies: a class (its wearers), a kind, a level (unit::), or everywhere everywhere
when a condition (6.3); the rule applies only where it holds always
then the consequence (6.4) required
strength must, should or suggest (6.2) required
because the reason shown to people when the rule refuses, flags or fills none
version the dated version required when published

Rules are named like keys and versioned like facets (chapter 4.1). A rule expanded from a shortcut (chapter 4.4) or a class line (chapter 5.3.1) is named by where it came from: title: required, DvdOnly: format{DVD}.

6.2 Strengths #

Strength A write that breaks it A value it needs that is empty A value it can produce
must refused, with the reason the record is incomplete derived, and a person may not adopt it
should saved and flagged flagged derived; adopting it is flagged
suggest nothing; a person's value wins filled, with the rule as its source derived; a person may adopt it freely

"A write that breaks it" means a write to a value the rule's then tests. A rule whose verdict changes because another value changed never deletes anything: see 6.7.

6.3 Conditions #

condition = term *( " and " term / " or " term )   ; "and" binds tighter than "or"
term      = [ "not " ] ( test / "(" condition ")" )
test      = path " is " value                      ; equals
          / path " is _" / path " is -"            ; is unknown · does not apply
          / path " in " values                     ; one of: "in DVD, Blu-ray"
          / path " in " number ".." number         ; inside a range, ends included
          / path " has" [ " " value ]              ; has a value · the list holds this item
          / path ( " before " / " after " ) value  ; dates, moments, times of day, or now
          / path ( " over " / " under " ) value    ; numbers, quantities, money, ranked choices
          / path " is unique"                      ; global (6.6); in "then" only
          / path " shown to " audience             ; visibility (chapter 12.4); in "then" only
          / path " is " state                      ; a value's state (6.3.2)
path      = address / aggregate
aggregate = combine " of " address                 ; "worst of parts[*].condition"
          / ( "count" / "least" / "most" ) " of " address   ; how many · smallest · largest
  • Addresses are chapter 1.10's, plus the wildcard [*] for every row or part, and the level prefix (unit::condition) to read a lower level from above.
  • Values are written as in the text form (chapter 2), read by the facet at the path.
  • now is the moment the rule is evaluated, supplied by the caller so results stay reproducible: when: auctionEnds before now. Moments compare by instant; a local date or time of day compared with a moment is undetermined; a plain date compared with a moment compares by the moment's UTC date.
  • Aggregates use the combine rules (chapter 4.4), count, and least and most (the smallest and largest number, quantity, money or date; for ranked choices, use best and worst). over and under on a ranked choice compare positions in the list: earlier is higher.

6.3.1 Empties in conditions #

A test on an absent or unknown value is undetermined. A test on does not apply (-) is false, except is -, which is true.

and true false undetermined
true true false undetermined
false false false false
undetermined undetermined false undetermined

or is the mirror (true wins), and not undetermined is undetermined. has, is _ and is - are never undetermined: they test the empties themselves. is unique holds for an empty value: empties are never compared with other records.

A rule whose when is false or undetermined doesn't apply. A must rule whose then is undetermined makes the record incomplete; a should rule's flags it; a suggest rule's fills it when it can.

6.3.2 Value states #

A condition MAY test the state of a value, not only the value: is confirmed, is contradicted, is unobserved (chapter 9.7.2), is conflicted (it has alternatives, chapter 8.4), is new (a value the vocabulary doesn't hold yet), is held (6.7), is adopted (chapter 8.2) and is suggested (it has suggestions). State tests are never undetermined.

They are what lets contracts form a cascade: cheap sources first, expensive ones only when needed.

contract: aiTitle
  kind: receive
  when: title is conflicted or not title has

6.4 Consequences #

then is a condition (6.3) that should hold, read in one of two ways:

A check. Any test. It holds or it breaks. then: runtime in 1..600, then: not hdrFormats has.

A production. A test of the form key is value produces that value when the key is empty:

Right side Kind Example
a plain value a fill then: videoCodec is MPEG-2
a template a derivation then: listingTitle is "{title} [{format}]"
an aggregate a rollup then: condition is worst of parts[*].condition
  • A fill produces only at suggest. At must and should, a plain value is a check.
  • A derivation or rollup produces at every strength. The engine writes it, recomputes it whenever what it reads changes, and stores it with origin: derived and the rule as its via.
  • A production writes only at a level where its key is stored: the top level, or a level where the key varies (chapter 1.7). Below that, records read the produced value through, as they read any other.
  • A person who edits a produced value adopts it (✎, chapter 8): the value stops following the rule until it is given back. The strength says whether adopting is allowed (6.2).

6.4.1 Templates #

A template is a quoted text with placeholders: {address} is replaced by the value there, printed as in the text form without its detail. {{ and }} write a literal brace.

  • {a ?? b ?? c} takes the first of them that has a value.
  • {a?} is replaced by nothing when a is empty, and a space or one of · , - : written right before it is dropped with it: "{title} · {subtitle?}" is "Vacation" when there is no subtitle.
  • If any other placeholder's value is empty, the template produces nothing. Templates are the only computation inside rules: arithmetic, lookups and AI belong to contracts.

6.5 What rules are for #

Every one of these is a rule of the shape above; no other mechanism exists.

Use Example
a check then: runtime in 1..600 · should
a requirement then: title has · must
forbidding when: format is DVD then: not hdrFormats has · must
narrowing for: OpticalDisc then: format in DVD, Blu-ray, 4K · must
a gate when: not format in Blu-ray, 4K then: hdrFormats is - · must
a fill when: format is DVD then: videoCodec is MPEG-2 · suggest
a derivation then: listingTitle is "{title} [{format}]" · should
a rollup then: condition is worst of parts[*].condition · must
a global rule then: gtin is unique · must

6.6 Global rules #

A global rule reads other records: is unique (no other record in the scope holds the same value) and catalog membership (chapter 12). Its scope is the rule's for. Global rules are their own class of rule because they need indexes; a conforming store at the Rules level MUST enforce must-strength global rules at write.

6.7 When a verdict changes #

A rule's verdict can change without anyone writing the value it tests: a gate closes because format changed, a new rule version narrows a list. Then the engine never deletes and never refuses the unrelated write. The affected value is held: kept, hidden from forms and pages, and flagged with the rule. It comes back if the verdict changes again, and a person or a migration recipe (chapter 10) can resolve it.

6.8 Rule sets #

The rules that apply to a record are those whose for it matches, from every class it wears, every facet shortcut, and the package. A package MUST be refused at publish when its rule set breaks any of these:

  1. Layered. Draw an edge from every address a rule reads (in when or on the right of then) to the address it produces. The graph MUST have no cycle: no rule depends on its own effect, and no two fills chase each other. Addresses, not bare keys: a rollup that reads parts[*].condition and produces condition on the whole is not a cycle.
  2. No contradictions. Two must rules that can apply to the same record MUST NOT require disjoint values of the same key. Implementations MUST detect this at least for is, in and range tests whose when conditions are both satisfiable at once; they MAY detect more.
  3. One spelling. A shortcut and a rule written in full that say the same thing MUST NOT both be present.

Order. Evaluation order never changes a result, except where a replace combine meets contributions in order (chapter 4.4). An implementation MUST say which order it used in that case.

Traceable. Every refusal, flag, fill, derivation and hold names its rule, and every produced value's source names it as via.

6.9 Results #

After a write, a record's rule results are:

Result Means
complete for each class it wears: no must rule is undetermined or broken
completeness a number from 0 to 1: the asked facets that have a value, divided by all asked facets. Unknown counts as empty, does-not-apply as answered. Parts are measured on themselves and never added to the whole's count, so a five-disc set isn't less complete than one disc for the same answers
incomplete a must rule needs a value that is empty; lists the rules
flagged a should rule broke or is undetermined; lists the rules and addresses
filled · derived values the rules produced, with their sources
held values hidden by a changed verdict (6.7)

A write that breaks a must rule is not a result: it is refused, and nothing changes.

6.10 The JSON form #

{
  "rule": "dvdIsMpeg2",
  "for": "OpticalDisc",
  "when": "format is DVD",
  "then": "videoCodec is MPEG-2",
  "strength": "suggest",
  "because": "DVD-Video is MPEG-2 by standard",
  "version": "2026-10-13"
}

Conditions are stored as their text (6.3), so there is one grammar for them, not two.

6.11 Open #

  • Events versus current values.
  • Which rules a store compiles into its database and which it checks in the application is an implementation choice; the results MUST be the same.

Edit on GitHub · Markdown