--- topic: what runs on it status: accepted date: 2026-09-03 deciders: jochen reconstructed: false extends: 0040-what-a-module-is.md --- # 41. Events are a relationship, the lighter sibling of provisioning ## Context [ADR 0040](0040-what-a-module-is.md) 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 0002](0002-nodes-communicate-over-a-broker.md)) 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](../01-RESEARCH/011-the-module-graph/00-overview.md)). 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. ## Progressive insight > **Progressive insight — 2026-09-26.** *"No provisioner and no per-consumer setup" was a fact > about the transport, and the transport changed.* This record's table says an event's machinery is > "nothing but the broker's topic routing", and the text that an event needs "no per-consumer setup > — only a subscription". That was true of a topic exchange, where a binding cost nothing and the > broker fanned out. On NATS > ([ADR 0106](0106-the-bus-is-nats.md)) a subscription is a **durable consumer**: a real object > with a name, an ack policy, a delivery limit and its own ack subject, created when a module is > assigned and removed when it is not. Per-consumer setup exists, and the controller does it. > > The decision is untouched — events are declared on both sides, 1:many, credential-free, and > still provisioning's lighter sibling; the lightness is now relative rather than absolute. > [ADR 0118](0118-a-module-declares-its-own-seats.md) adds the relationship this record's two > columns had no room for: work addressed to a role, where exactly one holder must act. ## References - [ADR 0002](0002-nodes-communicate-over-a-broker.md) — the broker events ride. - [ADR 0040](0040-what-a-module-is.md) — the relationships this extends. - [ADR 0039](0039-what-the-sdk-holds-and-refuses.md) — `emit`/`on` are stable sdk surface; the broker binding and the runtime are not. - [research 011](../01-RESEARCH/011-the-module-graph/00-overview.md) — the graph these edges join.