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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user