Files
mesh-controller/internal/catalogue/seats.go
T
jochen 97448194ac Seats are a closed set, a seat's holder answers for what it delivers, and a build source may live on the git seat
Implements novox/hq ADR 0110 and 0111.

The seat set lives in internal/catalogue/seats.go: fourteen seats, each with a scope, what occupying
it delivers, and the record that made it one. A test asserts the count and a decision per entry, so
changing the set means finding the argument, as the host's vocabulary test does. The first set is
every seat already claimed — including the-private-network, which the network module claims from a
manifest composed in this repository's code, not from any module.json — plus npm-package-registry
(ADR 0109) and git (ADR 0111). A test parses every catalogue manifest and this repository's own and
fails on any refused claim, so closing the set refuses nothing in use.

ParseManifest now refuses a claim on a seat the mesh does not define, a seat claimed at another
scope, and a delivering seat claimed by a module that does not provide what it delivers. A
malformed claim is refused once, for being malformed.

Resolution: among several providers of a mesh provision, a pin still wins; then the holder of the
seat that delivers it; then the only provider; otherwise refused as before. ADR 0009's "never
guessed" holds — the seat is the choice made once, mesh-wide, rather than a pin per consumer node.
A provider now carries the module it came from, because a provider is a (node, module) pair and the
pair is what tells a holder from a neighbour on the same machine.

The planner's second pass is now given the first pass's holdings. Without them, a node consuming a
seat-delivered provision was refused there, and a refused node's own claims dropped out of what the
mesh holds — letting a second holder of one of its seats pass unrefused.

`seats [--json]` lists every seat, what it delivers, and each holder, derived from assignments
every time and never stored. Unheld seats are listed. A stored claim outside the set — possible
for a manifest registered before the set closed, since stored manifests are not re-validated — is
shown rather than hidden.

`build --self <owner>/<repo>` builds from a repository on the git seat's holder. The clone URL is
composed at build time from the holder's node and what it serves for git; the recorded source is the
path and the seat (migration 0032), never an address, so a moved forge changes nothing recorded.
Nobody holding the seat refuses self-hosted builds and says so; external URLs are unchanged. An
address passed with --self is refused rather than recorded as a path.

Replaces three foundation tests that defended the builder's carried package binding. The catalogue
removed that binding when the builder began requiring the registry through a real grant, so the
tests were already failing on main; they now assert the builder requires what the npm seat delivers
and carries no copy of its own, and that the forge holds the npm and git seats.

Verified: go vet clean; the whole suite passes against a throwaway Postgres (make postgres), the new
inventory tests included; gofmt clean apart from cmd/mesh-builder/stdout_test.go, which fails on
main too.
2026-09-25 20:48:10 +02:00

150 lines
5.5 KiB
Go

