The event catalog is the complete list of channels and messages that travel on the Binions bus. Because the platform is choreographed rather than centrally orchestrated, this catalog is the contract: if you know which channel carries which message, you know how to observe, integrate with, or extend any part of the system. The catalog is published in a machine-readable form as an AsyncAPI document.
AsyncAPI, not OpenAPI. OpenAPI describes request/response REST APIs. Binions is event-driven, so its contract is described with AsyncAPI — the equivalent standard for message-based systems. The committed document is
docs/asyncapi-snapshot.json(AsyncAPI 2.6).
A channel is a Redis stream. The catalog defines 18 channels: nine action inboxes and nine fact streams, one of each per worker daemon.
| Worker daemon | Action channel (in) | Fact channel (out) |
|---|---|---|
| aiinjector | actions:ai | events:aiinjector |
| dataanaliser | actions:analytics | events:dataanaliser |
| datatransporter | actions:data | events:datatransporter |
| mailbox | actions:mail | events:mailbox |
| modbus | actions:modbus | events:modbus |
| scheduler | actions:schedule | events:scheduler |
| database | actions:database | events:database |
| traefiklinker | actions:traefik | events:traefiklinker |
| webhookcaller | actions:webhook | events:webhookcaller |
Two further streams complete the bus but are not part of the per-daemon contract above: events:logs (the shared log sink consumed by the logger) and control:<service> (one directed control stream per daemon).
The catalog defines 128 messages. They come in matched pairs: an action message you send to ask a daemon to do something, and one or more fact messages the daemon emits to report what happened. For example:
| You send (Action) | The daemon emits (Fact) |
|---|---|
Database.Write | Database.Inserted |
Database.Query | Database.QueryResult |
Database.RegisterConnection | Database.ConnectionRegistered |
Data.RegisterBucket | Data.BucketRegistered |
Data.ParseDocument | Data.Parsed |
Mail.Send | Mail.Sent |
Mail.Fetch | Mail.Received (per message) + Mail.Fetched (summary) |
AI.Classify | AI.Classified |
AI.Batch | AI.BatchCompleted (one aggregate for up to 200 items) |
Webhook.Send | Webhook.Delivered (per target) + Webhook.GroupCompleted (multicast summary) |
A few facts are emitted spontaneously rather than in reply to an action — most importantly Fact.Schedule.Fired, which the scheduler emits when a registered schedule ticks (and Fact.Schedule.Deregistered when an exhausted one-shot or calendar removes itself), and Fact.Mail.Received, which the mailbox emits when a message arrives at a registered inbox (including MQTT/AMQP sources). The modbus daemon’s polled subscriptions emit Fact.Modbus.ValueChanged as values move, Fact.Modbus.ThresholdCrossed on alarm edges, and — with the built-in server writable — Fact.Modbus.ServerRegisterWritten when an external master writes into the served map. Fact.Analytics.ItemEmitted fans a list out one fact per element, and the error side is spoken loudly too: Fact.AI.OperationFailed, Fact.Mail.OperationFailed, Fact.Data.OperationFailed and Fact.Analytics.OperationFailed make every daemon failure a reactable event, while the logger’s opt-in alerting emits Fact.Logs.ErrorRateExceeded onto its own fact stream when one producer starts erroring hard. The complete operation-by-operation list lives in the API reference.
A small set of facts is deliberately not part of the AsyncAPI contract: they exist to start playbooks, and their payload is shaped by whatever arrives from the outside world, not by a daemon's schema. They follow the same precedent as Fact.System.Boot:
Fact.Http.Received / Fact.Http.Replied — an HTTP request hit the /in/<route> gateway / its synchronous reply was matched. A request that fails the route’s signature check is turned away as Fact.Http.Rejected (audit trail; it never becomes Received).Fact.Showman.WsMessage — a browser sent a frame on a live page's WebSocket channel.Fact.Mail.Received — a message or feed frame arrived on a registered mailbox/feed (IMAP, MQTT, AMQP, Redis, WS, SSE, Kafka).Fact.System.Boot — the platform started; the standard provisioning trigger.Fact.Data.FileDiscovered — a watch-dir poller on a registered bucket observed a new file (bucket_id, watch_dir, key, size, discovered_at).Everything a playbook does (the 64 verbs and their reply facts) remains snapshot-tracked; only these inbound triggers sit outside it by design.
The playbook engine itself emits a family of observable facts as each run progresses. These are real bus messages — integrators can subscribe to them for observability, alerting, or to trigger downstream logic. Each carries a correlation id that lets you trace a run from start to finish.
| Fact | Emitted when |
|---|---|
Fact.Playbook.Started | A trigger was matched and the run has begun. |
Fact.Playbook.StepCompleted | An individual step finished successfully. |
Fact.Playbook.Completed | All steps finished; the run ended successfully. |
Fact.Playbook.Failed | A step failed (or a wait_for: timed out); the run was halted. No retry occurs automatically. |
Fact.Playbook.Rejected | A playbook file was refused on arrival (boot, reload, or deploy) because it could never run — unknown verb, a reference to an undeclared step, a missing secret. Carries the concrete errors[]; the previous good version keeps serving. |
Use lifecycle facts for chaining and monitoring. A playbook whose trigger is
Fact.Playbook.Completed(filtered by playbook name via the correlation id) becomes a clean post-processing hook.Fact.Playbook.Failedis the natural alert signal for dead-run notification workflows.
The AsyncAPI document is generated from the platform's own vocabulary, not maintained by hand. A small program emits the document deterministically, and the build pipeline compares that output against the committed snapshot:
asyncapi-emit tool produces the AsyncAPI 2.6 JSON from the same operation table the playbook engine uses.docs/asyncapi-snapshot.json.Why this matters to you. The published contract can never silently drift from the running code. If the snapshot says a daemon emits
Database.Inserted, the daemon really does — the build would not have passed otherwise.
The snapshot is a standard AsyncAPI 2.6 document, so any AsyncAPI viewer or code generator can open it. Its channels map names the streams; each channel's publish/subscribe blocks reference the messages; and components.messages holds the message definitions.