Policies
Today, what restricts an action is written as guards: a skill's select guards, and the
station's submit guard. Core applies them whatever the model says. Other keys restrict too:
depends_on and on_loss (a skill's item can be chosen only once the part it depends on holds something),
and the tasks of the open playbook: their order (after), which refuses a choice made too early,
their choices (one_of) and conditions (when), which refuse a method that is not the one, and
the required tasks, which block the submit. There are no reusable
policies, permissions, budgets or smart policies yet
(Policies).
Select guards: before an item enters the selection
select:
max_items: 1 # one only: choosing another replaces the first
guards: # an ordered list; each guard is ONE of these four
- form: # what the PERSON fills in, in the card
address: {type: text, required: true, label: "Delivery address"}
zone: {type: text, required: true, enum: [dakar, thies, rufisque]}
- name: zone-desservie # a condition that must hold
require: "$item.fields.zone in ['dakar', 'thies']"
reason: "We do not deliver to Rufisque yet."
- check: checkDeliverable # the tenant says whether the item is eligible
- update: {price: getDeliveryPrice} # the tenant computes the price for THIS request
- {check: checkThiesRoute, when: "$item.fields.zone == 'thies'"}
| guard | does | phase |
|---|---|---|
form | values the person gives; they become the item's fields | 3 |
require | a condition that must hold (see Conditions) | 3 |
check | the tenant's operation says whether the item is eligible | 5 |
update | the tenant's operation sets a value of the item; only price exists | 6 |
Core runs them by phase, and within a phase in the order written: first what the person
answers and the station's own conditions, then what calls the tenant. So the tenant is never
called for an item already refused. A refused item does not enter. A check's reason is the
tenant's; the values the person typed are replaced in it by their field's name.
- At most one
formand at most oneupdatein the list; a guard that names two of the four, or none, refuses the station at load. max_itemssays how many items the skill's part may hold. It is on the skill only:kinds.<k>.selectholdsguardsalone ({}means "no guard").- Every guard may carry
when:: it applies only when the condition holds. Aformwhosewhenis false for an item is not asked for that item. - A
requirealso carriesname(what its refusal is called, unique in the list) andreason(the sentence the person and the agent read). Withoutreason:this choice does not meet the condition '<name>', orthis choice does not meet a condition of this offerwithoutname.nameandreasonare written on arequireonly. - A
requirealone is no step on the card: Core adds the item, or says why not. It does not make the line a one-of choice and does not remove the −/+ buttons; it runs on every add.
Each form field has a type, required, taken from the table of types;
a type outside the table refuses the station at load, naming the field. Its other keys: required,
label, placeholder, enum (a closed list of answers) and identifies. Core checks each answer
by its type: a price expects a number, a quantity a whole number.
The form values become the item's fields: $item.fields.address. A field marked
identifies: true also identifies the room, which no longer asks for it (Auth).
nameA name field overwrites the item's displayed name: the delivery line would show the customer's
name instead of "Tomorrow 8am-12pm". Write customer_name.
Conditions: require and when
A condition is a text Core reads itself, against a closed grammar; it is never run as code.
| write | meaning |
|---|---|
==, !=, <, <=, >, >= | compare |
in, not in | in a list ([...], exact), or in a text (ignoring case) |
and, or, not, ( ) | combine |
5, 'text' or "text", true, false, null, ['a', 'b'] | values |
$item.fields.<name> | a field of the item: declared in present.fields, or asked by this select's form |
$item.price, $item.price_cadence, $item.ref, $item.kind, $item.id | what the item itself carries |
- Refused at load: a syntax error; any other reference (
$params…,identity…,$fields…, an undeclared field); a bare value (5,'yes'), which is not a condition; more than 500 characters, 16 levels, 50 list items or 18 digits. - A condition reads a form field only after the
formguard in the list, and aform'swhendoes not read its own fields. Only the answers to the form's fields enter the item: a value under another name is not kept. - No
$item.pricebeside anupdate: theupdatesets the price last, so a condition would read the price from before the quote. No$item.quantity: a guard sees the quantity of one add, not the line's. - Values.
trueis never a number. A number written as text is a number ("460" > 0).==and!=are exact:149and"149"differ. An absent value equalsnull:!=andnot inare therefore true for it ($item.fields.country != 'US'admits an item with no country; write$item.fields.country != null and …to require one). - What cannot be known. An absent or unreadable value is not compared with
<,>… Then Core fails closed: arequirenot known to hold refuses; awhenthat cannot be known applies its guard.
The submit guard: once, before anything
submit_guard:
form:
accept_terms: {type: boolean, required: true, label: "I accept the terms and conditions"}
confirm: true # an explicit yes from the person
It applies to the whole selection, before the first operation. If it fails, nothing is
executed. Core requires the person's confirmation on every submit, whatever the door, and
whether confirm is declared or not.
Who submits
The model cannot submit: it takes the confirmation that Core hands to the person's screen, never to the model. This is not authentication: a script that reads the room gets this confirmation just as the widget does (see Auth). See The submit.