Building the bus: the decisions the work needed, and what it taught back #150
@@ -0,0 +1,116 @@
|
||||
---
|
||||
topic: the mesh
|
||||
status: accepted
|
||||
date: 2026-09-26
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0119-amqp-is-a-provision-not-the-bus.md
|
||||
---
|
||||
|
||||
# 120. The mesh bus is required, not ambient
|
||||
|
||||
## Context
|
||||
|
||||
[Design 29](../03-DESIGN/01-to-be/29-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 0117](0117-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 0117'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 0119](0119-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 0117 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 0117](0117-the-bus-is-the-only-broker.md) — superseded by 0119; its bootstrap argument is
|
||||
narrowed here to the case it supports.
|
||||
- [ADR 0119](0119-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.
|
||||
@@ -135,6 +135,7 @@ python3 00-META/checks/index.py fail if stale
|
||||
- **0116** — [The bus is built in five steps, and the protocol moves with it](0116-the-bus-is-built-in-five-steps.md)
|
||||
- **0117** — [The bus is the only broker](0117-the-bus-is-the-only-broker.md) *(superseded)*
|
||||
- **0119** — [AMQP is a provision, not the bus](0119-amqp-is-a-provision-not-the-bus.md)
|
||||
- **0120** — [The mesh bus is required, not ambient](0120-the-mesh-bus-is-required-not-ambient.md)
|
||||
|
||||
### Its tiers, from the bottom up
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ code:
|
||||
updated: 2026-09-26
|
||||
decisions:
|
||||
- 02-DECISIONS/0118-a-module-declares-its-own-seats.md
|
||||
- 02-DECISIONS/0120-the-mesh-bus-is-required-not-ambient.md
|
||||
- 02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md
|
||||
- 02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md
|
||||
---
|
||||
@@ -64,7 +65,7 @@ convention, which later seats departed from.
|
||||
|---|---|---|---|
|
||||
| `mesh-controller` | — | mesh | — | the controller |
|
||||
| `mesh-store` | — | mesh | — | the store the mesh's own records live in |
|
||||
| `mesh-broker` | — | mesh | — | the broker carrying the mesh's own bus |
|
||||
| `mesh-broker` | — | mesh | `mesh-bus` | the broker carrying the mesh's own bus |
|
||||
| `mesh-vault` | — | mesh | `secret`, reserved | the vault |
|
||||
| `mesh-artifact-store` | `the-artifact-store` | mesh | `artifact-store` | the artifact registry |
|
||||
| `mesh-catalog` | `the-catalogue` | mesh | — | the catalogue |
|
||||
|
||||
@@ -6,6 +6,7 @@ updated: 2026-09-26
|
||||
decisions:
|
||||
- 02-DECISIONS/0118-a-module-declares-its-own-seats.md
|
||||
- 02-DECISIONS/0119-amqp-is-a-provision-not-the-bus.md
|
||||
- 02-DECISIONS/0120-the-mesh-bus-is-required-not-ambient.md
|
||||
- 02-DECISIONS/0106-the-bus-is-nats.md
|
||||
- 02-DECISIONS/0041-events-are-a-relationship.md
|
||||
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
|
||||
@@ -14,10 +15,22 @@ decisions:
|
||||
|
||||
# 29. What a module declares, and what the bus makes of it
|
||||
|
||||
**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. What a module declares are
|
||||
*relationships*; subjects, streams, consumers and permissions are all derived from those, and a
|
||||
manifest never contains one.
|
||||
**A module that speaks to the mesh requires the bus, and receives what it needs to connect**
|
||||
([ADR 0120](../../02-DECISIONS/0120-the-mesh-bus-is-required-not-ambient.md)). What a module
|
||||
declares are *relationships*; subjects, streams, consumers and permissions are all derived from
|
||||
those, and a manifest never contains one.
|
||||
|
||||
> **Revised 2026-09-26.** This document opened by calling the bus *ambient* — "no module requires
|
||||
> it, the way no module requires a filesystem". Two counts say otherwise: of 72 modules in the
|
||||
> catalogue, **49 take a broker credential and 23 do not**, so an ambient connection would mint an
|
||||
> account for a third of the catalogue that never speaks; and the 49 each hand-write the path it
|
||||
> lands at, which is provisioning done badly by hand. The bus is required, and a module that does
|
||||
> not require it has no account at all.
|
||||
|
||||
**The requirement delivers the connection; the declarations shape the authority.** `requires:
|
||||
mesh-bus` says *this module talks to the mesh* and grants no subject by itself. `emits`,
|
||||
`consumes`, `serves`, `uses` and a declared seat say what it may say and hear. Declaring a subject
|
||||
without requiring the bus is incoherent and refused at registration.
|
||||
|
||||
This document is the declaration model. [Design 25](25-the-bus-on-nats.md) is the bus itself —
|
||||
subjects, streams, accounts, enrolment — and stays the authority on the wire.
|
||||
@@ -254,6 +267,20 @@ What stays different, and must not be unified away: a provision has a **per-cons
|
||||
a sealed credential**, created and destroyed per consumer. A seat protocol has neither — it is a
|
||||
role you send to. Collapsing them would mean pretending a database is a subject.
|
||||
|
||||
### `mesh-bus` and `nats` are two interfaces, never one name
|
||||
|
||||
The mesh's own bus is **`mesh-bus`**, delivered by the `mesh-broker` seat and answered by the
|
||||
controller — because the bus's accounts are configuration rather than something a provisioner
|
||||
creates, so there is no provisioner process in the path and nothing waiting on a bus account in
|
||||
order to make bus accounts. A module that runs a NATS server of its own and offers it as a
|
||||
backing service provides **`nats`**, exactly as the AMQP broker provides `amqp`
|
||||
([ADR 0119](../../02-DECISIONS/0119-amqp-is-a-provision-not-the-bus.md)).
|
||||
|
||||
They are never the same name. A manifest saying `nats` could otherwise mean either the mesh's
|
||||
nervous system or a private queue, and the difference between those is the whole architecture.
|
||||
0119's rule decides which is legitimate: a private bus is a backing service, never a channel to
|
||||
another module.
|
||||
|
||||
### Where addresses survive
|
||||
|
||||
"Where is it?" is two different problems, and the bus solves one of them completely and the other
|
||||
|
||||
Reference in New Issue
Block a user