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.
This commit is contained in:
jochen
2026-09-25 20:48:10 +02:00
parent 7ffe6ce21b
commit 97448194ac
16 changed files with 1071 additions and 103 deletions
+46 -78
View File
@@ -1,7 +1,6 @@
package catalogue
import (
"encoding/json"
"fmt"
"os"
"reflect"
@@ -85,9 +84,8 @@ func TestTheFilterModuleNeverFlushesTheRuleset(t *testing.T) {
}
// The package registry's port is the node's, like every other foundation port (novox/hq
// 04-ISSUES/085, ADR 0100). Two halves, because the forge is reached two ways: through what the
// module that serves it says it serves, and — for the genesis window, before any module provides
// `package-registry` at all — through the one binding the builder carries instead of resolving.
// 04-ISSUES/085, ADR 0100). The forge is reached through what it says it serves, and consumers —
// the builder among them — are told that, rather than carrying a number of their own.
func TestTheForgesPortIsGivenLikeAnyOtherProvidersPort(t *testing.T) {
forge := catalogueManifest(t, "gitea")
@@ -104,97 +102,67 @@ func TestTheForgesPortIsGivenLikeAnyOtherProvidersPort(t *testing.T) {
// And every consumer of the package registry is told where the machine actually put it,
// because that is read from what the forge serves rather than written in the consumer.
if got := ServedOn(forge, "package-registry", given)["port"]; got != 3100 {
if got := ServedOn(forge, "npm-package-registry", given)["port"]; got != 3100 {
t.Errorf("the package registry is served on %v, not the port this node gave it", got)
}
if got := ServedOn(forge, "package-registry", nil)["port"]; got != float64(3000) {
if got := ServedOn(forge, "npm-package-registry", nil)["port"]; got != float64(3000) {
t.Errorf("without a setting the forge serves %v, not the catalogue's port", got)
}
}
// bindingIn is the package binding the builder carries, as the machine would receive it.
func bindingIn(t *testing.T, m Manifest, layers []Layer) map[string]any {
t.Helper()
for _, r := range m.Resources {
if fmt.Sprint(r["id"]) != "package-binding" {
continue
}
settled, err := ApplySettings(r, layers)
if err != nil {
t.Fatalf("the builder's package binding refused %v: %v", layers, err)
}
if settled["merge"] != nil || settled["protected"] != nil {
t.Fatal("the host would be sent fields it does not know")
}
var out map[string]any
if err := json.Unmarshal([]byte(fmt.Sprint(settled["content"])), &out); err != nil {
t.Fatalf("the builder's package binding is not a binding: %v", err)
}
return out
// And so is where a repository on it is cloned from (novox/hq ADR 0111), for the same reason:
// a build composes the URL from what the forge serves, so a given port is a followed port.
if got := ServedOn(forge, "git", given)["port"]; got != 3100 {
t.Errorf("git is served on %v, not the port this node gave the forge", got)
}
t.Fatal("the builder carries no package binding")
return nil
}
func TestTheBuildersCarriedPackageBindingTakesThePortFromTheNode(t *testing.T) {
// **The builder requires the registry the npm seat delivers, and carries no binding of its own.**
//
// It used to carry a hand-written binding because nothing provided a package registry to resolve
// one from at genesis. The catalogue now requires it like any consumer, and ADR 0110 makes the
// seat's holder the answer when more than one module provides it — so a carried copy would be a
// second answer to the same question, free to drift from the first. Asserted gone, not merely
// unused.
func TestTheBuilderRequiresTheRegistryTheNpmSeatDelivers(t *testing.T) {
builder := catalogueManifest(t, "builder")
// Nothing set: the catalogue's own number, which is what a mesh raised on the defaults uses.
serves := bindingIn(t, builder, nil)["serves"].(map[string]any)
if serves["port"] != float64(3000) {
t.Fatalf("the builder's binding defaults to %v", serves["port"])
seat, _ := SeatNamed("npm-package-registry")
var requires bool
for _, r := range builder.Requires {
requires = requires || r == seat.Delivers
}
// Given a port, the binding dials it — and the rest of what the forge serves survives, because
// a setting is merged into the module's own values rather than replacing them.
moved := bindingIn(t, builder, []Layer{{From: "anchor",
Values: map[string]any{"serves": map[string]any{"port": float64(3100)}}}})
got := moved["serves"].(map[string]any)
if got["port"] != float64(3100) {
t.Errorf("the builder dials %v, not the port this node gave the package registry", got["port"])
if !requires {
t.Fatalf("the builder does not require %q: %v", seat.Delivers, builder.Requires)
}
if got["scheme"] != "http" || got["npm-path"] != "/api/packages/novox/npm/" {
t.Errorf("setting the port lost the rest of what the forge serves: %v", got)
if builder.Binds[seat.Delivers] == "" {
t.Errorf("the builder is not told where the registry is: binds %v", builder.Binds)
}
if moved["as"] != "mesh-builder" || moved["from"] != "gitea" {
t.Errorf("setting the port changed who the binding is with: %v", moved)
}
}
// The two halves are one number. The builder carries a binding because at genesis nothing provides
// `package-registry` to resolve one from; the day the forge is a module, the same consumer is told
// what the forge serves. They have to start from the same port, or a mesh raised on the defaults
// dials one number before the forge is assigned and another after.
func TestTheBuildersCarriedBindingStartsWhereTheForgeServes(t *testing.T) {
forge := ServedOn(catalogueManifest(t, "gitea"), "package-registry", nil)
carried := bindingIn(t, catalogueManifest(t, "builder"), nil)["serves"].(map[string]any)
for _, key := range []string{"port", "scheme", "npm-path"} {
if fmt.Sprint(forge[key]) != fmt.Sprint(carried[key]) {
t.Errorf("the forge serves %s %v and the builder's carried binding says %v — the two "+
"halves of the same registry have drifted apart in the catalogue",
key, forge[key], carried[key])
for _, r := range builder.Resources {
if fmt.Sprint(r["id"]) == "package-binding" {
t.Fatal("the builder carries its own package binding beside the one the mesh resolves")
}
}
}
func TestTheBuildersPackageBindingKeepsItsIdentity(t *testing.T) {
builder := catalogueManifest(t, "builder")
// `at` above all: a setting that moves it points the builder, and the registry password it
// sends as basic auth, at a host somebody else chose.
for _, key := range []string{"provision", "from", "at", "as"} {
var refused error
for _, r := range builder.Resources {
if fmt.Sprint(r["id"]) != "package-binding" {
continue
}
_, refused = ApplySettings(r, []Layer{{From: "anchor",
Values: map[string]any{key: "something else"}}})
}
if refused == nil {
t.Errorf("%q can be set on the builder's package binding, which is not a port but who "+
"the binding is with", key)
// The forge holds the seats it answers for (novox/hq ADR 0110, 0111), parsed by the real parser —
// which refuses a delivering seat claimed by a module that does not provide what it delivers.
func TestTheForgeHoldsTheNpmAndGitSeats(t *testing.T) {
forge := catalogueManifest(t, "gitea")
holds := map[string]bool{}
for _, c := range forge.Claims {
holds[c.Name] = true
}
for _, seat := range []string{"npm-package-registry", "git"} {
if !holds[seat] {
t.Errorf("gitea does not claim the %s seat: %+v", seat, forge.Claims)
}
}
git := ServedOn(forge, "git", nil)
if git["scheme"] != "http" || git["port"] != float64(3000) {
t.Errorf("gitea serves nothing a clone URL can be composed from: %v", git)
}
npm := ServedOn(forge, "npm-package-registry", nil)
if npm["npm-path"] != "/api/packages/novox/npm/" {
t.Errorf("gitea no longer says where its npm registry is: %v", npm)
}
}
// **And the forge's own address follows it**, composed from the manifest in the catalogue beside
+8
View File
@@ -950,9 +950,11 @@ func ParseManifest(raw []byte) (Manifest, error) {
problems = append(problems, fmt.Sprintf("%s requires itself", m.Module))
}
}
wellFormed := true
for _, c := range m.Claims {
if !name.MatchString(c.Name) {
problems = append(problems, fmt.Sprintf("%q is not a usable claim name", c.Name))
wellFormed = false
}
switch c.At() {
case ScopeNode, ScopeSite, ScopeMesh:
@@ -960,8 +962,14 @@ func ParseManifest(raw []byte) (Manifest, error) {
problems = append(problems, fmt.Sprintf(
"%s claims %s at scope %q; a claim is held per node, per site or per mesh",
m.Module, c.Name, c.Scope))
wellFormed = false
}
}
// Against the seats the mesh defines (novox/hq ADR 0110), once every claim is at least a name
// and a scope — a malformed claim is refused for that, not a second time for being unknown.
if wellFormed {
problems = append(problems, claimProblems(m)...)
}
if m.Computed != "" && len(m.Resources) > 0 {
// One or the other. A module that both ships files and has them computed would leave
// nobody able to say where a given file came from.
+13
View File
@@ -87,6 +87,10 @@ type Provider struct {
At string
// Serves is what the providing module said a consumer needs to know, settled.
Serves map[string]any
// Module is which module on that node provides it. A provider is a (node, module) pair
// (novox/hq to-be 23), and the pair is what tells the holder of a seat apart from another module
// providing the same thing (ADR 0110).
Module string
}
// Held is a claim somebody already has, used for the scopes wider than one node.
@@ -381,6 +385,15 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
default:
chosenNode, pinned := world.Pinned[want]
if !pinned {
// **The seat's holder answers, when a seat delivers this** (novox/hq ADR 0110).
// Not a guess, which ADR 0009 refuses: the choice was made once, mesh-wide, by
// assigning the holder, where a pin makes it again on every consumer's node. A
// pin still wins — it is a consumer coupled to one provider's contents, and has
// said so.
if holder, held := HolderAmong(want, where, world.Held); held {
take(holder)
break
}
problems = append(problems, fmt.Sprintf(
"%d nodes provide %q, wanted by %s — say which with `pin %s %s <node>`: %s",
len(where), want, because[want], node.Name, want,
+149
View File
@@ -0,0 +1,149 @@
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
}
+208
View File
@@ -0,0 +1,208 @@
package catalogue
import (
"encoding/json"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
)
// Defends novox/hq ADR 0110: a seat is a module assignment from a closed set.
// The set is closed, and changing it is a decision.
//
// **The count is asserted, and every entry names the record that made it a seat**, so the next
// person changing the set finds the argument rather than a number to edit — the pattern the host's
// vocabulary test follows. If this fails because a seat was added, the fix is a record in novox/hq
// and a row in to-be 26, not a new number here.
func TestTheSeatsAreAClosedSetAndEachNamesItsDecision(t *testing.T) {
record := regexp.MustCompile(`^novox/hq ADR \d{4}$`)
seen := map[string]bool{}
delivered := map[string]string{}
for _, s := range Seats() {
if seen[s.Name] {
t.Errorf("%s is in the set twice", s.Name)
}
seen[s.Name] = true
if !record.MatchString(s.Decision) {
t.Errorf("%s names %q as its decision; every seat names the record that made it one",
s.Name, s.Decision)
}
switch s.Scope {
case ScopeNode, ScopeSite, ScopeMesh:
default:
t.Errorf("%s is held per %q, which is not a scope", s.Name, s.Scope)
}
if s.Delivers != "" {
// Two seats answering for one provision would put the question "which one?" back,
// which is the question a seat exists to answer.
if other, twice := delivered[s.Delivers]; twice {
t.Errorf("%s and %s both deliver %q", other, s.Name, s.Delivers)
}
delivered[s.Delivers] = s.Name
}
}
if len(Seats()) != 14 {
t.Errorf("the mesh defines %d seats rather than 14; the set is closed, so a change here is "+
"a decision (novox/hq ADR 0110): %s", len(Seats()), seatNames())
}
}
func claimed(claims string) []byte {
return []byte(`{"module":"thing","version":"1","provides":[{"name":"npm-package-registry","scope":"mesh"}],"claims":` + claims + `}`)
}
func TestAClaimOnASeatTheMeshDoesNotDefineIsRefused(t *testing.T) {
_, err := ParseManifest(claimed(`[{"name":"the-anything","scope":"node"}]`))
if err == nil {
t.Fatal("a module invented a seat by claiming it")
}
if !strings.Contains(err.Error(), "the-anything") || !strings.Contains(err.Error(), "not a seat") {
t.Fatalf("the refusal does not say the seat is unknown: %v", err)
}
// And it says what the seats are, because "no" without the list sends somebody reading code.
if !strings.Contains(err.Error(), "the-packet-filter") {
t.Fatalf("the refusal does not list the seats: %v", err)
}
}
func TestASeatClaimedAtAnotherScopeIsRefused(t *testing.T) {
_, err := ParseManifest(claimed(`[{"name":"npm-package-registry","scope":"node"}]`))
if err == nil {
t.Fatal("a mesh seat was held per node")
}
if !strings.Contains(err.Error(), "mesh seat") {
t.Fatalf("the refusal does not say which scope the seat is: %v", err)
}
}
func TestADeliveringSeatIsOnlyHeldByAModuleThatProvides(t *testing.T) {
// Holding it makes the module the mesh's answer for the provision. A module that cannot answer
// would be the answer anyway, and every consumer would be sent to it.
raw := []byte(`{"module":"thing","version":"1","claims":[{"name":"git","scope":"mesh"}]}`)
_, err := ParseManifest(raw)
if err == nil {
t.Fatal("a module holding the git seat need not provide git")
}
if !strings.Contains(err.Error(), `does not provide "git"`) {
t.Fatalf("the refusal does not say what is missing: %v", err)
}
}
func TestAClaimThatIsMalformedIsRefusedOnceForThat(t *testing.T) {
// Not a second time for being unknown: one mistake, one line.
_, err := ParseManifest(claimed(`[{"name":"Not A Name","scope":"node"}]`))
if err == nil {
t.Fatal("a malformed claim was accepted")
}
if strings.Contains(err.Error(), "not a seat") {
t.Fatalf("a malformed claim was also called unknown: %v", err)
}
}
// Every module in use claims a seat in the set, so closing it refuses nothing that runs.
//
// Read from the catalogue beside this checkout and from this repository's own manifest, the two
// places a manifest lives (ADR 0069). The private-network module's manifest is composed in code,
// and its claim is checked where it is composed.
func TestEveryManifestInUseClaimsASeatTheMeshDefines(t *testing.T) {
paths, _ := filepath.Glob("../../../mesh-catalog/modules/*/module.json")
if len(paths) == 0 {
t.Skip("the catalogue is not beside this checkout")
}
paths = append(paths, "../../module.json")
var checked int
for _, path := range paths {
raw, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
// Leniently, so a manifest refused for something unrelated is not reported as a seat
// problem, and the seat check below is the only thing this test holds a module to.
var m Manifest
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatalf("%s: %v", path, err)
}
for _, problem := range claimProblems(m) {
t.Errorf("%s: %s", path, problem)
}
checked += len(m.Claims)
}
if checked == 0 {
t.Fatal("no claims were checked, so this proved nothing")
}
}
// The holder of a seat answers among several providers.
func registryShelf() map[string]Manifest {
return shelf(
Manifest{Module: "gitea", Version: "1", Provides: FromAnywhere("npm-package-registry"),
Claims: []Claim{{Name: "npm-package-registry", Scope: ScopeMesh}}},
Manifest{Module: "verdaccio", Version: "1", Provides: FromAnywhere("npm-package-registry")},
Manifest{Module: "builder", Version: "1", Requires: []string{"npm-package-registry"}},
)
}
func twoRegistries() map[string][]Provider {
return map[string][]Provider{"npm-package-registry": {
{Node: "anchor", At: "anchor.internal", Module: "gitea"},
{Node: "archive", At: "archive.internal", Module: "verdaccio"},
}}
}
func giteaHoldsTheSeat() []Held {
return []Held{{Claim: "npm-package-registry", Scope: ScopeMesh, Node: "anchor", Module: "gitea"}}
}
func TestTheSeatsHolderAnswersWhenSeveralProvide(t *testing.T) {
// The whole point: a second registry beside the holder harms nothing, and nobody pins.
got, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
World{Offered: twoRegistries(), Held: giteaHoldsTheSeat()})
if err != nil {
t.Fatal(err)
}
if len(got.Needs) != 1 || got.Needs[0].From != "anchor" {
t.Fatalf("the seat's holder did not answer: %v", got.Needs)
}
}
func TestAPinStillWinsOverTheSeat(t *testing.T) {
// A consumer coupled to one provider's contents has said so, and the seat does not overrule it.
got, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
World{Offered: twoRegistries(), Held: giteaHoldsTheSeat(),
Pinned: map[string]string{"npm-package-registry": "archive"}})
if err != nil {
t.Fatal(err)
}
if len(got.Needs) != 1 || got.Needs[0].From != "archive" {
t.Fatalf("the pin was overruled by the seat: %v", got.Needs)
}
}
func TestWithTheSeatUnheldSeveralProvidersAreStillRefused(t *testing.T) {
// No seat held is no choice made, and ADR 0009's rule stands: never guessed.
_, err := Resolve(registryShelf(), []string{"builder"}, reachable(),
World{Offered: twoRegistries()})
if err == nil {
t.Fatal("one of two registries was picked with nobody holding the seat")
}
if !strings.Contains(err.Error(), "pin") {
t.Fatalf("the refusal does not say how to choose: %v", err)
}
}
func TestTheHolderIsTheModuleNotTheMachine(t *testing.T) {
// Two modules on one machine could provide the same thing; only the one holding the seat
// answers. A holder matched by node alone would send consumers to whichever came first.
providers := []Provider{
{Node: "anchor", At: "anchor.internal", Module: "verdaccio"},
{Node: "anchor", At: "anchor.internal", Module: "gitea"},
}
holder, held := HolderAmong("npm-package-registry", providers, giteaHoldsTheSeat())
if !held || holder.Module != "gitea" {
t.Fatalf("the holder was not told apart from a neighbour: %+v", holder)
}
}