Files
hq/02-DECISIONS/0190-a-seats-work-is-shared-by-its-holders-and-building-is-the-first-such-role.md
T

8.2 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
the mesh accepted 2026-10-02 jochen false 02-DECISIONS/0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md

190. A seat's work is shared by its holders, and building is the first such role

Context

Work addressed to a role goes to the seat's accept subjects, on a per-seat work queue (design 25 §1, ADR 0041). The holder's worker on that queue is already a queue group — "even though the seat guarantees one holder … the day somebody allows two holders for throughput, every message is processed twice with nothing reporting it" — and design 25 already says what a build queue shared by several machines is: a seat's accept subjects, on a work queue with a queue group of holders. The mechanism was drawn. Two things stopped it being used.

First, the build role is a mesh-scoped seat, mesh-build-machine, so there is one holder in the whole mesh (ADR 0121: the mesh's single build machine). Second, the worker is a push consumer with one delivery in flight — set so after 2026-10-01, when a push consumer handing out many at once left twenty-six of forty-three asks undelivered (issue 175) — and one in flight on a shared consumer is one build at a time across every holder there could be.

Measured on 2026-10-02: a change to code comments in the tool runtime rebuilt its thirty-five dependent images, one after another, on one machine, for about half an hour, while three other machines with a container runtime sat idle; the work the mesh wanted next waited behind it. The operator's words: this is our first occurrence of a mesh advantage — and: make sure the setup is done generically, so if another module also requires mesh functionality it can re-use the pattern.

Considered Options

  1. A second build machine by configuration — a concurrency setting on the one holder, or a second holder admitted by hand. Rejected: a setting on one machine shares nothing, and a second holder of a mesh-scoped seat contradicts what a mesh seat means.
  2. A build-specific dispatcher — the controller choosing a machine per build and asking it by name. Rejected: it reinvents the queue the bus already is, it makes the controller a scheduler, and it is specific to building; the next role needing the same would build its own.
  3. A seat's work is shared by its holders, and the build role becomes node-scoped. Chosen. It is what the bus was drawn to do, it is one rule for every role rather than one for building, and "the machines that are online and hold the seat" is exactly the set a queue group's members is.

Decision

1. Work asked of a seat is taken by whichever of its holders is idle. Every holder of a seat with accepts reads the seat's one work queue; a node-scoped seat held on several machines has several holders, and an ask goes to one of them. The asker addresses the role — mesh.seat.<seat>.accept.<verb> — and never a machine. The outcome, the role's own event, says which machine did the work (on), as a build's already does (ADR 0157).

2. A holder takes one ask at a time, when it is idle, by pulling. The worker is a pull consumer: a holder fetches one ask, works it, acknowledges, fetches the next. The server never hands an ask to a busy holder, so a slow machine never holds work an idle one could take — the fault issue 175 found in push delivery is removed by the shape rather than by a limit, and the one-in-flight limit that made the shared queue serial goes with it. A holder that dies mid-work leaves its ask to be redelivered to another, as today.

3. Work that must run on one particular machine is not a work queue. That is a node seat's verb asked of that machine (design 33 §4), and nothing here changes it. A role's work queue is for work whose result is the same whichever holder does it: a build is, because what comes out is published by digest to the mesh's store.

4. This is one pattern, not one role's. Any module that declares a node-scoped seat with accepts gets decisions 1 and 2 with no further mechanism: the controller derives the queue and the worker, the holders pull, the module's manifest says what every holding machine must have. The build agent is the first; a module needing work done somewhere on the mesh — a scan, a conversion, a fetch — declares a seat of its own the same way (ADR 0126).

5. Building is the first such role. The build role is node-build-agent, scope node, with the same build ask and the same started, built and log.<id> events as before. Its holder is the build-agent module: the builder as it is — a container runtime, the artifact store and the package registry resolved as provisions, a workspace, the bus credential — assignable to every machine that has a container runtime. mesh-build-machine and the builder module are retired when the new holder is assigned where the old one was. The tiered plan (ADR 0162) is unchanged: a tier's asks go out together and are now worked together.

Consequences

  • A tier of thirty-five images is built by as many machines as hold the seat and are online. A machine that is off builds nothing and blocks nothing.
  • Every holding machine fetches base images from the store and pushes what it builds; the store is reached as a provision, so this is what the provision was for. A machine with a slow link builds slowly, and takes fewer asks for it, which is the point of pulling.
  • A build's outcome carries which machine built it, so a build that fails on one machine and not another is a fact the record shows, not a mystery.
  • What got harder: a build's cache is per machine, so a cold machine pays the first pull of every base it has never seen; the artifact store is now asked by several machines at once, and the package registry likewise. Both are provisions and both are made for that.
  • ADR 0121's "the mesh's single build machine" and ADR 0162's "built by whichever build machine is running — the only one there could be" were true and are no longer; both records carry a note.

How it is checked

Rule Checked by
Two holders of one seat each take one of two asks, and a third ask waits for the first to be idle the controller's test over the work queue against a real bus: two machines bound to one worker, three asks
An ask is never delivered to a busy holder the same test: the busy holder's ask count stays at one until it acknowledges
A holder that dies mid-work leaves its ask for another the same test, one holder closed mid-ask
The asker names no machine the controller's seat table: node-build-agent accepts build and the asking side publishes to the seat's accept subject, as the existing tests of build already require
Live builds shows a tier's builds on more than one machine within one plan; seats shows node-build-agent held on every machine with a container runtime — held on all four machines and a build taken by a workstation's agent, 2026-10-03

References