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.
nowis 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, andleastandmost(the smallest and largest number, quantity, money or date; for ranked choices, usebestandworst).overandunderon 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. Atmustandshould, 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: derivedand the rule as itsvia. - 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 whenais 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:
- Layered. Draw an edge from every address a rule reads (in
whenor on the right ofthen) 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 readsparts[*].conditionand producesconditionon the whole is not a cycle. - No contradictions. Two
mustrules that can apply to the same record MUST NOT require disjoint values of the same key. Implementations MUST detect this at least foris,inand range tests whosewhenconditions are both satisfiable at once; they MAY detect more. - 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.