Nodes and transitions
A node is one place a participant can be in a journey; a transition is a one-way way out of it. Nodes carry no content, only actions to run on arrival and the transitions out.
start standard answered, end
┌─────────┐ always ┌─────────────┐ if ≥ 10 ┌─────────┐
│ welcome │─────────────►│ sleep-check │─────────────►│ support │
└─────────┘ └─────────────┘ priority 0 └─────────┘
│ answered, priority 1
▼
┌──────────┐
│ week-one │
└──────────┘
Nodes
idis the wiring. Transitions point at it, so changing it breaks every transition into the node.nameis the label, free to change.typeisstart(where an assignment begins),standard, orend(reaching it completes the journey; its transitions are never read). Beyond that the types are markers: a transition may lead into a start, and one out of an end is accepted and ignored.- Loops are allowed. A node may point back at itself or an earlier node; give the way back a When that needs something from the participant, so each lap waits for it.
Transitions
A transition is when something happens, if something holds, go to a node:
{ "toNodeId": "support",
"when": [{ "source": "sequenceResponses.SLEEP_CHECK", "operator": "answered" }],
"if": { "source": "sequenceResponses.SLEEP_CHECK.latest.scores.TOTAL", "operator": "at_least", "value": 10 } }
Both are made of the same clause: a source (a path naming the one thing, by
code), an operator, and a value only where it compares what the source holds.
when lists what makes the transition look; any one is enough. Its clauses take event
operators: what has just happened to the source, or the time it has reached.
| Source | Operator | Looks |
|---|---|---|
journeys.current | checked | On arriving, and every time the journey looks again |
sequenceResponses.<sequence> | answered | Each time that sequence is answered while they are here |
atomResponses.<atom> | answered | Each time that atom is answered, in a sequence or on its own |
time.inNode, time.inJourney | reached, with a value in seconds | From that long after arriving at the node, or after the journey started |
A date (extras.QUIT_DATE.latest, a date answer) | reached | From the date and time it holds |
A date's days (extras.QUIT_DATE.latest.daysUntil) | reached, with a value | When the days come to it |
tags.<tag>, groups.<group>, relationships.<relationship>, cohorts.<cohort> | created, deleted | When its assignment to the person is created or deleted (shown as "assignment created", "assignment deleted") |
extras.<extra>.latest | set, cleared, changed | When the extra is given a value, cleared or given another |
if is a condition of state operators that must hold for the
transition to be taken; without one, the When alone decides. Things are named by their code
(Referencing a participant's data).
- Order decides. Transitions with a
priorityare checked first, lowest first; the rest follow in the order written. The first whose When looks and whose If holds is taken, so put the specific case before the general one: acheckedWhen with no If first swallows every one after it. - A
checkedWhen with no If written last is the "otherwise". - A journey looks again at the next moment anything could change: a time or date coming due, a reading arriving, the hour or the day turning. A wait is the node's clock reaching it.
Checks
| Check | When |
|---|---|
duplicate_node_id, duplicate_action_id, transition_target_missing, a wait shorter than the floor, and journey_too_large (more than 500 nodes, 50 transitions from one node, 32 levels of nesting or 500 clauses in one rule) | Every save, draft included: the save is refused |
start_node_missing, multiple_start_nodes, dead_end_node (a node that isn't end and has no way out) | Errors: a version read back lists them, and publishing is refused while there are any |
A path or token that doesn't read, or can't be used where it is written (unknown_path, not_here, personal_in_external, …) | Errors, the same way |
node_unreachable, journey_empty, since_active_without_node_wait | Warnings: listed, never blocking |