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.
103 lines
5.9 KiB
Markdown
103 lines
5.9 KiB
Markdown
---
|
|
topic: the mesh
|
|
status: accepted
|
|
date: 2026-09-27
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 02-DECISIONS/0122-a-seat-is-data-a-rename-is-a-database-update.md
|
|
---
|
|
|
|
# 129. A seat carries the protocol of its role
|
|
|
|
## Context
|
|
|
|
[ADR 0126](0126-a-module-declares-its-own-seats.md) let a module declare a seat with its protocol:
|
|
what work the role accepts, what it emits, what it serves. A module's own seats work that way today.
|
|
**The mesh's own seats — the `mesh-*` set — carry no protocol at all**, only a name, a scope and the
|
|
provision they deliver. They say who does a job and nothing about what may be said to them or by
|
|
them.
|
|
|
|
That gap surfaced three times in one day, each time as a different-looking problem.
|
|
|
|
**A build machine.** On the bus the mesh runs on today a builder has its own account kind, created by
|
|
its own command, with permissions written by hand: read the build queue, write to two exchanges. One
|
|
publish to a shared exchange reached all three audiences a finished build has — whoever asked, the
|
|
controller that records it, and the catalogue that places it in the module graph. On a bus where
|
|
permissions are per subject those are three separate grants, and nothing derives them, because a
|
|
builder is not a module and holds a seat that promises nothing.
|
|
|
|
**An event about a role rather than about a module.** The module holding the artifact-store seat
|
|
declared an event named after a *different* module
|
|
([issue 127](../04-ISSUES/127-a-module-event-derives-a-subject-nothing-publishes/00-report.md)). The
|
|
bus refuses that, because a namespace belongs to who it is named for. The event is genuinely about the
|
|
role — "the artifact store accepted an image" — and a consumer written against whichever module holds
|
|
that role today breaks when the holder changes. There was nowhere else to put it.
|
|
|
|
**A catalogue catching up.** The controller answers a request for builds it may have missed by
|
|
re-publishing them under its own name, which no consumer of the builder's subject hears. Publishing
|
|
them under the builder's name would be the controller signing an event as another module. Answering
|
|
into the asker's inbox needs a grant over every inbox in the mesh, which
|
|
[design 25](../03-DESIGN/01-to-be/25-the-bus-on-nats.md) §4 refuses.
|
|
|
|
Three symptoms, one cause: **the mesh has roles it cannot describe.**
|
|
|
|
## Decision
|
|
|
|
**A seat carries the protocol of its role, whether the seat is a module's or the mesh's own.** The
|
|
`mesh-*` set gains the same three fields a declared seat has — what it accepts, what it emits, what it
|
|
serves — and the holder's authority, its work queue and its consumers are derived from them by the
|
|
machinery that already does this for a module's seats.
|
|
|
|
**Builds become work submitted to a role.** The build machine seat accepts a build and emits an
|
|
outcome. The dedicated `mesh.build.*` branch and the stream behind it retire: a work queue shared by
|
|
several build machines is exactly what a seat's `accepts` already is, and keeping a second mechanism
|
|
for it means two things to reason about and two places for a permission to be wrong.
|
|
|
|
**One publish still reaches three audiences, and now the mesh derived the subject.** A build's outcome
|
|
is the seat's own event. Whoever asked matches it by the id their request carried; the controller
|
|
records it; the catalogue places it. That is the fan-out the shared exchange gave for free, expressed
|
|
as a subject rather than as a topology, and it means no holder needs permission to publish into
|
|
anybody's inbox.
|
|
|
|
## Alternatives considered
|
|
|
|
**A dedicated principal kind for a builder**, mirroring the account the old bus issues it. Smaller: one
|
|
addition to the composer, no change to seats, and it matches how a builder is treated today. Not taken
|
|
because it answers one of the three symptoms and leaves the other two, and because "the builder is
|
|
special" is a claim nobody could justify from the design — a build machine is a role the mesh has, and
|
|
the mesh has a word for a role.
|
|
|
|
**Leaving the outcome as a reply to the asker's inbox.** Rejected on authority: a holder able to answer
|
|
any asker needs a grant across the whole inbox space, which is the one grant design 25 §4 refuses by
|
|
name. The seat's event costs the asker a filter and costs the mesh nothing.
|
|
|
|
## Reconciled with 0122, which landed in parallel
|
|
|
|
*Added 2026-09-27, on merging.* [ADR 0122](0122-a-seat-is-data-a-rename-is-a-database-update.md) moved
|
|
the seat set out of compiled code and into a table the controller owns. This record was written against
|
|
the slice, and says the `mesh-*` set "gains the same three fields a declared seat has".
|
|
|
|
**The decision is unaffected and the mechanism is better for it.** What a seat accepts, emits and serves
|
|
becomes three columns beside its name and scope, so giving a role a protocol is a write rather than a
|
|
rebuild — which is the whole argument of 0122 applied to the thing this record adds. Where this text
|
|
says the set gains fields, read: the table gains columns.
|
|
|
|
## Consequences
|
|
|
|
**A seat is now the mesh's unit of "a role that talks".** A role that accepts work, announces outcomes
|
|
or answers questions says so where it is defined, and everything about permissions, queues and
|
|
consumers follows. Nothing hand-writes a grant for a role again.
|
|
|
|
**The shared library cannot yet publish on a seat, and that is now the blocking gap rather than a
|
|
curiosity.** A module holding a seat has the authority and no way to use it; the build machine is
|
|
written in Go and reaches the bus directly, so it is unaffected, but the artifact-store event stays
|
|
under its module's own name until the library has a surface for this. That is a task, and this record
|
|
is what makes it one.
|
|
|
|
**A second mechanism disappears.** `mesh.build.*`, the BUILDS stream and the builder's hand-written
|
|
account all retire. Fewer things, and the ones left are derived.
|
|
|
|
**The catch-up question is not settled by this**, only made answerable: a seat that serves something
|
|
gives the controller a way to be asked, which the mesh did not have. Whether catch-up should be a
|
|
question at all remains open.
|