Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7d6f2eda0 | ||
|
|
694555214a | ||
|
|
a7249541df | ||
|
|
95d8253f71 |
@@ -4,12 +4,15 @@ status: implemented
|
|||||||
code:
|
code:
|
||||||
- mesh-controller internal/catalogue/seats.go
|
- mesh-controller internal/catalogue/seats.go
|
||||||
- mesh-controller internal/catalogue/resolve.go
|
- mesh-controller internal/catalogue/resolve.go
|
||||||
|
- mesh-controller internal/inventory/seats.go
|
||||||
|
- mesh-controller internal/inventory/migrations/0039-a-seat-is-held-by-one-assignment-on-record.sql
|
||||||
- mesh-controller cmd/mesh-controller/seats.go
|
- mesh-controller cmd/mesh-controller/seats.go
|
||||||
- mesh-controller cmd/mesh-controller/source.go
|
- mesh-controller cmd/mesh-controller/source.go
|
||||||
- mesh-controller internal/inventory/migrations/0032-a-source-may-live-on-a-seat.sql
|
- mesh-controller internal/inventory/migrations/0032-a-source-may-live-on-a-seat.sql
|
||||||
- mesh-catalog modules/gitea/module.json
|
- mesh-catalog modules/gitea/module.json
|
||||||
updated: 2026-09-27
|
updated: 2026-09-27
|
||||||
decisions:
|
decisions:
|
||||||
|
- 02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md
|
||||||
- 02-DECISIONS/0126-a-module-declares-its-own-seats.md
|
- 02-DECISIONS/0126-a-module-declares-its-own-seats.md
|
||||||
- 02-DECISIONS/0128-the-mesh-bus-is-required-not-ambient.md
|
- 02-DECISIONS/0128-the-mesh-bus-is-required-not-ambient.md
|
||||||
- 02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md
|
- 02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md
|
||||||
@@ -42,6 +45,31 @@ is refused. A seat makes a role singular, never a module.
|
|||||||
**The seat points at the assignment.** Everything the mesh knows about the holder is what it knows
|
**The seat points at the assignment.** Everything the mesh knows about the holder is what it knows
|
||||||
about that assignment: the node, the node's settings for the module, and what the module serves.
|
about that assignment: the node, the node's settings for the module, and what the module serves.
|
||||||
|
|
||||||
|
**Which assignment holds a seat is a fact on record, and changes as one act.** Revision, 2026-09-27
|
||||||
|
([ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md)). Until
|
||||||
|
then the holder was derived — the module that is assigned and claims the seat holds it, and a second
|
||||||
|
eligible assignment was refused. That has no way to pass a seat from one holder to the next without a
|
||||||
|
moment in which nobody holds it, and the controller finds its own bus through one of these seats: that
|
||||||
|
moment took the control plane down for an evening. So the holder is now one row the controller keeps,
|
||||||
|
written by a handover — `seat <name> --to <node>/<module>` — that names the seat and the assignment
|
||||||
|
taking it over and replaces the previous holder in the same write. Between two handovers the seat has
|
||||||
|
exactly one holder, and it is never none.
|
||||||
|
|
||||||
|
Three consequences follow. **A seat with no row is held as it always was**: the sole eligible
|
||||||
|
assignment holds it, and two eligible ones are refused — so a mesh that has never handed a seat over
|
||||||
|
behaves exactly as before, and the row appears the first time somebody does. **With a row, any other
|
||||||
|
assignment whose module could hold the seat is eligible and silent**: neither refused nor holding.
|
||||||
|
That is what lets the next holder run beside the current one until the handover, which the bus's move
|
||||||
|
needs ([28](28-building-the-bus.md), task 5.3). **And a holding is the assignment's**: unassigning the
|
||||||
|
holder takes the row with it, so a seat never points at something that is not running anywhere, and
|
||||||
|
the seat falls back to derivation rather than to nothing.
|
||||||
|
|
||||||
|
The handover refuses what would make the new holder wrong before anything is written: the seat must
|
||||||
|
exist, the assignment must exist, and the module must be able to hold the seat — claim it at its scope
|
||||||
|
and provide what it delivers, judged against the store's row and not against anything compiled into a
|
||||||
|
binary. It does not check that the module is running yet; `push` confirms that afterwards, and a
|
||||||
|
handover that could only be recorded after the new holder was up could not be the switch.
|
||||||
|
|
||||||
**The set is closed.** A seat the mesh does not define is refused wherever it is named, and so is one
|
**The set is closed.** A seat the mesh does not define is refused wherever it is named, and so is one
|
||||||
named at the wrong scope. Adding a seat is a decision, recorded, for the reason every addition to the
|
named at the wrong scope. Adding a seat is a decision, recorded, for the reason every addition to the
|
||||||
host's vocabulary is one: the set is what a person reads to learn what a mesh can have, and an entry
|
host's vocabulary is one: the set is what a person reads to learn what a mesh can have, and an entry
|
||||||
@@ -207,7 +235,9 @@ checked as their tables say:
|
|||||||
| `mesh-*` is the mesh's, and a module may not declare one | 0118: a registration test refusing a manifest that declares any `mesh-*` seat, naming the prefix. |
|
| `mesh-*` is the mesh's, and a module may not declare one | 0118: a registration test refusing a manifest that declares any `mesh-*` seat, naming the prefix. |
|
||||||
| Two modules cannot declare the same seat | 0118: a registration test; the second is refused and the first untouched. |
|
| Two modules cannot declare the same seat | 0118: a registration test; the second is refused and the first untouched. |
|
||||||
| A holder satisfies the seat's protocol | 0118: a claim whose module does not serve what the seat declares is refused at assignment. |
|
| A holder satisfies the seat's protocol | 0118: a claim whose module does not serve what the seat declares is refused at assignment. |
|
||||||
| A seat is held by one assignment, and only by one whose module can hold it | 0118: resolution tests for a second holder and for a seat the definition does not name. |
|
| A seat is held by one assignment, and only by one whose module can hold it | 0118: resolution tests for a second holder and for a seat the definition does not name. 0131: `CanHold` is the one judgement, shared by registration and the handover, and its test follows the store's row. |
|
||||||
|
| A holder on record settles the seat; another eligible assignment is silent, not refused | 0131: resolution tests with a recorded holder on the same machine, on another machine, and under a seat's former name; without a record, the old rule's tests still pass unchanged. |
|
||||||
|
| A handover replaces the holder as one write, needs an assignment to point at, and goes with it | 0131: store tests — a second handover leaves one row; a handover to a module not assigned where named is refused; unassigning the holder removes the row. |
|
||||||
| A requirement naming a seat is answered by its holder; a foundation seat cannot be named | 0118: resolution tests with a second provider on the consumer's node, with the seat unheld, and naming `mesh-store`. |
|
| A requirement naming a seat is answered by its holder; a foundation seat cannot be named | 0118: resolution tests with a second provider on the consumer's node, with the seat unheld, and naming `mesh-store`. |
|
||||||
| Several providers and none local is a person's choice | 0118: an assignment test listing candidates with the seat's holder first and recording the pin. |
|
| Several providers and none local is a person's choice | 0118: an assignment test listing candidates with the seat's holder first and recording the pin. |
|
||||||
| `secret` is reserved | 0118: the parser and resolution refusals for another provider and a pin. |
|
| `secret` is reserved | 0118: the parser and resolution refusals for another provider and a pin. |
|
||||||
|
|||||||
@@ -683,12 +683,43 @@ healthy while reacting to nothing.
|
|||||||
> crash-looped for two hours and nothing could be deployed until it was repaired by hand.
|
> crash-looped for two hours and nothing could be deployed until it was repaired by hand.
|
||||||
> An earlier version of this note said the old broker stays as an ordinary provider of
|
> An earlier version of this note said the old broker stays as an ordinary provider of
|
||||||
> `amqp` ([ADR 0127](../../02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md)); that is withdrawn by [ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md) — see 5.4.
|
> `amqp` ([ADR 0127](../../02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md)); that is withdrawn by [ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md) — see 5.4.
|
||||||
- [ ] 5.3 **the seat changes hands as one act.** A command takes a seat and the assignment taking it
|
|
||||||
|
**What the first live attempt found, 2026-09-27.** With the seat handed over on record and the
|
||||||
|
new bus's module registered and assigned beside the old one, `push` refused the control node:
|
||||||
|
*not one user has a credential for the new bus*. The check is right — a bus whose user list is
|
||||||
|
empty refuses every connection in the mesh — and it exposed the half of this task nobody had
|
||||||
|
built. A credential is minted at three moments only: a machine's at enrolment, a module's at
|
||||||
|
`module issue`, a person's at `operator`. **Nothing mints one for a machine already enrolled, or
|
||||||
|
for the control plane itself.** And on the host, the membership — bus address, fingerprint,
|
||||||
|
password, transport — is written once, at enrolment, and nothing ever rewrites it. So "move
|
||||||
|
each machine and confirm it reports" had no mechanism under it on either side.
|
||||||
|
|
||||||
|
The mechanism, to build before anything moves:
|
||||||
|
- **the control plane mints what is missing** — every user the records derive with no hash —
|
||||||
|
and delivers each plaintext where its owner reads it: a machine's inside its declaration, as a
|
||||||
|
sealed *membership* for the new bus (address, fingerprint, password, transport); a module's as
|
||||||
|
its broker secret, the path `module issue` already uses; the control plane's own as its module
|
||||||
|
secret, so it reads it the way any module does;
|
||||||
|
- **the host saves a delivered membership and re-dials on it** — the same file enrolment wrote,
|
||||||
|
the same reconnect path a lost connection takes, so a machine moved this way is a machine
|
||||||
|
that came back, and nothing new has to be right for it to work;
|
||||||
|
- **the switch is then two acts in one push**: `MESH_BUS_NATS` on the control plane, and
|
||||||
|
`seat mesh-broker --to <node>/<the new bus's module>` — the seat never empty, every machine
|
||||||
|
already holding a credential that works on the other side.
|
||||||
|
|
||||||
|
Until the first bullet exists the check keeps refusing, and it should: a machine moved without
|
||||||
|
a credential cannot come back, and afterwards there is no bus to tell it anything over.
|
||||||
|
- [x] 5.3 **the seat changes hands as one act.** A command takes a seat and the assignment taking it
|
||||||
over, and the seat is never empty in between — the emptiness is the outage of 2026-09-27, when
|
over, and the seat is never empty in between — the emptiness is the outage of 2026-09-27, when
|
||||||
the control plane, which finds its own bus through this seat, lost the address and looped.
|
the control plane, which finds its own bus through this seat, lost the address and looped.
|
||||||
Today only `seat rename` exists. This is what 5.2 uses to move `mesh-broker` from the old
|
**Built 2026-09-27** (mesh-controller `seat_holding`, migration 0039; design 26 says how it is
|
||||||
broker's assignment to the new one's, and it is built first ([ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md)).
|
checked). Its first live use recorded the standing holder — which the row moving under it had
|
||||||
- [ ] 5.4 **the old broker and everything that named AMQP leave the mesh** ([ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md),
|
made unable to satisfy what the seat delivers, so the first handover on a mesh that predates the
|
||||||
|
record writes down who holds without re-judging them. This is what 5.2 uses to move
|
||||||
|
`mesh-broker` from the old broker's assignment to the new one's ([ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md)).
|
||||||
|
- [~] 5.4 **the old broker and everything that named AMQP leave the mesh** — the catalogue half done
|
||||||
|
2026-09-27 (three modules removed; registration refuses the word; the seat's row delivers
|
||||||
|
`mesh-bus`, migration 0040); the live half — unassigning the old broker — waits on 5.2 ([ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md),
|
||||||
superseding [ADR 0127](../../02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md)): the two modules that
|
superseding [ADR 0127](../../02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md)): the two modules that
|
||||||
required `amqp` are removed, the broker's module is unassigned and removed, registration refuses
|
required `amqp` are removed, the broker's module is unassigned and removed, registration refuses
|
||||||
a manifest that provides or requires `amqp`, and a whole-catalogue check asserts none does. Not
|
a manifest that provides or requires `amqp`, and a whole-catalogue check asserts none does. Not
|
||||||
|
|||||||
Reference in New Issue
Block a user