Merge pull request 'Refuse an unmet seat dependency the catalogue could meet (hq ADR 0207 §4)' (#266) from feat/0207-refuse-unmet-seat-dependencies into main
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{})
|
||||
|
||||
Reference in New Issue
Block a user