Corbie

Machine types

Statechart

A statechart is in one state at a time, and moves to another state when an event or a timer arrives.

Use it for work that waits, loops, branches or reacts to events.

# review.yaml

statechart: review
input: { document: string }
initial: drafting

states:
  drafting:
    job: { script: ./draft.js, input: { document: "${input.document}" } }
    on:
      done: waiting
      failed: gave_up
  waiting:
    ticket: verdict
    timers: { too_long: 48h }
    on:
      verdict:
        - { go: approved, when: "${event.data.ok}" }
        - { go: drafting }
      too_long: gave_up
  approved: { end: done, output: "${states.drafting.output}" }
  gave_up:  { end: failed }

// draft.js

return "A draft of " + input.document
corbie init --ceiling allcorbie start &corbie run submit review.yaml --input '{"document": "the plan"}'corbie run show <id> --ticketscorbie run reply <id> <ticket> '{"ok": true}'  takencorbie run show <id> --wait  output: "A draft of the plan"

review.yaml writes a draft, then waits up to 48 hours for a verdict from outside the run. <id> is the ID that corbie run submit printed, and <ticket> the value that corbie run show <id> --tickets printed.

Waiting for an answer

A ticket is a random value that lets whoever holds it answer once.

  1. On entering waiting, the statechart issues a ticket named verdict.
  2. corbie run show <id> --tickets prints verdict and the ticket's value.
  3. Whoever has the value answers with corbie run reply, from any client.
  4. The answer arrives as the event verdict, with the answer as its data.
  • Only the client that started the run can print the ticket. It hands the value to whoever should answer.
  • A ticket is answered once. Its rule takes only the ticket's answer: an event named verdict from the parent, a child, the client or the state's own job finds no rule. A timer's rule takes only the timer.
  • A statechart run as a child hands the ticket to its parent with a send, such as send: { to: parent, event: review_wanted, data: "${tickets.verdict}" }.

Fields

initialthe first state, needed where there is more than one statesthe states, by name; a name is used once in a statechart

A state has these fields, and at most one of job:, jobs: and states:.

joba script or a machine, started on entry and cancelled on leaving jobsseveral named jobs: the state is done when all are, and its output is each job's output by name statesstates inside this one, with their own initial: ticketthe name of a ticket the state issues while it is active defer[<event>, ...]: events kept while the state is active and no rule takes them, and offered again once no active state defers them timers{ <name>: <time> }: timers started on entry, cancelled on leaving entry{ send: ... }, sent on entering the state exit{ send: ... }, sent on leaving it on{ <event>: transition }: where to go on each event, by its name

A final state has only these fields, so a last send goes on the transition into it.

enddone or failed: the state ends the state it is inside, or the statechart, as done or as failed outputthe result, for the statechart or the state it is inside

The events under on: are these.

donethe state's jobs have finished, or a state inside it has ended faileda job has failed <timer>a timer of the state is due <ticket>the state's ticket is answered <other>an event from the parent, a child or the client, or a value a job emitted

Transitions

A transition is a state's name, or these fields, or a list of either. The first one whose when: is true is taken.

gothe state to go to; with none, the statechart stays and only sends whena condition, one expression: "${event.data.ok}", in quotes between { } sendone send or a list: { to, event, data }, where to: is parent, the state or named job whose job is a machine, or { key, machine }

A send to a key names the run that has the key, as in Definitions.

to: { key: "chat:${event.data.id}" }the run with the key to: { machine: ./chat.yaml, key: "chat:${event.data.id}" }that run, or a new run of the machine
keythe key of the run machinea file or a definition written in place, with no expression; a new run gets the event's data as its input
  • An event that the active state has no transition for goes to the state it is inside, and so on outward.
  • A job's failure that no state has a transition for fails the statechart.
  • Any other event that no state has a transition for is dropped, unless an active state names it under defer:. A chart keeps at most 100 events.
  • An entry: send to the state's own child arrives once the child has started. An exit: send cannot go to it.
  • When a client started the run, a send to parent is only written to the record. That client sends an event with corbie run send <id> <event> <data>.
  • A ticket's name is used once in a statechart.
  • A send cannot be named start, done, failed or timer.
  • to: { key } naming no running run is noted in the record as undelivered.

A timer's time is one of these.

90s, 48h, 7da length of time, from when the state is entered "18:00"a time of day in UTC, at its next occurrence "mon 09:00"a time on one day of the week "09:00 Europe/London"a time of day in a named time zone "${input.at}"one of these, from input, when the state is entered

What an expression reads

inputthe statechart's input runthe run's id and key states.<name>.outputthe state's last output tickets.<name>a ticket of this state, or of a state it is inside eventthe event that caused the transition: { name, data, from }
  the event                    event.data                   event.from
  done                         the job's output             the state
  failed                       { status, error, output }    the state or named job
  a timer                      nothing                      the state
  a ticket's answer            the answer                   the state that issued it
  a value a job emitted        the value                    the state or named job
  an event from a child        its data                     the state or named job
  an event from the parent     its data                     "parent"
  an event from the client     its data                     "client"
  an event sent to the key     its data                     the sender's run ID

The state that a transition enters reads the same event in its job's input, its entry: and its output:. A final state returns a ticket's answer with output: "${event.data}".

A statechart has no variables. To build a value over a loop, pass it through a script job.