spec/vectors/niadra-expr.v0.json: the server and both SDKs run the same file.
Field types and how values travel
An event’sfields and an object push carry each value as JSON, in the form its declared type expects:
A value of another kind is kept as sent, but an expression over the field cannot read it as its type: a money amount sent as the string
"38000.00" is not a number to a reading, a derived field or a timer. A field the type does not declare is kept as a plain field and opens a drift data issue, never a refusal. What an event says about itself (operation, version, last_event_type, source_version, record_ref, input_refs) is bookkeeping: never drift, and served only when the type declares it as a field. Every key of an event’s fields applies to every object in its object_refs, so send one object per event.
What a field declares
A
plain field declares no observers and no unobserved_blocks. A change of a field’s sensitivity, access or pii, of a source’s licence or of the type’s purposes is a privacy decision: it also needs the role that answers for security.
What the server refuses in a type
Beyond the shape of the document, a type is refused, with the path of each entry that breaks a rule (for exampletimers.launch_at.on_fire: transition(scheduled->actve) is not a transition of the lifecycle), when:
- A field, a value, a time axis or a derived field takes a name another one already has, or a name the language reserves (below).
- A state the lifecycle, a timer or a vocabulary names is not declared; a
timers_onnames an undeclared axis; a content field,completeness.total_field, a known defect, aunionkey or arefreshaction names an undeclared field or value;prefer_source,use_source,quote()or a vocabulary names an undeclared source; a derived field names an undeclared relation, or one whose type the registry does not declare, or aminormaxover a field that type does not have. - A derived axis does not name its rule, or a rule is on an axis that is not derived; a type derived by introspection lost its fingerprint.
- A member breaks its own rule: a
plainfield that declares observers, aclaim_max_ageovermax_age, and the rules of sources, the lifecycle, timers, refetch and purposes that Object types and state describes. - An expression does not parse or does not resolve against the type: conditions (
when,active_when,where) must give true or false, and a timer’sduea date or a datetime.
niadra-expr
The language is small, deterministic and total: no loops, no recursion, no I/O and no clock but the one it is given, bounded text and nesting, and a result or one of three errors for every input. The same text gives the same result on the server, in each SDK and in your resolver worker.Tokens
- Spaces, tabs and line breaks separate tokens.
- A name is
[a-z_][a-z0-9_]*, at most 64 characters, lowercase ASCII. - A number is digits with an optional
.and at least one digit after it; there is no sign, a negative number comes from subtraction. - A duration is a whole number of at most six digits followed by
ms,s,min,hord(mis not a unit, and1.5his written90min). - A string is in single quotes, on one line, with
\'and\\as its only escapes, at most 256 characters. - The operators are
==,!=,<,<=,>,>=,+,-,(,),[,],,and.; the keywords areand,or,not,in,true,false,none,yes,no,unobservedandknown_defect.
Grammar
or binds loosest, then and, not, a comparison, + and - (left to right), and .. Comparisons do not chain: a < b < c is an error. A dotted name such as lead.city is one name with parts; after a group or a call, . reads a field of a quote. The logical words appear only on one side of == or !=.
Values and the four logical values
unobserved and known_defect are unknown. A present value is a boolean, a number, a string, a duration (whole milliseconds), a date, a datetime (to the millisecond) or a list. Not observed is never false: a comparison with an unknown value is unknown, and so is its negation.
Names
A one-part name is, in order: a context name (state, the lifecycle state; derived_status; watch_count, 0 by default; purpose, the read’s purpose, display by default); a field, a computed value or a time axis of the object; a declared absence name; otherwise a field nobody observed, unobserved. A dotted name is a declared input of that path (lead.city), config.<key> (the company’s configuration value, absent when not set) or <field>.completeness. The keywords and state, derived_status, watch_count, purpose and config are reserved. A state is a string: state == 'open', never state == open.
Operators
x == yesis true exactly whenxcarriesyes, and likewise for the other logical words; the result is always true or false:available == unobservedasks whether a value was observed.- Otherwise, when either side of a comparison,
+or-is unknown, the result is unknown (known_defectwhen either carries it). - Equality: two absences are equal when either has no name or both have the same one (
noneis any absence); an absence never equals a present value; numbers by value, strings by their characters, durations by length, dates and datetimes as instants (a date is the start of its day), lists item by item; any other pair isexpr_type. - Order (
<,<=,>,>=) is false when either side is absent; numbers, strings (by code point), durations, dates and datetimes compare; any other kind isexpr_type. intakes a list on its right: true when the left equals an item, unknown when an unknown item could decide, false otherwise.and,orandnottake true, false or unknown, with three-valued logic, left to right, and the first operand that decides stops the evaluation:false and xnever evaluatesx.+and-: number with number; duration with duration; a date or datetime plus or minus a duration gives a datetime; a date plus or minus business days gives a date; two dates or datetimes subtracted give a duration. An absent operand gives an absence.
Functions
The deadlines of a court, a contract or a product are your company’s rules, as declared values: the language only counts simple business days.
Errors and bounds
An expression has at most 1,024 characters, 256 tokens and 16 nested groups; a list literal at most 64 items, a string at most 256 characters, a name at most 64 and a duration at most six digits. A list read from a slot has at most 1,000 items, and a business-day count is at most 1,000.
Next steps
Object types and state
What a type is for, freshness, the read purpose and shared objects.
Typed reads
POST /v1/state/read: fields, values, readings and timers.
