diff --git a/modules/mesh-delivery-gate/README.md b/modules/mesh-delivery-gate/README.md index bcc3db09..0da8df86 100644 --- a/modules/mesh-delivery-gate/README.md +++ b/modules/mesh-delivery-gate/README.md @@ -19,3 +19,22 @@ module and the backup's words, and the pull request's comment shows it. The hold of such a delivery asks the controller (`delivery-backups-again`) to take the backups again and the delivery goes on delivering; a record from before the controller cleared the failure does not hold it again. A held delivery already ends as covered or delivered when a later merge covers it. + +## Taking the seat over from today's module + +The gate claims the seat `mesh-delivery` beside the module `mesh-delivery`, which the catalogue no longer holds. The controller +keeps a module that is assigned (it refuses to forget it), so the running module goes by a handover of verbs after the merge, +never by hand on a machine (novox/hq ADR 0449 §4, ADR 0450 §6): + +1. assign `mesh-delivery-gate` to the machine that holds the seat (`mesh-controller.assign`); +2. check that `mesh-controller.seats` still names `mesh-delivery`, held by the running module; +3. unassign `mesh-delivery` from that machine (`mesh-controller.unassign`) and push it (`mesh-controller.push`); +4. the seat passes: the gate reads the controller's assignments and the node-engine's have keys, finds the module on no machine, + carries its deliveries, groups and pull keys over once (the key `carried` in its `deliveries` says it did), reads the controller's + walks and the forge's open pull requests, and acts. + +Until then the gate is silent: it takes no event, writes no key, asks nothing and answers no verb but `delivery_status`, which +says why (`holds-the-seat`, `seat-waits`). + +The catalogue holds the names of the old module that the handover needs, and no others: the three buckets the gate carries +over, and the event the issue tracker and the tool runner hear until the module is forgotten (`catalogue_test.go`). diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/catalogue_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/catalogue_test.go new file mode 100644 index 00000000..af564adc --- /dev/null +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/catalogue_test.go @@ -0,0 +1,69 @@ +package main + +import ( + "encoding/json" + "os" + "path/filepath" + "slices" + "strings" + "testing" +) + +// **Today's module is gone from the catalogue** (novox/hq ADR 0437 §1, issue 1351; how it is checked): no `modules/mesh-delivery` +// is held, no manifest declares the module `mesh-delivery`, and no manifest names the module in what it reads, hears or +// calls but through the seat's name (`mesh-delivery` as a seat or a repository, `seat:mesh-delivery.`) and through +// the names below, each of which stays until today's module is forgotten by the controller, because it is the only way to +// reach what that module left: the three buckets the gate carries over (ADR 0450 §6) and the event the trackers hear +// (issue 1350). Removing the module from the catalogue does not stop the running module: the controller keeps it until it is +// unassigned (mesh-controller upgrades.go retireDeleted), and the handover follows the merge by verbs. +var oldModuleNames = map[string][]string{ + "mesh-delivery-gate": {"mesh-delivery.deliveries", "mesh-delivery.groups", "mesh-delivery.pulls"}, + "mesh-issues": {"mesh-delivery.transition"}, +} + +func TestTheCatalogueHoldsNoModuleMeshDeliveryButThroughTheNamesItsHandoverNeeds(t *testing.T) { + modules := filepath.Join("..", "..", "..") + if _, err := os.Stat(filepath.Join(modules, "mesh-delivery")); err == nil { + t.Fatal("modules/mesh-delivery is still in the catalogue: its code is the gate's now") + } + dirs, err := os.ReadDir(modules) + if err != nil { + t.Fatal(err) + } + read := 0 + for _, dir := range dirs { + raw, err := os.ReadFile(filepath.Join(modules, dir.Name(), "module.json")) + if err != nil { + continue + } + read++ + var manifest any + if err := json.Unmarshal(raw, &manifest); err != nil { + t.Fatalf("%s: %v", dir.Name(), err) + } + if m, _ := manifest.(map[string]any); m["module"] == "mesh-delivery" { + t.Errorf("%s declares the module mesh-delivery", dir.Name()) + } + var walk func(v any) + walk = func(v any) { + switch x := v.(type) { + case string: + if strings.HasPrefix(x, "mesh-delivery.") && !slices.Contains(oldModuleNames[dir.Name()], x) { + t.Errorf("%s names the module mesh-delivery in %q: through the seat, or the gate's name", dir.Name(), x) + } + case []any: + for _, e := range x { + walk(e) + } + case map[string]any: + for _, e := range x { + walk(e) + } + } + } + walk(manifest) + } + if read < 20 { + t.Fatalf("only %d manifests were read: the catalogue is not where the test looks", read) + } +}