Events and State
A document ingestion service reacts to uploads. A document moves from received to validating to indexed or failed, and each step emits an event that other parts of the system listen to. Written with callbacks and flags, the lifecycle becomes a set of booleans that can disagree: an “indexed” flag set while the error banner is still visible, or a validation result that arrives after the document was cancelled.
EventBus carries the messages. Machine decides which transitions are legal and ignores the rest.
Op runs the request the machine is waiting on.
flowchart TD Upload["uploadFinished event"] --> Bus["EventBus"] Bus --> Actor["Machine actor: received"] Actor -->|VALIDATE| Validating["validating"] Validating -->|VALIDATED| Indexed["indexed"] Validating -->|REJECTED| Failed["failed"] Validating --> Op["Op manager runs the validator"] Op -->|OpOk| Bus Op -->|OpErr| Bus
Describe the messages
Section titled “Describe the messages”The bus schema is the contract between producers and consumers. Every message carries its payload in
value, and a listener that selects kind: "VALIDATED" receives the parsed document, not an
unknown payload.
emit delivers synchronously. If a subscriber emits another message on the same bus, the second
message is queued until the current delivery finishes, so a handler cannot recurse through the
stack. The EventBus guide covers ordered sequences and forwarding.
Model the lifecycle
Section titled “Model the lifecycle”The machine names the states and the events each state accepts. A transition returns the next state;
an event with no handler leaves the state unchanged, so VALIDATED arriving twice cannot move an
indexed document backwards.
The allowed target kinds are part of each state’s type, so a transition to an undeclared state is a
compile error. The Machine guide covers entry effects, exit cleanup, and the
actor’s cancellation.
Run the work under a policy
Section titled “Run the work under a policy”Validation is an external request that can fail and be retried. The manager owns the retry and the timeout, and its settled outcome is translated into a bus message. That keeps the machine free of network policy and keeps the bus free of retries.
strategy: "exclusive" drops a duplicate validation request for the same document while one is in
flight. The Op guide covers the remaining strategies and the meaning of each
OpNil reason.
Connect the three
Section titled “Connect the three”The actor subscribes to the bus and republishes its own transitions. The machine decides whether an event is legal; the manager performs the fallible work; the bus carries the outcome to everyone else.
The actor accepts validate because its current state declares a handler for it. An event that no
current state handles is ignored, which is how a duplicate or out-of-order message stops at the
boundary instead of corrupting the view.
Failure behavior
Section titled “Failure behavior”A rejected validation settles the manager with OpErr and emits rejected, which moves the machine
to failed. A cancellation settles the manager with Aborted and the document
moves to cancelled only if that event is emitted. Subscriber callbacks must not throw: an
exception escapes emit and stops delivery to the remaining subscribers.
The machine aborts the previous state’s entry scope on every transition, so an asynchronous effect
started by validating cannot write into indexed. If the ingestion pipeline also needs durable
writes, hand the final state to a Transaction instead of calling the
storage layer from the subscriber.