A module emits and consumes events, both declared (emits/consumes), parallel to provides/requires. Events are 1:many, broadcast, credential- free — no provisioner, just the broker's topic routing — so most inter- module reaction should be an event, not a provision. Every event carries source/node/time so it is auditable; the audit logger is just a module consuming '#', no privilege. A consumes for an event nothing emits is a dangling edge and refused, like requires. One per-node runtime serves tools, provisioning and events alike. Extends ADR 0045; builds on ADR 0001 (the broker) and 0044 (emit/on are stable sdk surface; the binding and runtime are not). Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
4.2 KiB
status, date, deciders, reconstructed, extends
| status | date | deciders | reconstructed | extends |
|---|---|---|---|---|
| accepted | 2026-09-03 | jochen | false | 0045-what-a-module-is.md |
46. Events are a relationship, the lighter sibling of provisioning
Context
ADR 0045 names two relationships between modules — seats and provide/require (provisioning). A third is latent in the mesh and worth making first-class: the broker every node already runs (ADR 0001) can carry a module's activity as events, which any other module reacts to. A logger that writes an audit trail, a module that acts when another module acts, observability — all of it is one mechanism, and today it is ambient rather than declared.
Decision
A module emits events and consumes events, and both are declared — parallel to provides /
requires, so the mesh knows the event graph the same way it knows the provisioning graph.
Events are provisioning's lighter sibling
| provisioning | events | |
|---|---|---|
| shape | 1:1, a provider creates a resource for one consumer | 1:many, a module emits, any number listen |
| credential | yes — sealed, per consumer | none — it is broadcast |
| machinery | a provisioner (the reconcile adapter) | nothing but the broker's topic routing |
| declared as | provides / requires |
emits / consumes |
Because an event is broadcast and credential-free, there is no provisioner and no per-consumer setup — only a subscription. That is why it is the lighter relationship, and why most inter-module reaction should be an event, not a provision.
An event carries what an audit needs
Every event carries its type (a dotted topic key, so listeners match by prefix), its source module, the node it came from, and the time. A body follows. The metadata is not optional: a reaction may only need the body, but an audit trail needs to know who did what, where and when, and an event that cannot answer that is not auditable.
The audit logger is just a consumer of everything
A logger that records the whole mesh's activity is not a privileged component — it is an
ordinary module that consumes # (every event) and writes them down. It holds no special access;
it only listens widely. That it falls out of the model with no new machinery is the check that the
model is right.
consumes is validated like requires
A consumes for an event that nothing emits is a dangling edge, and the mesh refuses it
before deploy — the same rule that catches a requires for a resource nothing provides
(research 011). A listener waiting for an
event that can never arrive is a silent failure, and this repository's whole discipline is against
silent failure.
One runtime serves all three
The per-node module runtime that serves a module's tools also wires its consumes (subscribe,
dispatch to the handler) and lets its code emit. Tools are invoked (request/reply), resources
are provisioned (1:1, credentialed), events are emitted and consumed (1:many, broadcast) —
three relationships, one broker, one runtime, all declared on the manifest.
Consequences
- The mesh gains a declared event graph alongside the provisioning graph — visible, validated, reasoned over.
- Reaction becomes the default coordination: a module acts on another's event without either knowing the other, and without a credentialed link. Coupling drops.
- An audit trail is a module, not a platform feature — and can be swapped, extended or run more than once (a file logger and a queryable one) with no change to anything that emits.
- The runtime must dispatch a module's event handlers as well as its tools; that generalisation is small (both arrive by importing the module's entrypoint) but it is real work.
References
- ADR 0001 — the broker events ride.
- ADR 0045 — the relationships this extends.
- ADR 0044 —
emit/onare stable sdk surface; the broker binding and the runtime are not. - research 011 — the graph these edges join.