--- topic: what runs on it status: accepted date: 2026-09-04 deciders: jochen reconstructed: false extends: 0041-events-are-a-relationship.md --- # 43. A module's broker account is scoped by what it emits and consumes ## Context [ADR 0041](0041-events-are-a-relationship.md) made events a relationship — `emits` and `consumes` on the manifest. [ADR 0042](0042-the-shape-of-an-event-on-the-wire.md) gave them a wire shape — the `mesh.events` exchange, the durable per-consumer queue, the reserved routing-key origins. Neither said how a module *reaches* the broker: what account it holds, and what that account is allowed to do. As the code stands, there is no answer. The mesh can provision a **node** account (at enrolment) and a **builder** account (scoped to the build queue), and it can *deliver* any module a sealed own-secret at a declared path — but it has no way to provision a broker **account** for a general module. A module that declares `own-secrets: {broker: …}` and nothing more receives thirty-two random bytes, not a credential. So on the broker, `emits` and `consumes` are enforced by nothing: a running module could bind any queue, consume any pattern, and publish under any origin, and the manifest that says otherwise would be describing a boundary no code draws — the exact shape of fault [04-ISSUES/003](../04-ISSUES/003-firewall-scope-is-read-by-no-code/00-report.md) records, a scope declared in manifests and read by nothing. This settles it, so a module's place on the bus is a thing the broker enforces rather than a thing the manifest merely claims. ## Decision ### A module gets a broker account when it is assigned, and its permissions are the manifest When the mesh assigns a module to a node it provisions a broker account for that module on that node, sealed to the node ([ADR 0004](0004-a-node-and-how-it-joins.md)) and delivered as the module's `own-secrets` broker — `amqps://` with the mesh's fingerprint, the shape [ADR 0042](0042-the-shape-of-an-event-on-the-wire.md) already carries. The account's permissions are derived from the manifest, and are exactly these: - **What it consumes.** Read on `mesh.events`, and configure-and-read on its own queue `..events` bound to the patterns in `consumes`. It cannot bind or read another module's queue. A module that consumes nothing gets no read on the events exchange at all. - **What it emits.** Write to `mesh.events`, restricted to routing keys under its own origin, `module..*`. It cannot publish as another module, and cannot publish under the reserved `mesh.*` or `node.*` origins — those belong to the mesh and the host (ADR 0042). A module that emits nothing gets no write. - **Nothing else.** The events account reaches `mesh.events` and that module's own queue, and no more. Tool serving and calling over `mesh.rpc` is a separate grant on the same principle — a module serves the tool keys it declares and calls the ones it is bound to — and is scoped the same way rather than folded in here. ### Consuming everything is a privilege, granted deliberately `consumes: ["#"]` — the audit logger — is read across the whole bus: every module's events, the mesh's, every node's. That is not a pattern like any other; it is the power to see everything, and the account is where it becomes visible. The grant that lets one module read the entire bus is one the mesh issues on purpose and can be audited — the answer to *who can read everything* is a row, not a guess — rather than a breadth any manifest acquires by typing a single character. A `#` consume is a reviewed grant, not a default one. ### The account is how the declaration is enforced Because the account can do only what `emits` and `consumes` name, the broker itself refuses a module that tries to consume a queue it did not declare or emit under an origin it does not own. That is what makes an event relationship a rule and not a comment — the discipline that a stated rule says how it is checked. A manifest that over-declares grants more than the module uses, which is visible and reviewable; one that under-declares makes the module fail closed at the broker, which is the safe direction to be wrong in. ## Consequences - The control plane gains a **generic module broker-account**, derived from the manifest. The builder stops being a special case: its access to the build queue becomes an ordinary expression of what it consumes and serves, not a bespoke account method. One rule, and the builder is an instance of it. - The runtime reads its credential from a file (the broker own-secret), `amqps://` verified against the mesh's fingerprint. The `guest` account is for raising the substrate, never for a module — a module documented as holding its own credential and handed the broker's administrative one is worse than one with no credential story at all. - `emits` and `consumes` stop being advisory. They are the module's authority on the bus, so the manifest is now a security boundary and is reviewed as one, the discipline [ADR 0010](0010-delivery.md) applies to the declaration vocabulary. - *Who can read the whole bus* becomes an answerable question, because `#` is a grant and not an accident. ## References - [ADR 0041](0041-events-are-a-relationship.md) — events are a relationship; this scopes the account by that relationship. - [ADR 0042](0042-the-shape-of-an-event-on-the-wire.md) — the wire this account secures: the queue, the origins, the `amqps` credential shape. - [ADR 0004](0004-a-node-and-how-it-joins.md) — the link is the security boundary; a module's account is sealed to its node the same way a node's is. - [ADR 0010](0010-delivery.md) — a declaration is owned and its additions reviewed; a module's broker permissions are that discipline applied to the bus. - [04-ISSUES/003](../04-ISSUES/003-firewall-scope-is-read-by-no-code/00-report.md) — a scope declared in manifests and enforced by no code: the fault this decision closes for events.