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
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
This commit is contained in:
@@ -105,6 +105,8 @@ func TestUnassigningTheLastHolderUnderItsDependentsIsRefused(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStatusReportsAnUnheldDependencyWithoutRefusingTheMachine(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)
|
open := aMesh(t)
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
register(t, open, serviceManagerHolder())
|
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
|
// 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.
|
// make a machine vanish from the network rather than report anything.
|
||||||
resolution.Unheld = UnheldDependencies(catalogue, node.Name, resolution.Modules, nil)
|
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 {
|
if enforceSeatDependencies && !world.Unchecked {
|
||||||
for _, u := range resolution.Unheld {
|
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
|
// 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.
|
// 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.
|
// 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):
|
// 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
|
// 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
|
// **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
|
// 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
|
// 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
|
// still says it, and `status` reports it — before the switch and after it alike: there is never a
|
||||||
// refuse what it cannot meet. The first return is those lines.
|
// remedy to name for it. The first return is those lines.
|
||||||
func AssignRefusal(catalogue map[string]Manifest, node string, assigned, adding []string) ([]string, error) {
|
func AssignRefusal(catalogue map[string]Manifest, node string, assigned, adding []string) ([]string, error) {
|
||||||
set := manifestsOf(catalogue, append(append([]string(nil), assigned...), adding...))
|
set := manifestsOf(catalogue, append(append([]string(nil), assigned...), adding...))
|
||||||
judged := map[string]bool{}
|
judged := map[string]bool{}
|
||||||
@@ -217,7 +228,7 @@ func AssignRefusal(catalogue map[string]Manifest, node string, assigned, adding
|
|||||||
}
|
}
|
||||||
var refused, said []string
|
var refused, said []string
|
||||||
for _, u := range UnheldDependencies(catalogue, node, set, judged) {
|
for _, u := range UnheldDependencies(catalogue, node, set, judged) {
|
||||||
if len(u.Holders) == 0 && !enforceSeatDependencies {
|
if len(u.Holders) == 0 {
|
||||||
said = append(said, u.String())
|
said = append(said, u.String())
|
||||||
continue
|
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
|
// 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"))
|
web := withResources(mod("web", nil, nil, nil), res("container", "web"))
|
||||||
cat := shelf(web)
|
cat := shelf(web)
|
||||||
said, err := AssignRefusal(cat, "workstation", nil, []string{"web"})
|
for _, on := range []bool{false, true} {
|
||||||
if err != nil {
|
enforceSeatDependencies = on
|
||||||
t.Fatalf("a dependency nothing could meet was refused: %v", err)
|
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
|
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) {
|
func TestTheHoldersMutualDependenceResolvesWhenAssignedTogether(t *testing.T) {
|
||||||
@@ -165,6 +166,9 @@ func TestTheHoldersMutualDependenceResolvesWhenAssignedTogether(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStatusIsToldOfAnUnmetDependencyAndTheNodeStillResolves(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"))
|
web := withResources(mod("web", nil, nil, nil), res("container", "web"))
|
||||||
cat := shelf(append(coreThree(), web)...)
|
cat := shelf(append(coreThree(), web)...)
|
||||||
got, err := Resolve(cat, []string{"web"}, workstation(), World{})
|
got, err := Resolve(cat, []string{"web"}, workstation(), World{})
|
||||||
@@ -179,7 +183,7 @@ func TestStatusIsToldOfAnUnmetDependencyAndTheNodeStillResolves(t *testing.T) {
|
|||||||
|
|
||||||
func TestWithTheSwitchFlippedAnUnmetDependencyRefusesTheNode(t *testing.T) {
|
func TestWithTheSwitchFlippedAnUnmetDependencyRefusesTheNode(t *testing.T) {
|
||||||
enforceSeatDependencies = true
|
enforceSeatDependencies = true
|
||||||
defer func() { enforceSeatDependencies = false }()
|
defer func() { enforceSeatDependencies = true }()
|
||||||
web := withResources(mod("web", nil, nil, nil), res("container", "web"))
|
web := withResources(mod("web", nil, nil, nil), res("container", "web"))
|
||||||
cat := shelf(append(coreThree(), web)...)
|
cat := shelf(append(coreThree(), web)...)
|
||||||
_, err := Resolve(cat, []string{"web"}, workstation(), World{})
|
_, err := Resolve(cat, []string{"web"}, workstation(), World{})
|
||||||
|
|||||||
Reference in New Issue
Block a user