ADR 0046 — events are a relationship, provisioning's lighter sibling
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
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
---
|
||||
status: accepted
|
||||
date: 2026-09-03
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 0045-what-a-module-is.md
|
||||
---
|
||||
|
||||
# 46. Events are a relationship, the lighter sibling of provisioning
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0045](0045-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 0001](0001-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.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0001](0001-nodes-communicate-over-a-broker.md) — the broker events ride.
|
||||
- [ADR 0045](0045-what-a-module-is.md) — the relationships this extends.
|
||||
- [ADR 0044](0044-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.
|
||||
Reference in New Issue
Block a user