package catalogue
import (
"fmt"
"sort"
"strings"
)
// The seats a mesh can have (novox/hq ADR 0110).
//
// **A closed set, defined here rather than by whoever claims one.** Until this, a well-formed name
// became a seat by being claimed, so nothing could say which seats a mesh has or who fills them:
// `the-showcase` and `the-build-machine` were each invented by the module claiming it. The set is
// what a person reads to learn what a mesh can have, so an entry nobody argued for is an entry
// nobody can explain — the same reason every shape in the host's vocabulary names its decision.
//
// A seat is held by a module assignment. What the mesh knows about a holder is what it knows about
// that assignment; nothing about holders is kept here or anywhere else.
// Seat is one role the mesh defines.
type Seat struct {
// Name is what a manifest claims.
Name string
// Scope is where there may be only one holder.
Scope string
// Delivers is the provision the seat's holder answers for, or empty. A seat that delivers a
// provision may only be held by a module providing it at the seat's scope, and its holder is
// what a requirement for that provision resolves to when several modules provide it.
Delivers string
// Decision is the record that made it a seat.
Decision string
}
// seats is the whole set, in the order a person reads it: the mesh's own, then a node's.
var seats = []Seat{
{Name: "mesh-controller", Scope: ScopeMesh, Decision: "novox/hq ADR 0079"},
{Name: "mesh-store", Scope: ScopeMesh, Delivers: "postgres-database", Decision: "novox/hq ADR 0079"},
{Name: "mesh-broker", Scope: ScopeMesh, Delivers: "amqp", Decision: "novox/hq ADR 0079"},
{Name: "the-artifact-store", Scope: ScopeMesh, Delivers: "artifact-store", Decision: "novox/hq ADR 0075"},
{Name: "the-catalogue", Scope: ScopeMesh, Decision: "novox/hq ADR 0110"},
{Name: "npm-package-registry", Scope: ScopeMesh, Delivers: "npm-package-registry", Decision: "novox/hq ADR 0109"},
{Name: "git", Scope: ScopeMesh, Delivers: "git", Decision: "novox/hq ADR 0111"},
{Name: "the-build-machine", Scope: ScopeNode, Decision: "novox/hq ADR 0110"},
{Name: "the-dns-port", Scope: ScopeNode, Decision: "novox/hq ADR 0110"},
{Name: "the-intrusion-prevention", Scope: ScopeNode, Decision: "novox/hq ADR 0110"},
{Name: "the-packet-filter", Scope: ScopeNode, Decision: "novox/hq ADR 0110"},
{Name: "the-private-network", Scope: ScopeNode, Decision: "novox/hq ADR 0110"},
{Name: "the-resolver-configuration", Scope: ScopeNode, Decision: "novox/hq ADR 0110"},
{Name: "the-showcase", Scope: ScopeNode, Decision: "novox/hq ADR 0110"},
}
// Seats is every seat the mesh defines, in reading order.
func Seats() []Seat {
return append([]Seat(nil), seats...)
}
// SeatNamed is the seat a claim names, if the mesh defines one.
func SeatNamed(name string) (Seat, bool) {
for _, s := range seats {
if s.Name == name {
return s, true
}
}
return Seat{}, false
}
// SeatDelivering is the seat whose holder answers for a provision, if there is one.
func SeatDelivering(provision string) (Seat, bool) {
if provision == "" {
return Seat{}, false
}
for _, s := range seats {
if s.Delivers == provision {
return s, true
}
}
return Seat{}, false
}
// claimProblems is what is wrong with a manifest's claims against the set.
//
// Three refusals, each naming the seat: a seat the mesh does not define, a seat claimed at another
// scope, and a seat that delivers a provision claimed by a module that does not provide it — which
// would make the module the mesh's answer for something it cannot answer.
func claimProblems(m Manifest) []string {
var problems []string
for _, c := range m.Claims {
seat, known := SeatNamed(c.Name)
if !known {
problems = append(problems, fmt.Sprintf(
"%s claims %q, which is not a seat this mesh defines (novox/hq ADR 0110) — "+
"the seats are: %s", m.Module, c.Name, seatNames()))
continue
}
if c.At() != seat.Scope {
problems = append(problems, fmt.Sprintf(
"%s claims %s at scope %q, and %s is a %s seat",
m.Module, c.Name, c.At(), c.Name, seat.Scope))
}
if seat.Delivers != "" && !providesAt(m, seat.Delivers, seat.Scope) {
problems = append(problems, fmt.Sprintf(
"%s claims %s, whose holder answers for %q, and %s does not provide %q at %s scope",
m.Module, c.Name, seat.Delivers, m.Module, seat.Delivers, seat.Scope))
}
}
return problems
}
func providesAt(m Manifest, provision, scope string) bool {
for _, o := range m.Provides {
if o.Name == provision && o.At() == scope {
return true
}
}
return false
}
func seatNames() string {
names := make([]string, 0, len(seats))
for _, s := range seats {
names = append(names, s.Name)
}
sort.Strings(names)
return strings.Join(names, ", ")
}
// HolderAmong is which of several providers of a provision holds the seat that delivers it.
//
// Found by the (node, module) pair, because a provider is identified by both (novox/hq to-be 23):
// two modules on one node could both provide a provision, and only the one holding the seat
// answers for it. Nothing when no seat delivers the provision, when nobody holds
// it, or when the holder is not among the providers offered.
func HolderAmong(provision string, providers []Provider, held []Held) (Provider, bool) {
seat, delivered := SeatDelivering(provision)
if !delivered {
return Provider{}, false
}
for _, h := range held {
if h.Claim != seat.Name || h.Scope != seat.Scope {
continue
}
for _, p := range providers {
if p.Node == h.Node && p.Module == h.Module {
return p, true
}
}
}
return Provider{}, false
}