The architecture 0117 opened needs a module to offer a service as a role on the bus — one holder, addressed by what it does. A closed table in the controller cannot express that: a capability a module contributes would require changing the mesh itself. But 0110 closed the set for a good reason — nothing could say what seats a mesh had, and the hand count came out at eleven of thirteen. That argues for enumerable, not hardcoded, and 0110 weighed free-form against a fixed table without considering a third option: closed at any moment and derived from the catalogue. A derived list cannot drift, which is how the count broke. So: the mesh's seats stay the mesh's, reserved by the mesh- prefix so the prefix is the rule and there is no list to maintain; ten seats are renamed to restore 0079's convention; everything 0110 decided about what a seat IS survives untouched. Design 29 carries the declaration model: three namespaces, subjects derived from local names so a manifest survives the wire changing, queues never declared, five relationships (the job and state shapes 0041 had no room for), and the build-publish-deploy lifecycle with hard, soft and build-time dependencies distinguished. 0041 gets a progressive insight: "no per-consumer setup, only a subscription" was a fact about a topic exchange, and a JetStream durable consumer is a real object someone creates. WBS 1.3/1.4 were wrong and say so: streams come at registration and consumers at assignment, so only the foundation set belongs at genesis.
5.3 KiB
topic, status, date, deciders, reconstructed, extends
| topic | status | date | deciders | reconstructed | extends |
|---|---|---|---|---|---|
| what runs on it | accepted | 2026-09-03 | jochen | false | 0040-what-a-module-is.md |
41. Events are a relationship, the lighter sibling of provisioning
Context
ADR 0040 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) 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.
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) 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 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 — the broker events ride.
- ADR 0040 — the relationships this extends.
- ADR 0039 —
emit/onare stable sdk surface; the broker binding and the runtime are not. - research 011 — the graph these edges join.