From eb99284890cc469e03e38078dfa5e38f5198a8d1 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 14 Oct 2026 08:32:15 +0200 Subject: [PATCH 1/3] Hand a mesh of one its store by loopback until a resolver answers its name (issue 701) Genesis places the private network at step 21, before anything holds the mesh's resolver seat, and from then the store was named anchor.internal. Nothing answered that name, so the filter's build at step 22 could not pull its base. While the store's node is the only one on the network and no resolver holds the seat, keep answering it loopback, as before the network; a mesh of several, or one with a resolver, is answered as before. --- cmd/mesh-controller/addresses_test.go | 89 +++++++++++++++++++++++++++ cmd/mesh-controller/network.go | 27 ++++++++ testdata/store-tests | 1 + 3 files changed, 117 insertions(+) diff --git a/cmd/mesh-controller/addresses_test.go b/cmd/mesh-controller/addresses_test.go index dad8dce3..64cd9389 100644 --- a/cmd/mesh-controller/addresses_test.go +++ b/cmd/mesh-controller/addresses_test.go @@ -10,6 +10,7 @@ import ( "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/licences" "github.com/novox/mesh-controller/internal/link" + "github.com/novox/mesh-controller/internal/overlay" ) // An address is read from the node's settings where it is used, never recorded with a port @@ -350,3 +351,91 @@ func TestOnANodeWithNoNetworkTheStoreIsReachedByLoopback(t *testing.T) { t.Fatalf("a builder beside the store is handed the base %q", got) } } + +// **A mesh of one on its network, with no resolver yet, still reaches its store by loopback** (novox/hq issue +// 701). Genesis places the network at its step 21, before anything holds the mesh's resolver seat; the +// filter's build at step 22 was handed anchor.internal:5000, which nothing answered, and stopped. Once a +// resolver holds the seat the store is named by the network's name; a mesh of several is answered as before +// (TestTheRegistryTrustAndEveryImageFollowThePortTheNodeGaveTheStore). +func TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt(t *testing.T) { + open := aLoneNode(t) + ctx := t.Context() + // On the private network, as genesis's network step leaves it: placed as the hub, able to run the + // overlay, its key reported, the overlay assigned. + if err := open.inventory.SetPlace(ctx, "anchor", "anchor.example:51820", "here", true, "10.77.0.1"); err != nil { + t.Fatal(err) + } + nodes, err := open.inventory.Nodes(ctx) + if err != nil || len(nodes) != 1 { + t.Fatalf("nodes: %v %v", nodes, err) + } + if err := open.inventory.RecordProfile(ctx, nodes[0].ID, map[string]any{"capabilities": []any{ + map[string]any{"name": "container-runtime", "present": true}, + map[string]any{"name": "wireguard", "present": true}, + map[string]any{"name": "systemd", "present": true}}}); err != nil { + t.Fatal(err) + } + if err := open.inventory.RecordOverlayKey(ctx, nodes[0].ID, aPublicKey(t)); err != nil { + t.Fatal(err) + } + if _, err := open.inventory.Assign(ctx, "anchor", overlay.Name); err != nil { + t.Fatal(err) + } + register(t, open, aStore()) + register(t, open, catalogue.Manifest{Module: "builder", Version: "1", + Requires: []string{catalogue.ArtifactStoreProvision}, + Resources: []map[string]any{{"id": "server", "type": "container", "name": "mesh-builder", + "image": "registry.example/mesh-builder@" + aDigest}}}) + if err := open.inventory.RecordBuild(ctx, inventory.Build{ + ID: "b1", Repository: "r", Module: "mesh-tools", Commit: "abc", + Made: []inventory.Artifact{{Name: "build", Kind: "image", + Reference: catalogue.ArtifactStoreScheme + "mesh-tools/build@" + aDigest}}, + }); err != nil { + t.Fatal(err) + } + register(t, open, catalogue.Manifest{Module: "mesh-tools", Version: "1"}) + for _, module := range []string{"distribution", "builder"} { + if _, err := assign(ctx, open, "anchor", module); err != nil { + t.Fatal(err) + } + } + if err := open.inventory.SetSettings(ctx, "anchor", "distribution", + map[string]any{catalogue.PortsSetting: map[string]any{"5000": 5101}}); err != nil { + t.Fatal(err) + } + shelf, err := open.inventory.Catalogue(ctx) + if err != nil { + t.Fatal(err) + } + onNetwork, err := whereEveryoneIs(ctx, open.inventory, shelf) + if err != nil || len(onNetwork) != 1 { + t.Fatalf("the lone node is not on the network: %v %v", onNetwork, err) + } + if got, err := artifactStoreAddress(ctx, open.inventory, shelf, "anchor"); err != nil || got != "127.0.0.1:5101" { + t.Fatalf("on the network with no resolver, the mesh of one is handed %q (%v), a name nothing answers", got, err) + } + if got := heldBy(ctx)["mesh-tools/build"]; got != "127.0.0.1:5101/mesh-tools/build@"+aDigest { + t.Fatalf("the builder beside the store is handed the base %q", got) + } + + // A resolver holds the seat: the name is answered, and the store is named by it. + if _, err := open.inventory.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil { + t.Fatal(err) + } + register(t, open, catalogue.Manifest{Module: "resolver", Version: "1", + Provides: []catalogue.Offer{{Name: "wildcard-resolution", Scope: catalogue.ScopeMesh}}, + Claims: []catalogue.Claim{{Name: "mesh-dns-resolver", Scope: catalogue.ScopeMesh}}}) + if _, err := assign(ctx, open, "anchor", "resolver"); err != nil { + t.Fatal(err) + } + shelf, err = open.inventory.Catalogue(ctx) + if err != nil { + t.Fatal(err) + } + if got, err := artifactStoreAddress(ctx, open.inventory, shelf, "anchor"); err != nil || got != "anchor.internal:5101" { + t.Fatalf("with a resolver answering the mesh's names, the store is handed as %q (%v)", got, err) + } + if got := heldBy(ctx)["mesh-tools/build"]; got != "anchor.internal:5101/mesh-tools/build@"+aDigest { + t.Fatalf("the builder is handed the base %q", got) + } +} diff --git a/cmd/mesh-controller/network.go b/cmd/mesh-controller/network.go index 283e8335..d0f2c12d 100644 --- a/cmd/mesh-controller/network.go +++ b/cmd/mesh-controller/network.go @@ -721,6 +721,22 @@ func artifactStoreAddress(ctx context.Context, inv *inventory.Inventory, return "", err } if found { + // **The store's own node is not handed a name nothing answers** (novox/hq issue 701). A node's + // internal name is answered by the mesh's resolver (ADR 0194), and genesis places the network + // (its step 21) before anything holds that seat: the filter's build was handed + // anchor.internal:5000 and the machine's runtime could not look it up. Until a resolver holds + // the seat, a mesh of one keeps reaching its store by loopback, as it did before the network. + // A mesh of more than one node is answered as before: loopback is no answer for the others, and + // one name for every node is what a mesh of several without a resolver is still put together to. + if node == forNode && len(on) == 1 { + answered, err := meshNamesAnswered(ctx, inv, shelf) + if err != nil { + return "", err + } + if !answered { + return "127.0.0.1:" + port, nil + } + } return overlay.InternalName(node) + ":" + port, nil } holder, port, found, err := artifactStoreHolder(ctx, inv) @@ -730,6 +746,17 @@ func artifactStoreAddress(ctx context.Context, inv *inventory.Inventory, return "127.0.0.1:" + port, nil } +// meshNamesAnswered is whether something answers the mesh's internal names: a holder of the mesh's +// resolver seat on the private network (novox/hq ADR 0194, ADR 0223), as a machine's resolver file +// lists them (replicatedHolders). +func meshNamesAnswered(ctx context.Context, inv *inventory.Inventory, shelf map[string]catalogue.Manifest) (bool, error) { + holders, err := replicatedHolders(ctx, inv, shelf) + if err != nil { + return false, err + } + return len(holders[resolverSeat]) > 0, nil +} + // artifactStoreHolder is whichever node is assigned a module offering the artifact store, on or // off the network, and the port that node put it on. func artifactStoreHolder(ctx context.Context, inv *inventory.Inventory) (node, port string, found bool, err error) { diff --git a/testdata/store-tests b/testdata/store-tests index ef990b47..e0952edc 100644 --- a/testdata/store-tests +++ b/testdata/store-tests @@ -18,6 +18,7 @@ ./cmd/mesh-controller TestTheConditionsVerbReadsTheLooksTheBucketNoLongerHolds ./internal/inventory TestAWalksStartIsKeptAndARefusedSaveLeavesItUnsaid ./cmd/mesh-controller TestAWalkLetGoAndStoppedByVerbsIsSaidStartedAndEnded +./cmd/mesh-controller TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt ./cmd/mesh-controller TestAWalkStoppedOrClosedByAPersonIsSaidEndedStopped ./cmd/mesh-controller TestAWalkRetriedByAPersonIsSaidStartedAgain ./cmd/mesh-controller TestABacklogReleasedByAPersonIsSaidStarted -- 2.54.0 From d53fea31f0afa4d1ce4eadc804aa9d5462090d62 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 14 Oct 2026 08:58:00 +0200 Subject: [PATCH 2/3] Compose a lone node off the network while nothing answers its name (issue 701) The first change kept the base references on loopback, and the build agent then failed to publish: its registry is its same-node binding, composed at the node's own internal name. While a mesh of one has no resolver, compose that node without its name, so what it binds to on itself is loopback, as before the network. --- cmd/mesh-controller/addresses_test.go | 23 ++++++++++++++++++++++- cmd/mesh-controller/network.go | 22 ++++++++++++++-------- cmd/mesh-controller/plan.go | 15 ++++++++++++++- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/cmd/mesh-controller/addresses_test.go b/cmd/mesh-controller/addresses_test.go index 64cd9389..a4025239 100644 --- a/cmd/mesh-controller/addresses_test.go +++ b/cmd/mesh-controller/addresses_test.go @@ -385,7 +385,11 @@ func TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt(t *testing.T) { register(t, open, catalogue.Manifest{Module: "builder", Version: "1", Requires: []string{catalogue.ArtifactStoreProvision}, Resources: []map[string]any{{"id": "server", "type": "container", "name": "mesh-builder", - "image": "registry.example/mesh-builder@" + aDigest}}}) + "image": "registry.example/mesh-builder@" + aDigest}, + // Where the build agent publishes, as its manifest says it. + {"id": "env", "type": "file", "path": "/etc/mesh-builder.env", + "content": "MESH_REGISTRY=${bound:" + catalogue.ArtifactStoreProvision + ":at}:${bound:" + + catalogue.ArtifactStoreProvision + ":port}\n"}}}) if err := open.inventory.RecordBuild(ctx, inventory.Build{ ID: "b1", Repository: "r", Module: "mesh-tools", Commit: "abc", Made: []inventory.Artifact{{Name: "build", Kind: "image", @@ -417,6 +421,20 @@ func TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt(t *testing.T) { if got := heldBy(ctx)["mesh-tools/build"]; got != "127.0.0.1:5101/mesh-tools/build@"+aDigest { t.Fatalf("the builder beside the store is handed the base %q", got) } + publishesTo := func() string { + t.Helper() + for _, r := range composed(t, open, "anchor").Resources { + if r["path"] == "/etc/mesh-builder.env" { + content, _ := r["content"].(string) + return content + } + } + t.Fatal("the builder's environment was not composed") + return "" + } + if got := publishesTo(); got != "MESH_REGISTRY=127.0.0.1:5101\n" { + t.Fatalf("the builder beside the store publishes to %q, a name nothing answers", got) + } // A resolver holds the seat: the name is answered, and the store is named by it. if _, err := open.inventory.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil { @@ -438,4 +456,7 @@ func TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt(t *testing.T) { if got := heldBy(ctx)["mesh-tools/build"]; got != "anchor.internal:5101/mesh-tools/build@"+aDigest { t.Fatalf("the builder is handed the base %q", got) } + if got := publishesTo(); got != "MESH_REGISTRY=anchor.internal:5101\n" { + t.Fatalf("with a resolver, the builder publishes to %q", got) + } } diff --git a/cmd/mesh-controller/network.go b/cmd/mesh-controller/network.go index d0f2c12d..51944230 100644 --- a/cmd/mesh-controller/network.go +++ b/cmd/mesh-controller/network.go @@ -728,12 +728,12 @@ func artifactStoreAddress(ctx context.Context, inv *inventory.Inventory, // the seat, a mesh of one keeps reaching its store by loopback, as it did before the network. // A mesh of more than one node is answered as before: loopback is no answer for the others, and // one name for every node is what a mesh of several without a resolver is still put together to. - if node == forNode && len(on) == 1 { - answered, err := meshNamesAnswered(ctx, inv, shelf) + if node == forNode { + unanswered, err := aLoneNameNothingAnswers(ctx, inv, shelf, len(on)) if err != nil { return "", err } - if !answered { + if unanswered { return "127.0.0.1:" + port, nil } } @@ -746,15 +746,21 @@ func artifactStoreAddress(ctx context.Context, inv *inventory.Inventory, return "127.0.0.1:" + port, nil } -// meshNamesAnswered is whether something answers the mesh's internal names: a holder of the mesh's -// resolver seat on the private network (novox/hq ADR 0194, ADR 0223), as a machine's resolver file -// lists them (replicatedHolders). -func meshNamesAnswered(ctx context.Context, inv *inventory.Inventory, shelf map[string]catalogue.Manifest) (bool, error) { +// aLoneNameNothingAnswers is whether the mesh is one node on its private network and nothing answers that +// node's internal name: no holder of the mesh's resolver seat (novox/hq ADR 0194, ADR 0223), as a machine's +// resolver file lists them (replicatedHolders). Genesis is exactly that from its network step until a +// resolver is installed (novox/hq issue 701): the node then reaches what runs on it by loopback, as it did +// before the network, and is not composed a name no lookup can answer. +func aLoneNameNothingAnswers(ctx context.Context, inv *inventory.Inventory, shelf map[string]catalogue.Manifest, + onNetwork int) (bool, error) { + if onNetwork != 1 { + return false, nil + } holders, err := replicatedHolders(ctx, inv, shelf) if err != nil { return false, err } - return len(holders[resolverSeat]) > 0, nil + return len(holders[resolverSeat]) == 0, nil } // artifactStoreHolder is whichever node is assigned a module offering the artifact store, on or diff --git a/cmd/mesh-controller/plan.go b/cmd/mesh-controller/plan.go index acfb086d..3d11f00d 100644 --- a/cmd/mesh-controller/plan.go +++ b/cmd/mesh-controller/plan.go @@ -103,6 +103,19 @@ func planFor(ctx context.Context, open *stores, nodeName string) (catalogue.Reso if err != nil { return catalogue.Resolution{}, nil, err } + // **This node's own name, only where something answers it** (novox/hq issue 701). A mesh of one on its + // network with no resolver yet is composed as off it: what it binds to on itself is reached by + // loopback, as before the network, rather than by a name no lookup on the machine can answer. + ownName := onNetwork[nodeName] + if ownName != "" { + unanswered, err := aLoneNameNothingAnswers(ctx, inv, shelf, len(onNetwork)) + if err != nil { + return catalogue.Resolution{}, nil, err + } + if unanswered { + ownName = "" + } + } // What this mesh can answer with a record rather than a machine, and which record each of // this node's modules was put on. Read across a context boundary by name, which is what @@ -136,7 +149,7 @@ func planFor(ctx context.Context, open *stores, nodeName string) (catalogue.Reso resolved, err := catalogue.Resolve(shelf, assigned, catalogue.Node{Name: nodeName, Site: site, Capabilities: capabilities, - At: onNetwork[nodeName], PublicDomain: publicDomain, + At: ownName, PublicDomain: publicDomain, Account: who.Account, AccountHome: who.AccountHome, AgentAccount: who.AgentAccount, AgentAccountHome: who.AgentAccountHome}, world) if err != nil { -- 2.54.0 From 0350b4e222764d9dbed7b799a61eb4c7c2441c9f Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 14 Oct 2026 11:06:56 +0200 Subject: [PATCH 3/3] Judge a mesh of one by the inventory, not by who reads as on the network (issue 701, review 275) The network reading leaves out a machine whose assignments cannot be read or whose modules do not resolve, so a bad reading could make a mesh of several look like one and send its machines to loopback. Count the nodes the inventory knows, and every recorded holding and assigned claimant of the resolver seat, before any address filter. --- cmd/mesh-controller/addresses_test.go | 47 ++++++++++++++++++++++++ cmd/mesh-controller/network.go | 53 ++++++++++++++++++++------- cmd/mesh-controller/plan.go | 2 +- testdata/store-tests | 1 + 4 files changed, 89 insertions(+), 14 deletions(-) diff --git a/cmd/mesh-controller/addresses_test.go b/cmd/mesh-controller/addresses_test.go index a4025239..e4071e8b 100644 --- a/cmd/mesh-controller/addresses_test.go +++ b/cmd/mesh-controller/addresses_test.go @@ -460,3 +460,50 @@ func TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt(t *testing.T) { t.Fatalf("with a resolver, the builder publishes to %q", got) } } + +// **A mesh of two in which one machine does not resolve is still a mesh of two** (review 275). The reading of +// who is on the network leaves the unresolvable laptop out, which made it look like a mesh of one; the +// store's own node must keep its internal name, and its builder keep publishing to it. +func TestAMeshOfTwoWithAnUnresolvableMachineKeepsItsNames(t *testing.T) { + open := aMesh(t) + ctx := t.Context() + register(t, open, aStore()) + register(t, open, catalogue.Manifest{Module: "builder", Version: "1", + Requires: []string{catalogue.ArtifactStoreProvision}, + Resources: []map[string]any{{"id": "env", "type": "file", "path": "/etc/mesh-builder.env", + "content": "MESH_REGISTRY=${bound:" + catalogue.ArtifactStoreProvision + ":at}:${bound:" + + catalogue.ArtifactStoreProvision + ":port}\n"}}}) + for _, module := range []string{"distribution", "builder"} { + if _, err := assign(ctx, open, "anchor", module); err != nil { + t.Fatal(err) + } + } + if err := open.inventory.SetSettings(ctx, "anchor", "distribution", + map[string]any{catalogue.PortsSetting: map[string]any{"5000": 5101}}); err != nil { + t.Fatal(err) + } + // The laptop does not resolve: two modules claim one node seat there, straight into the store. + twoRivalsAndANote(t, open) + for _, m := range []string{"rival-one", "rival-two"} { + if _, err := open.inventory.Assign(ctx, "laptop", m); err != nil { + t.Fatal(err) + } + } + shelf, err := open.inventory.Catalogue(ctx) + if err != nil { + t.Fatal(err) + } + onNetwork, err := whereEveryoneIs(ctx, open.inventory, shelf) + if err != nil || len(onNetwork) != 1 { + t.Fatalf("the unresolvable laptop is still read as on the network: %v %v", onNetwork, err) + } + + if got, err := artifactStoreAddress(ctx, open.inventory, shelf, "anchor"); err != nil || got != "anchor.internal:5101" { + t.Fatalf("a mesh of two read as one: the store's own node is handed %q (%v)", got, err) + } + for _, r := range composed(t, open, "anchor").Resources { + if r["path"] == "/etc/mesh-builder.env" && r["content"] != "MESH_REGISTRY=anchor.internal:5101\n" { + t.Fatalf("a mesh of two read as one: the builder publishes to %v", r["content"]) + } + } +} diff --git a/cmd/mesh-controller/network.go b/cmd/mesh-controller/network.go index 51944230..e9c32ae9 100644 --- a/cmd/mesh-controller/network.go +++ b/cmd/mesh-controller/network.go @@ -729,7 +729,7 @@ func artifactStoreAddress(ctx context.Context, inv *inventory.Inventory, // A mesh of more than one node is answered as before: loopback is no answer for the others, and // one name for every node is what a mesh of several without a resolver is still put together to. if node == forNode { - unanswered, err := aLoneNameNothingAnswers(ctx, inv, shelf, len(on)) + unanswered, err := aLoneNameNothingAnswers(ctx, inv) if err != nil { return "", err } @@ -746,21 +746,48 @@ func artifactStoreAddress(ctx context.Context, inv *inventory.Inventory, return "127.0.0.1:" + port, nil } -// aLoneNameNothingAnswers is whether the mesh is one node on its private network and nothing answers that -// node's internal name: no holder of the mesh's resolver seat (novox/hq ADR 0194, ADR 0223), as a machine's -// resolver file lists them (replicatedHolders). Genesis is exactly that from its network step until a -// resolver is installed (novox/hq issue 701): the node then reaches what runs on it by loopback, as it did -// before the network, and is not composed a name no lookup can answer. -func aLoneNameNothingAnswers(ctx context.Context, inv *inventory.Inventory, shelf map[string]catalogue.Manifest, - onNetwork int) (bool, error) { - if onNetwork != 1 { - return false, nil - } - holders, err := replicatedHolders(ctx, inv, shelf) +// aLoneNameNothingAnswers is whether the mesh is one node and nothing will answer that node's internal +// name: the inventory knows exactly one node, and no module holds or claims the mesh's resolver seat +// anywhere (novox/hq ADR 0194, ADR 0223). Genesis is exactly that from its network step until a resolver is +// installed (novox/hq issue 701): the node then reaches what runs on it by loopback, as it did before the +// network, and is not composed a name no lookup can answer. +// +// **Read from the mesh, never from who is on the network** (review 275): that reading leaves out a machine +// whose assignments cannot be read or whose modules do not resolve, and a bad reading must never make a +// mesh of several look like a mesh of one and send its machines to loopback. Every recorded holding and +// every assignment of a claimant counts, wherever it is and whether or not its machine has an address. +func aLoneNameNothingAnswers(ctx context.Context, inv *inventory.Inventory) (bool, error) { + nodes, err := inv.Nodes(ctx) if err != nil { return false, err } - return len(holders[resolverSeat]) == 0, nil + if len(nodes) != 1 { + return false, nil + } + holdings, err := inv.Holdings(ctx) + if err != nil { + return false, err + } + for _, h := range holdings { + if s, ok := catalogue.SeatNamed(h.Claim); ok && s.Name == resolverSeat { + return false, nil + } + } + entries, err := inv.Catalogued(ctx) + if err != nil { + return false, err + } + for _, e := range entries { + if len(e.On) == 0 { + continue + } + for _, c := range e.Manifest.Claims { + if s, ok := catalogue.SeatNamed(c.Name); ok && s.Name == resolverSeat { + return false, nil + } + } + } + return true, nil } // artifactStoreHolder is whichever node is assigned a module offering the artifact store, on or diff --git a/cmd/mesh-controller/plan.go b/cmd/mesh-controller/plan.go index 3d11f00d..e0deab4c 100644 --- a/cmd/mesh-controller/plan.go +++ b/cmd/mesh-controller/plan.go @@ -108,7 +108,7 @@ func planFor(ctx context.Context, open *stores, nodeName string) (catalogue.Reso // loopback, as before the network, rather than by a name no lookup on the machine can answer. ownName := onNetwork[nodeName] if ownName != "" { - unanswered, err := aLoneNameNothingAnswers(ctx, inv, shelf, len(onNetwork)) + unanswered, err := aLoneNameNothingAnswers(ctx, inv) if err != nil { return catalogue.Resolution{}, nil, err } diff --git a/testdata/store-tests b/testdata/store-tests index e0952edc..2b7fe261 100644 --- a/testdata/store-tests +++ b/testdata/store-tests @@ -19,6 +19,7 @@ ./internal/inventory TestAWalksStartIsKeptAndARefusedSaveLeavesItUnsaid ./cmd/mesh-controller TestAWalkLetGoAndStoppedByVerbsIsSaidStartedAndEnded ./cmd/mesh-controller TestAMeshOfOneIsHandedNoNameUntilAResolverAnswersIt +./cmd/mesh-controller TestAMeshOfTwoWithAnUnresolvableMachineKeepsItsNames ./cmd/mesh-controller TestAWalkStoppedOrClosedByAPersonIsSaidEndedStopped ./cmd/mesh-controller TestAWalkRetriedByAPersonIsSaidStartedAgain ./cmd/mesh-controller TestABacklogReleasedByAPersonIsSaidStarted -- 2.54.0