Merge main: renumber this branch's records around the trunk's

Both lines of work numbered from the same point, so four decision records and one design
document existed twice with different content. The trunk keeps its numbers and this branch
yields — the only rule that scales, because the trunk's are already cited by what merged
before them.

  0117 the bus is the only broker        -> 0125
  0118 a module declares its own seats   -> 0126
  0119 amqp is a provision, not the bus  -> 0127
  0120 the mesh bus is required          -> 0128
  0123 a seat carries its role's protocol -> 0129
  0124 the predecessor is ending          -> 0130
  design 29, what a module declares       -> design 32

Applied to the code repositories too, because a stale reference is worse when numbers
collide than when they dangle: the reader lands on a real record that decided something
else.

Two reconciliations the merge forced, both real:

**0110 was marked wholly superseded and was not.** Its successor says in as many words that
everything 0110 decided about what a seat *is* stands untouched — and two records that
landed on the trunk rest on exactly that part. So it is accepted again, extended rather than
replaced, with a note saying which of its claims moved and where.

**A seat's protocol becomes columns, not fields.** The trunk moved the seat set out of
compiled code into a table the controller owns. This branch had added what a role accepts,
emits and serves to the Go slice. The decision is unaffected and the mechanism is better for
it: giving a role a protocol is now a write rather than a rebuild, which is the trunk's own
argument applied to what this branch added.

One check still fails and it fails on main too: a record resting on ADR 0112 while that is
still 'proposed'. Left alone — it is not this merge's to answer.
This commit is contained in:
2026-09-27 18:23:41 +02:00
36 changed files with 1489 additions and 104 deletions
@@ -0,0 +1,116 @@
---
topic: the mesh
status: accepted
date: 2026-09-26
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md
---
# 128. The mesh bus is required, not ambient
## Context
[Design 29](../03-DESIGN/01-to-be/32-what-a-module-declares.md) opened by saying the bus is
*ambient*: "No module requires it, the way no module requires a filesystem. Every module gets a
connection and an identity whether it asks or not."
**Two counts say that is wrong.** Of the 72 modules in the catalogue, **49 declare an own-secret
named `broker` and 23 do not.** So the bus is not universal — nearly a third of the catalogue
never speaks to it — and an ambient connection would mint an account, a password and a permission
set for every one of those 23, each a credential nothing uses and everything must rotate.
And the 49 that do take one **each hand-write the path it lands at**
(`own-secrets: { broker: "/var/lib/<module>/broker" }`). That is a special case doing badly what
provisioning already does well: a consumer names where a credential lands, the mesh seals it
there, and rotation and removal follow the same path as every other credential.
**The argument that made the bus ambient was narrower than it looked.**
[ADR 0125](0125-the-bus-is-the-only-broker.md) reasoned that bus accounts cannot be provisioned
because a provisioner is itself a module that needs an account before it can run. That is true of
a **provisioner process**, and it is not true of a provision: the mesh's bus accounts are composed
by the *controller*, into configuration, and the controller is not waiting on a bus account to
exist. The circularity is real for one mechanism and absent for the other, and the earlier record
applied it to both.
## Considered Options
1. **Keep the bus ambient.** Rejected on the counts above: it over-grants to 23 modules and keeps
a hand-written path in 49.
2. **Derive the requirement** from whether a module declares any `emits`, `consumes`, `serves` or
`uses`. Rejected: it is the ambient model with extra inference. A reader of a manifest still
cannot see that the module holds a bus credential, and the rule would have to be re-derived
every time the set of bus-facing declarations grew.
3. **The mesh bus is a provision a module requires**, delivered by the seat that holds it.
Adopted.
## Decision
**A module that speaks to the mesh requires `mesh-bus`, and receives what it needs to connect.**
The contract is an address, a credential sealed to the module, and the trust to verify the
server. It lands where the module's manifest says, like any provision. A module that does not
require it gets no account, no password and no permissions — and 23 modules in the catalogue
should get none.
**The `mesh-broker` seat delivers `mesh-bus`.** Its holder is the mesh's own bus, and what
holding it delivers is the connection to that bus — which is what a seat delivering a provision
has always meant ([design 26](../03-DESIGN/01-to-be/26-the-seats.md)).
**The requirement delivers the connection; the declarations shape the authority.** They are two
different things and both stay explicit. `requires: mesh-bus` says *this module talks to the
mesh*; `emits`, `consumes`, `serves`, `uses` and a declared seat say *what it may say and hear*,
and the permission set is derived from those and nothing else
([ADR 0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md)). Requiring the bus
grants no subject; declaring a subject without requiring the bus is refused at registration as
incoherent.
**The `mesh-bus` provision is answered by the controller, not by a provisioner.** This is the
surviving kernel of ADR 0125's bootstrap argument, narrowed to what it actually supports: the
bus's accounts are configuration the controller composes and the server reloads
([ADR 0106](0106-the-bus-is-nats.md) — never through a management API), so there is no provisioner
process in the path and nothing waiting on a bus account to create bus accounts. It is a provision
whose provider is the mesh itself.
**A module may also provide a NATS server of its own, and that is a different interface.** Exactly
as the AMQP broker provides `amqp` ([ADR 0127](0127-amqp-is-a-provision-not-the-bus.md)), a module
may run its own NATS and offer it as a backing service. That interface is **`nats`**; the mesh's
own bus is **`mesh-bus`**; the two are never the same name, because a manifest that said `nats`
could mean either and the difference is the whole architecture. The rule from 0119 decides which
is legitimate: a private bus is a backing service, never a channel to another module.
## Consequences
- **Design 29's opening is reversed.** The bus is not ambient; it is required, and the document's
first paragraph says the opposite of this.
- **The seat's `Delivers` is `mesh-bus`** — corrected twice in one day, which is worth recording
rather than tidying: it read `amqp`, which was the old broker's interface; ADR 0125 emptied it,
on the reasoning that a bus cannot be provisioned; and it is neither. The seat delivers the
mesh's bus.
- **`own-secrets: { broker: ... }` is retired** in favour of the provision's own delivery, across
49 manifests. That is a mechanical change, and it belongs with the conversions in step 4 rather
than step 1.
- **23 modules lose a credential they never used.** Not a regression — an over-grant removed, and
the smallest honest statement of what this buys.
- **What got harder:** one more line in most manifests. The trade is that the line is true, and
its absence is also true.
## How it is checked
- **A module with no `requires: mesh-bus` has no account.** A composition test: the derived user
list contains exactly the modules that require it, and the 23 that do not appear nowhere in it.
- **Declaring a subject without requiring the bus is refused.** A registration test on a manifest
with `emits` and no requirement, naming the contradiction.
- **Requiring the bus grants no subject on its own.** A composition test: a module that requires
`mesh-bus` and declares nothing else gets a connection and an empty permission set.
- **`nats` and `mesh-bus` are distinct interfaces.** A resolution test: a module requiring `nats`
is answered by a module providing it, never by the seat holder, and vice versa.
## References
- [ADR 0125](0125-the-bus-is-the-only-broker.md) — superseded by 0119; its bootstrap argument is
narrowed here to the case it supports.
- [ADR 0127](0127-amqp-is-a-provision-not-the-bus.md) — a broker as a backing service; this
applies the same shape to the mesh's own bus and separates the two names.
- [ADR 0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md) — authority from
declarations, which this leaves untouched.
- [design 26](../03-DESIGN/01-to-be/26-the-seats.md) — a seat delivering a provision.