diff --git a/cmd/mesh-controller/seat_dependencies_test.go b/cmd/mesh-controller/seat_dependencies_test.go index 27f95c3..bea72be 100644 --- a/cmd/mesh-controller/seat_dependencies_test.go +++ b/cmd/mesh-controller/seat_dependencies_test.go @@ -105,6 +105,8 @@ func TestUnassigningTheLastHolderUnderItsDependentsIsRefused(t *testing.T) { } func TestStatusReportsAnUnheldDependencyWithoutRefusingTheMachine(t *testing.T) { + // The mesh as it ran before the switch (novox/hq ADR 0207 §4). + defer catalogue.EnforcingSeatDependencies(false)() open := aMesh(t) ctx := t.Context() register(t, open, serviceManagerHolder()) diff --git a/internal/catalogue/resolve.go b/internal/catalogue/resolve.go index bece43e..4964f80 100644 --- a/internal/catalogue/resolve.go +++ b/internal/catalogue/resolve.go @@ -675,9 +675,14 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world // Reported until the switch; refused after it, though never in the first pass, whose refusals // make a machine vanish from the network rather than report anything. resolution.Unheld = UnheldDependencies(catalogue, node.Name, resolution.Modules, nil) + // Only a dependency some module in the catalogue could meet is refused: one with no possible + // holder has no remedy to name, and stays a report in `status` (novox/hq ADR 0207 §4, read with + // the assign rule above it). if enforceSeatDependencies && !world.Unchecked { for _, u := range resolution.Unheld { - problems = append(problems, u.String()) + if len(u.Holders) > 0 { + problems = append(problems, u.String()) + } } } diff --git a/internal/catalogue/seat_dependencies.go b/internal/catalogue/seat_dependencies.go index 44f2f7f..80539b3 100644 --- a/internal/catalogue/seat_dependencies.go +++ b/internal/catalogue/seat_dependencies.go @@ -39,8 +39,19 @@ var appliedThrough = map[string]string{ // which is when the three holders are assigned to every node: switching it before then would stop // every machine lacking one from being sent anything at all. // +// Switched on 2026-10-04, when `status` first reported no unmet dependency on any node: systemd, +// pacman and docker were assigned to all four machines that afternoon (novox/hq to-be 42). +// // A variable rather than a constant only so a test can hold both behaviours; nothing else sets it. -var enforceSeatDependencies = false +var enforceSeatDependencies = true + +// EnforcingSeatDependencies sets the switch and returns what puts it back. For tests in other +// packages that hold the behaviour from before the switch; nothing else calls it. +func EnforcingSeatDependencies(on bool) (restore func()) { + was := enforceSeatDependencies + enforceSeatDependencies = on + return func() { enforceSeatDependencies = was } +} // foundationModules are the pieces genesis lays before any module exists (novox/hq ADR 0207 §5): // the host and the private network. Registered as modules so they can be assigned, but what they @@ -207,8 +218,8 @@ func manifestsOf(catalogue map[string]Manifest, names []string) []Manifest { // **A dependency nothing in the catalogue can meet is said, not refused.** A refusal names the // module that would meet it; with none registered there is no remedy to name, and refusing would // stop every assignment of that kind until a module that does not exist yet is written. The answer -// still says it, and `status` reports it, until the switch (enforceSeatDependencies) makes the mesh -// refuse what it cannot meet. The first return is those lines. +// still says it, and `status` reports it — before the switch and after it alike: there is never a +// remedy to name for it. The first return is those lines. func AssignRefusal(catalogue map[string]Manifest, node string, assigned, adding []string) ([]string, error) { set := manifestsOf(catalogue, append(append([]string(nil), assigned...), adding...)) judged := map[string]bool{} @@ -217,7 +228,7 @@ func AssignRefusal(catalogue map[string]Manifest, node string, assigned, adding } var refused, said []string for _, u := range UnheldDependencies(catalogue, node, set, judged) { - if len(u.Holders) == 0 && !enforceSeatDependencies { + if len(u.Holders) == 0 { said = append(said, u.String()) continue } diff --git a/internal/catalogue/seat_dependencies_test.go b/internal/catalogue/seat_dependencies_test.go index 0703ee9..fc76f9c 100644 --- a/internal/catalogue/seat_dependencies_test.go +++ b/internal/catalogue/seat_dependencies_test.go @@ -120,24 +120,25 @@ func TestAnAssignmentMissingAHolderIsRefusedNamingTheSeatAndItsPossibleHolders(t } } -func TestADependencyNoCatalogueModuleCanMeetIsSaidNotRefusedUntilTheSwitch(t *testing.T) { +func TestADependencyNoCatalogueModuleCanMeetIsSaidNeverRefused(t *testing.T) { // No runtime module in the catalogue: refusing would stop every container's assignment until one - // is written, with no remedy to name. + // is written, with no remedy to name — so it is said, with the switch on as with it off. web := withResources(mod("web", nil, nil, nil), res("container", "web")) cat := shelf(web) - said, err := AssignRefusal(cat, "workstation", nil, []string{"web"}) - if err != nil { - t.Fatalf("a dependency nothing could meet was refused: %v", err) + for _, on := range []bool{false, true} { + enforceSeatDependencies = on + said, err := AssignRefusal(cat, "workstation", nil, []string{"web"}) + if err != nil { + t.Fatalf("switch %v: a dependency nothing could meet was refused: %v", on, err) + } + if len(said) != 1 || !strings.Contains(said[0], "no module in the catalogue claims it yet") { + t.Errorf("switch %v: the assignment does not say what it depends on: %v", on, said) + } + if _, err := Resolve(cat, []string{"web"}, workstation(), World{}); err != nil { + t.Errorf("switch %v: a dependency nothing could meet refused the node: %v", on, err) + } } - if len(said) != 1 || !strings.Contains(said[0], "no module in the catalogue claims it yet") { - t.Errorf("the assignment does not say what it depends on: %v", said) - } - enforceSeatDependencies = true - defer func() { enforceSeatDependencies = false }() - if _, err := AssignRefusal(cat, "workstation", nil, []string{"web"}); err == nil { - t.Error("with the switch on, a dependency nothing could meet was not refused") - } } func TestTheHoldersMutualDependenceResolvesWhenAssignedTogether(t *testing.T) { @@ -165,6 +166,9 @@ func TestTheHoldersMutualDependenceResolvesWhenAssignedTogether(t *testing.T) { } func TestStatusIsToldOfAnUnmetDependencyAndTheNodeStillResolves(t *testing.T) { + // Before the switch (the state the mesh ran in until every node held the three seats). + enforceSeatDependencies = false + defer func() { enforceSeatDependencies = true }() web := withResources(mod("web", nil, nil, nil), res("container", "web")) cat := shelf(append(coreThree(), web)...) got, err := Resolve(cat, []string{"web"}, workstation(), World{}) @@ -179,7 +183,7 @@ func TestStatusIsToldOfAnUnmetDependencyAndTheNodeStillResolves(t *testing.T) { func TestWithTheSwitchFlippedAnUnmetDependencyRefusesTheNode(t *testing.T) { enforceSeatDependencies = true - defer func() { enforceSeatDependencies = false }() + defer func() { enforceSeatDependencies = true }() web := withResources(mod("web", nil, nil, nil), res("container", "web")) cat := shelf(append(coreThree(), web)...) _, err := Resolve(cat, []string{"web"}, workstation(), World{})