From 72f30e9c29b7bc47c3f8d2094db4c06afa8edae3 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 10 Sep 2026 21:17:09 +0200 Subject: [PATCH] overlay: placing a node with nothing said no longer unplaces it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sibling of node public-domain, and the worse one: a placement is three facts declared together, so an invocation that said none of them took all three away — the endpoint every other machine dials, the site, and the hub. A mesh whose hub was placed that way has no paths left, at the moment somebody was trying to look at it. --nothing keeps the real case (a machine that roams and opens every path itself) sayable, by name. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF --- cmd/mesh-control/network.go | 24 +++++++++- cmd/mesh-control/network_test.go | 78 ++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 cmd/mesh-control/network_test.go diff --git a/cmd/mesh-control/network.go b/cmd/mesh-control/network.go index 53ce35c..c1a2d82 100644 --- a/cmd/mesh-control/network.go +++ b/cmd/mesh-control/network.go @@ -59,7 +59,8 @@ func overlayCommand(ctx context.Context, args []string) error { func overlayPlace(ctx context.Context, inv *inventory.Inventory, args []string) error { if len(args) == 0 { - return errors.New("overlay place [--endpoint host:port] [--site name] [--hub]") + return errors.New( + "overlay place [--endpoint host:port] [--site name] [--hub], or --nothing") } node := args[0] @@ -67,10 +68,31 @@ func overlayPlace(ctx context.Context, inv *inventory.Inventory, args []string) endpoint := set.String("endpoint", "", "where this node can be dialled, or empty for nowhere") site := set.String("site", "", "where this machine physically is, or empty if it roams") hub := set.Bool("hub", false, "this node is the hub every other routes through") + nothing := set.Bool("nothing", false, + "place it with nothing set: not dialable, no site, not the hub") if err := set.Parse(args[1:]); err != nil { return err } + // **All three are declared together, so saying nothing took all three away.** The sibling of + // `node public-domain`: `overlay place anchor` reads like it places the node it names, and it + // silently unset the endpoint every other machine dials, the site it is in, and the hub if it + // was the hub — every path through it going with them, at the moment somebody was trying to + // look at it. + // + // A placement with nothing set is a real thing to want — a machine that roams and opens every + // path itself is exactly that — so it keeps a way to say so, by name. + if set.NFlag() == 0 { + return fmt.Errorf("overlay place %s was given nothing to place it with, and all three are "+ + "declared together — it would take away the endpoint other machines dial %s at, its "+ + "site, and the hub if it is the hub. Say --endpoint/--site/--hub, or --nothing if that "+ + "is what you meant", node, node) + } + if *nothing && (*endpoint != "" || *site != "" || *hub) { + return fmt.Errorf("give %s a placement or --nothing, not both: they say opposite things "+ + "and the mesh will not choose between them", node) + } + // Declared, all three. The address is evidence of reachability and is not the fact, and hub // election by address prefix fails silently (novox/hq ADR 0007). if err := inv.SetPlace(ctx, node, *endpoint, *site, *hub, ""); err != nil { diff --git a/cmd/mesh-control/network_test.go b/cmd/mesh-control/network_test.go new file mode 100644 index 0000000..3c354c6 --- /dev/null +++ b/cmd/mesh-control/network_test.go @@ -0,0 +1,78 @@ +package main + +import ( + "context" + "strings" + "testing" + + "github.com/novox/mesh-control/internal/inventory" +) + +// placementOf is what the mesh holds about where one node is. +func placementOf(t *testing.T, ctx context.Context, inv *inventory.Inventory, name string) inventory.Overlay { + t.Helper() + placed, err := inv.Overlays(ctx) + if err != nil { + t.Fatal(err) + } + for _, one := range placed { + if one.Name == name { + return one + } + } + t.Fatalf("%s is not placed at all", name) + return inventory.Overlay{} +} + +// The sibling of `node public-domain`, and the worse one: a placement is three facts declared +// together, so an invocation that said none of them took all three away — the endpoint every other +// machine dials, the site, and the hub. A mesh whose hub was placed that way has no paths left. +func TestPlacingANodeWithNothingSaidDoesNotUnplaceIt(t *testing.T) { + open := aMesh(t) + ctx := t.Context() + if err := overlayPlace(ctx, open.inventory, + []string{"anchor", "--endpoint", "198.51.100.10:51820", "--site", "hosting", "--hub"}); err != nil { + t.Fatal(err) + } + + err := overlayPlace(ctx, open.inventory, []string{"anchor"}) + if err == nil { + t.Fatal("saying nothing unplaced the node instead of being refused") + } + if !strings.Contains(err.Error(), "--nothing") { + t.Errorf("the refusal does not say how to mean it: %v", err) + } + + held := placementOf(t, ctx, open.inventory, "anchor") + if held.Endpoint != "198.51.100.10:51820" || held.Site != "hosting" || !held.Hub { + t.Fatalf("the placement was taken away by an invocation that was refused: %+v", held) + } +} + +// Placing a machine with nothing set is a real thing to want — one that roams and opens every path +// itself is exactly that — so it keeps a way to be said, by name. +func TestPlacingANodeWithNothingIsAskedForByName(t *testing.T) { + open := aMesh(t) + ctx := t.Context() + if err := overlayPlace(ctx, open.inventory, + []string{"anchor", "--endpoint", "198.51.100.10:51820", "--site", "hosting"}); err != nil { + t.Fatal(err) + } + if err := overlayPlace(ctx, open.inventory, []string{"anchor", "--nothing"}); err != nil { + t.Fatal(err) + } + held := placementOf(t, ctx, open.inventory, "anchor") + if held.Endpoint != "" || held.Site != "" || held.Hub { + t.Fatalf("--nothing did not place it with nothing: %+v", held) + } +} + +// Both at once cannot be meant, so neither silently wins. +func TestAPlacementAndNothingTogetherIsRefused(t *testing.T) { + open := aMesh(t) + ctx := t.Context() + if err := overlayPlace(ctx, open.inventory, + []string{"anchor", "--site", "hosting", "--nothing"}); err == nil { + t.Fatal("a placement and --nothing together was accepted") + } +}