diff --git a/docs/spec/entities.md b/docs/spec/entities.md index 8e8b231..6ea737a 100644 --- a/docs/spec/entities.md +++ b/docs/spec/entities.md @@ -325,6 +325,8 @@ One outcome would have fitted exactly one real rule. Negative stock wants the wr The total is recomputed from the guarded entity's own rows for the incoming record's key-tuple - excluding the record being updated - rather than read from the materialised aggregate, so the decision cannot race the aggregate's maintenance. The guarded entity must be the aggregate's own source. +A record with any of the aggregate's grouping keys unset **is not guarded at all**. It belongs to no key-tuple - the aggregate itself ignores such a row, and materialises no target row for it - so it contributes to no total and can breach no minimum: the write proceeds, nothing is refused, a `marker` reads as holding and a `setStatus` is not written. This is the guard's half of a rule the aggregate already states, and it keeps the two a pair of computations of the same total rather than of two different ones. A grouping key that must always be there is declared `required`, which is the better statement where it is true. + `outcome: task` stamps a flag; it does not create or route to a task. A workflow [decision](/spec/processes#decision-steps) reads the marker and routes the record - the two constructs compose, and the guard is the part that computes. ## Role-scoped field visibility — `visibleTo` diff --git a/docs/spec/glue.md b/docs/spec/glue.md index 44a9b7e..495ecdb 100644 --- a/docs/spec/glue.md +++ b/docs/spec/glue.md @@ -695,6 +695,8 @@ postings: - { Account: rule(receivableAccount), credit: "Amount" } ``` +`rule.match` is a single `column: literal` selector, and the literal has to say something: it is matched against the rule row's own column as authored, so a blank one looks up the empty value, matches no rule row, and sends *every* source document to the unposted worklist while the model, the generation and the deployment all stay healthy. An absent or blank match value is an authoring error, reported when the intent is read. A rule *row* whose match column is empty is a different thing - an ordinary row of the rule table, matched by no non-blank literal. + ### Conditional rule column When the account column must be chosen by a **source value** — a payment posts to the bank account for a transfer, the cash account for cash — a single item row selects the rule column by a classifier instead of duplicating the row per case (the same `by` / `cases` / `default` shape a conditional value-copy uses). Quote it, since it carries colons and braces: