package host import ( "bytes" "context" "encoding/json" "errors" "log" "path/filepath" "strings" "testing" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" ) func testRegistry(t *testing.T) *registry.Registry { t.Helper() reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db")) if err != nil { t.Fatalf("open registry: %v", err) } t.Cleanup(func() { reg.Close() }) return reg } // --- Critical 1 (Opus review, fix round): channel-label identity ------------- // TestPublishedBindingBoxMatchesTunnelChannelName is the reviewer-named test: // a published binding's Box, joined with its Fleet as "fleet/box", must // equal a channel name edge.fleetChannelFor's rule 1 would actually resolve // — that function's whole check is // `bound := fleet + "/" + boxLabel; hub.State(bound) == "connected"`, and a // box only ever shows up connected in the tunnel hub under the exact string // its own tunnel client presented as CARLOS_TUNNEL_NAME // (internal/host/tunnel_client.go's tunnelLoop dials with that env var // verbatim). This exercises the full pipeline this task wires — // resolveFleetIdentity -> fleetBindingHook -> WriteFleetBinding — and // asserts its published Box is dial-able, not just present. func TestPublishedBindingBoxMatchesTunnelChannelName(t *testing.T) { const tunnelName = "pi/amber-gliding" // what this box's own tunnel client presents id, err := resolveFleetIdentity(envFunc(map[string]string{ "CARLOS_FLEET": "pi", "CARLOS_TUNNEL_NAME": tunnelName, })) if err != nil { t.Fatalf("resolveFleetIdentity: %v", err) } ctx := context.Background() s := release.NewDirStore(t.TempDir()) hook := fleetBindingHook(s, id.Fleet, id.Label) hook(registry.Route{Host: "one.example.com", Kind: "instance"}) body, _, err := s.Get(ctx, fleetBindingKey(id.Fleet, "one.example.com")) if err != nil { t.Fatalf("Get: %v", err) } var b FleetBinding if err := json.Unmarshal(body, &b); err != nil { t.Fatalf("unmarshal: %v", err) } channel := b.Fleet + "/" + b.Box if channel != tunnelName { t.Fatalf("published binding's fleet/box = %q, want %q (this box's own CARLOS_TUNNEL_NAME) — a mismatch is a binding the edge could never dial", channel, tunnelName) } } // --- WriteFleetBinding -------------------------------------------------------- func TestWriteFleetBindingRoundTrips(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) b := FleetBinding{Host: "one.example.com", Fleet: "pi", Box: "amber-gliding", BoundAt: 1700000000000} if err := WriteFleetBinding(ctx, s, b, true); err != nil { t.Fatalf("WriteFleetBinding: %v", err) } body, _, err := s.Get(ctx, "fleets/pi/bindings/one.example.com.json") if err != nil { t.Fatalf("Get: %v", err) } var got FleetBinding if err := json.Unmarshal(body, &got); err != nil { t.Fatalf("unmarshal: %v", err) } if got != b { t.Fatalf("round-tripped binding = %+v, want %+v", got, b) } } // A box republishes to the SAME key on every wake and once more at agent // startup — a bare create-only Put would fail every write after the first // with ErrCASConflict, which is exactly the bug the read-etag-then-Put dance // (mirroring status.WriteBox) exists to avoid. func TestWriteFleetBindingOverwritesExistingKey(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) first := FleetBinding{Host: "one.example.com", Fleet: "pi", Box: "amber-gliding", BoundAt: 100} second := FleetBinding{Host: "one.example.com", Fleet: "pi", Box: "amber-gliding", BoundAt: 200} if err := WriteFleetBinding(ctx, s, first, true); err != nil { t.Fatalf("first write: %v", err) } if err := WriteFleetBinding(ctx, s, second, true); err != nil { t.Fatalf("second write (must replace, not conflict): %v", err) } body, _, err := s.Get(ctx, "fleets/pi/bindings/one.example.com.json") if err != nil { t.Fatalf("Get: %v", err) } var got FleetBinding if err := json.Unmarshal(body, &got); err != nil { t.Fatalf("unmarshal: %v", err) } if got.BoundAt != 200 { t.Fatalf("BoundAt = %d, want the second write's 200", got.BoundAt) } } // Critical 2 (Opus review, fix round): a non-takeover write (the startup // pass's shape) must never steal a binding that already names a DIFFERENT // box — only a takeover=true (wake-success) write may do that. func TestWriteFleetBindingNonTakeoverNeverStealsAnotherBox(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) held := FleetBinding{Host: "one.example.com", Fleet: "pi", Box: "box-a", BoundAt: 100} if err := WriteFleetBinding(ctx, s, held, true); err != nil { t.Fatalf("seed box-a's binding: %v", err) } // box-b observes the SAME host resident locally (e.g. a stale local // route not yet cleaned up) and tries a non-takeover (startup-pass) // write with a LATER BoundAt — later in time must still lose, because // takeover=false is a non-authoritative observation, not a lease. attempt := FleetBinding{Host: "one.example.com", Fleet: "pi", Box: "box-b", BoundAt: 999} if err := WriteFleetBinding(ctx, s, attempt, false); err != nil { t.Fatalf("non-takeover write: %v", err) } body, _, err := s.Get(ctx, "fleets/pi/bindings/one.example.com.json") if err != nil { t.Fatalf("Get: %v", err) } var got FleetBinding if err := json.Unmarshal(body, &got); err != nil { t.Fatalf("unmarshal: %v", err) } if got.Box != "box-a" { t.Fatalf("Box = %q, want box-a (non-takeover write must not steal)", got.Box) } } // A takeover=true write (a real wake success) MAY claim a binding away from // a different box — this is the counterpart proving the non-stealing rule // above is specific to non-takeover callers, not a blanket "first box wins". func TestWriteFleetBindingTakeoverClaimsFromAnotherBox(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) held := FleetBinding{Host: "one.example.com", Fleet: "pi", Box: "box-a", BoundAt: 100} if err := WriteFleetBinding(ctx, s, held, true); err != nil { t.Fatalf("seed box-a's binding: %v", err) } claim := FleetBinding{Host: "one.example.com", Fleet: "pi", Box: "box-b", BoundAt: 200} if err := WriteFleetBinding(ctx, s, claim, true); err != nil { t.Fatalf("takeover write: %v", err) } body, _, err := s.Get(ctx, "fleets/pi/bindings/one.example.com.json") if err != nil { t.Fatalf("Get: %v", err) } var got FleetBinding if err := json.Unmarshal(body, &got); err != nil { t.Fatalf("unmarshal: %v", err) } if got.Box != "box-b" { t.Fatalf("Box = %q, want box-b (a wake-success takeover must be able to claim)", got.Box) } } // Fix round 2 (Opus re-review): the logical-clock guard must be scoped to // the SAME box only — BoundAt is each box's own wall clock, never a shared // one. A dead box A's binding stamped with a clock running ahead of // surviving box B's must not permanently block B's lease-backed takeover // write: box-a's stale binding here carries a BoundAt in the FUTURE // relative to box-b's claim, and the takeover must still go through, // because a cross-box BoundAt comparison is comparing skew, not recency. func TestWriteFleetBindingTakeoverIgnoresOtherBoxsFutureBoundAt(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) stale := FleetBinding{Host: "one.example.com", Fleet: "pi", Box: "box-a", BoundAt: 9_000_000_000_000} if err := WriteFleetBinding(ctx, s, stale, true); err != nil { t.Fatalf("seed box-a's (clock-ahead) binding: %v", err) } claim := FleetBinding{Host: "one.example.com", Fleet: "pi", Box: "box-b", BoundAt: 1_000_000_000_000} if err := WriteFleetBinding(ctx, s, claim, true); err != nil { t.Fatalf("takeover write: %v", err) } body, _, err := s.Get(ctx, "fleets/pi/bindings/one.example.com.json") if err != nil { t.Fatalf("Get: %v", err) } var got FleetBinding if err := json.Unmarshal(body, &got); err != nil { t.Fatalf("unmarshal: %v", err) } if got.Box != "box-b" { t.Fatalf("Box = %q, want box-b — a lease-backed takeover must not be blocked by a DIFFERENT box's clock running ahead", got.Box) } } // Important 5 (Opus review, fix round): a stored binding with a strictly // greater BoundAt must never be overwritten by an older one, even by the // SAME box, and even with takeover=true — a physical write race must not // let older information win just because its Put happens to land second. func TestWriteFleetBindingSkipsWhenStoredIsLogicallyNewer(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) newer := FleetBinding{Host: "one.example.com", Fleet: "pi", Box: "amber-gliding", BoundAt: 2000} if err := WriteFleetBinding(ctx, s, newer, true); err != nil { t.Fatalf("seed newer binding: %v", err) } older := FleetBinding{Host: "one.example.com", Fleet: "pi", Box: "amber-gliding", BoundAt: 1000} if err := WriteFleetBinding(ctx, s, older, true); err != nil { t.Fatalf("write older binding: %v", err) } body, _, err := s.Get(ctx, "fleets/pi/bindings/one.example.com.json") if err != nil { t.Fatalf("Get: %v", err) } var got FleetBinding if err := json.Unmarshal(body, &got); err != nil { t.Fatalf("unmarshal: %v", err) } if got.BoundAt != 2000 { t.Fatalf("BoundAt = %d, want 2000 (the older write must be skipped)", got.BoundAt) } } // --- ReconcileFleetBindings --------------------------------------------------- func TestReconcileFleetBindingsStampsMatchingRoute(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) reg := testRegistry(t) if err := reg.Upsert(registry.Route{Host: "one.example.com", Kind: "instance", Region: "fleet/pi"}); err != nil { t.Fatalf("upsert: %v", err) } if err := WriteFleetBinding(ctx, s, FleetBinding{ Host: "one.example.com", Fleet: "pi", Box: "amber-gliding", BoundAt: 1000, }, true); err != nil { t.Fatalf("WriteFleetBinding: %v", err) } if err := ReconcileFleetBindings(ctx, s, reg); err != nil { t.Fatalf("ReconcileFleetBindings: %v", err) } rt, err := reg.Get("one.example.com") if err != nil { t.Fatalf("Get: %v", err) } if rt.FleetBox != "amber-gliding" { t.Fatalf("FleetBox = %q, want amber-gliding", rt.FleetBox) } } // A route with no published binding at all — a non-fleet route, or a fleet // route this platform box hasn't heard from yet — must be left completely // alone: ReconcileFleetBindings only ever calls SetFleetBox for a host it // found a MATCHING binding for. func TestReconcileFleetBindingsLeavesNonFleetRoutesAlone(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) reg := testRegistry(t) if err := reg.Upsert(registry.Route{Host: "static.example.com", Kind: "static"}); err != nil { t.Fatalf("upsert: %v", err) } if err := ReconcileFleetBindings(ctx, s, reg); err != nil { t.Fatalf("ReconcileFleetBindings: %v", err) } rt, err := reg.Get("static.example.com") if err != nil { t.Fatalf("Get: %v", err) } if rt.FleetBox != "" { t.Fatalf("FleetBox = %q, want empty (untouched)", rt.FleetBox) } } // Important 4 (Opus review, fix round): a binding published under fleet // "eleven" must never stamp a route actually PLACED on fleet "pi" — even // with a far newer BoundAt than anything "pi" published. This is the v1 // trust-boundary structural check that replaced an earlier "newest BoundAt // wins across fleets" reduction, which this scenario would have wrongly let // the mismatched, newer binding win. func TestReconcileFleetBindingsRejectsMismatchedFleetPlacement(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) reg := testRegistry(t) if err := reg.Upsert(registry.Route{Host: "shared.example.com", Kind: "instance", Region: "fleet/pi"}); err != nil { t.Fatalf("upsert: %v", err) } // A binding published under the WRONG fleet, naming the same host, with // a much later BoundAt. if err := WriteFleetBinding(ctx, s, FleetBinding{ Host: "shared.example.com", Fleet: "eleven", Box: "eleven-box", BoundAt: 9_000_000_000_000, }, true); err != nil { t.Fatalf("write eleven binding: %v", err) } if err := ReconcileFleetBindings(ctx, s, reg); err != nil { t.Fatalf("ReconcileFleetBindings: %v", err) } rt, err := reg.Get("shared.example.com") if err != nil { t.Fatalf("Get: %v", err) } if rt.FleetBox != "" { t.Fatalf("FleetBox = %q, want empty — a mismatched fleet's binding must never stamp this route", rt.FleetBox) } } // The positive counterpart: a binding published under the route's OWN fleet // (Region "fleet/pi") DOES get applied, proving the rejection above is about // the mismatch specifically, not FleetPlacement/ReconcileFleetBindings being // broadly broken. func TestReconcileFleetBindingsAppliesMatchingFleetPlacement(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) reg := testRegistry(t) if err := reg.Upsert(registry.Route{Host: "shared.example.com", Kind: "instance", Region: "fleet/pi"}); err != nil { t.Fatalf("upsert: %v", err) } if err := WriteFleetBinding(ctx, s, FleetBinding{ Host: "shared.example.com", Fleet: "pi", Box: "pi-box", BoundAt: 1000, }, true); err != nil { t.Fatalf("write pi binding: %v", err) } if err := ReconcileFleetBindings(ctx, s, reg); err != nil { t.Fatalf("ReconcileFleetBindings: %v", err) } rt, err := reg.Get("shared.example.com") if err != nil { t.Fatalf("Get: %v", err) } if rt.FleetBox != "pi-box" { t.Fatalf("FleetBox = %q, want pi-box", rt.FleetBox) } } // A corrupt binding object must be logged and skipped, never fatal — the // loadBoxes precedent (internal/status/fleet.go) — and must not stop the // pass from stamping every OTHER host's valid binding. func TestReconcileFleetBindingsSkipsCorruptJSON(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) reg := testRegistry(t) if err := reg.Upsert(registry.Route{Host: "good.example.com", Kind: "instance", Region: "fleet/pi"}); err != nil { t.Fatalf("upsert good: %v", err) } if err := reg.Upsert(registry.Route{Host: "bad.example.com", Kind: "instance", Region: "fleet/pi"}); err != nil { t.Fatalf("upsert bad: %v", err) } if _, err := s.Put(ctx, "fleets/pi/bindings/bad.example.com.json", []byte("{ not json"), ""); err != nil { t.Fatalf("seed corrupt object: %v", err) } if err := WriteFleetBinding(ctx, s, FleetBinding{ Host: "good.example.com", Fleet: "pi", Box: "amber-gliding", BoundAt: 1000, }, true); err != nil { t.Fatalf("WriteFleetBinding: %v", err) } if err := ReconcileFleetBindings(ctx, s, reg); err != nil { t.Fatalf("ReconcileFleetBindings: %v", err) } good, err := reg.Get("good.example.com") if err != nil { t.Fatalf("Get good: %v", err) } if good.FleetBox != "amber-gliding" { t.Fatalf("good.FleetBox = %q, want amber-gliding despite the corrupt sibling", good.FleetBox) } bad, err := reg.Get("bad.example.com") if err != nil { t.Fatalf("Get bad: %v", err) } if bad.FleetBox != "" { t.Fatalf("bad.FleetBox = %q, want empty (corrupt binding never stamped)", bad.FleetBox) } } // Already-stamped, matching FleetBox must not trigger a needless SetFleetBox // call — proven indirectly by asserting the value survives a second // reconcile pass unchanged. func TestReconcileFleetBindingsIdempotent(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) reg := testRegistry(t) if err := reg.Upsert(registry.Route{Host: "one.example.com", Kind: "instance", Region: "fleet/pi"}); err != nil { t.Fatalf("upsert: %v", err) } if err := WriteFleetBinding(ctx, s, FleetBinding{ Host: "one.example.com", Fleet: "pi", Box: "amber-gliding", BoundAt: 1000, }, true); err != nil { t.Fatalf("WriteFleetBinding: %v", err) } for i := 0; i < 2; i++ { if err := ReconcileFleetBindings(ctx, s, reg); err != nil { t.Fatalf("ReconcileFleetBindings pass %d: %v", i, err) } } rt, err := reg.Get("one.example.com") if err != nil { t.Fatalf("Get: %v", err) } if rt.FleetBox != "amber-gliding" { t.Fatalf("FleetBox = %q, want amber-gliding", rt.FleetBox) } } // --- publishResidentFleetBindings (agent-startup pass) ------------------------ func TestPublishResidentFleetBindingsCoversEveryLocalRoute(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) reg := testRegistry(t) if err := reg.Upsert(registry.Route{Host: "one.example.com", Kind: "instance"}); err != nil { t.Fatalf("upsert one: %v", err) } if err := reg.Upsert(registry.Route{Host: "two.example.com", Kind: "instance"}); err != nil { t.Fatalf("upsert two: %v", err) } publishResidentFleetBindings(s, "pi", "amber-gliding", reg) for _, host := range []string{"one.example.com", "two.example.com"} { body, _, err := s.Get(ctx, fleetBindingKey("pi", host)) if err != nil { t.Fatalf("Get %s: %v", host, err) } var b FleetBinding if err := json.Unmarshal(body, &b); err != nil { t.Fatalf("unmarshal %s: %v", host, err) } if b.Host != host || b.Fleet != "pi" || b.Box != "amber-gliding" || b.BoundAt == 0 { t.Fatalf("binding for %s = %+v, want host/fleet/box populated and a nonzero BoundAt", host, b) } } } // A registry with no routes at all must not error or panic — a startup pass // on a freshly-provisioned fleet box, before any host has been assigned. func TestPublishResidentFleetBindingsEmptyRegistryIsNoop(t *testing.T) { s := release.NewDirStore(t.TempDir()) reg := testRegistry(t) publishResidentFleetBindings(s, "pi", "amber-gliding", reg) keys, err := s.List(context.Background(), "fleets/") if err != nil { t.Fatalf("List: %v", err) } if len(keys) != 0 { t.Fatalf("keys = %v, want none written for an empty registry", keys) } } // Critical 2 (Opus review, fix round): the missing test the reviewer named — // the startup pass must be non-stealing. A route resident locally on THIS // box (box-b) already has a binding published by a DIFFERENT box (box-a); // the startup pass must leave it alone rather than overwrite it, since it // has no lease behind it, unlike a wake-success write. func TestPublishResidentFleetBindingsDoesNotStealAnotherBoxsBinding(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) reg := testRegistry(t) if err := reg.Upsert(registry.Route{Host: "one.example.com", Kind: "instance"}); err != nil { t.Fatalf("upsert: %v", err) } if err := WriteFleetBinding(ctx, s, FleetBinding{ Host: "one.example.com", Fleet: "pi", Box: "box-a", BoundAt: 100, }, true); err != nil { t.Fatalf("seed box-a's binding: %v", err) } // box-b's startup pass encounters the same host resident in ITS OWN // local registry (e.g. a route not yet cleaned up after a failover) and // must not steal box-a's binding. publishResidentFleetBindings(s, "pi", "box-b", reg) body, _, err := s.Get(ctx, fleetBindingKey("pi", "one.example.com")) if err != nil { t.Fatalf("Get: %v", err) } var got FleetBinding if err := json.Unmarshal(body, &got); err != nil { t.Fatalf("unmarshal: %v", err) } if got.Box != "box-a" { t.Fatalf("Box = %q, want box-a (the startup pass must not steal)", got.Box) } } // --- fleetBindingHook (fire-and-forget wake publish) -------------------------- // errPutStore fails every Put while otherwise behaving like a normal // DirStore (Get/List/ListDirs/Delete pass through) — enough to exercise // WriteFleetBinding's error path without a broken embedded nil Store. type errPutStore struct{ release.Store } func (errPutStore) Put(ctx context.Context, key string, body []byte, ifMatch string) (string, error) { return "", errors.New("boom: put refused") } func TestFleetBindingHookLogsErrorWithoutPanicking(t *testing.T) { var buf bytes.Buffer prev := log.Writer() log.SetOutput(&buf) defer log.SetOutput(prev) store := errPutStore{Store: release.NewDirStore(t.TempDir())} hook := fleetBindingHook(store, "pi", "amber-gliding") hook(registry.Route{Host: "one.example.com", Kind: "instance"}) // must not panic even though every Put fails if !strings.Contains(buf.String(), "fleet binding") { t.Fatalf("expected the Put failure to be logged, got: %q", buf.String()) } } func TestFleetBindingHookWritesBinding(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) hook := fleetBindingHook(s, "pi", "amber-gliding") hook(registry.Route{Host: "woken.example.com", Kind: "instance"}) body, _, err := s.Get(ctx, fleetBindingKey("pi", "woken.example.com")) if err != nil { t.Fatalf("Get: %v", err) } var b FleetBinding if err := json.Unmarshal(body, &b); err != nil { t.Fatalf("unmarshal: %v", err) } if b.Host != "woken.example.com" || b.Fleet != "pi" || b.Box != "amber-gliding" { t.Fatalf("binding = %+v, want host/fleet/box populated from the hook's closure", b) } } // fleetBindingHook must publish with takeover=true — a wake success is // lease-backed authority to claim a binding away from a different box. // Proven end-to-end here (rather than just trusting the source) since a // regression (accidentally passing false) would only show up as a silent // no-op against an existing binding, never a compile error. func TestFleetBindingHookTakesOverFromAnotherBox(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) if err := WriteFleetBinding(ctx, s, FleetBinding{ Host: "one.example.com", Fleet: "pi", Box: "old-box", BoundAt: 100, }, true); err != nil { t.Fatalf("seed old-box's binding: %v", err) } hook := fleetBindingHook(s, "pi", "new-box") hook(registry.Route{Host: "one.example.com", Kind: "instance"}) body, _, err := s.Get(ctx, fleetBindingKey("pi", "one.example.com")) if err != nil { t.Fatalf("Get: %v", err) } var got FleetBinding if err := json.Unmarshal(body, &got); err != nil { t.Fatalf("unmarshal: %v", err) } if got.Box != "new-box" { t.Fatalf("Box = %q, want new-box (OnWake's hook must take over)", got.Box) } } // TestSidecarBindingIsNeverPublished: a fleet binding says "this box serves // this host". Nothing routes to a sidecar, so publishing one would be a lie // the platform-side reconciler then acts on — ReconcileFleetBindings stamps // SetFleetBox from it, and a fleet-placed route is exactly what the edge's // fleet dispatch arm binds. The filter therefore lives in the publisher's own // host selection, not in the reconciler that reads it back. func TestSidecarBindingIsNeverPublished(t *testing.T) { got := residentBindingHosts([]registry.Route{ {Host: "app.example.com", Kind: "instance", Socket: "/run/carlos/a.sock"}, {Host: "bot.eleven.bdm.sidecar.internal", Kind: "sidecar", Region: "fleet/eleven"}, }) if len(got) != 1 || got[0] != "app.example.com" { t.Fatalf("published %v — a sidecar must never be advertised as served by a box", got) } } // The end-to-end shape of the same refusal: the startup pass over a registry // holding both kinds must write a binding object for the instance and none // at all for the sidecar. func TestPublishResidentFleetBindingsSkipsSidecars(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) reg := testRegistry(t) if err := reg.Upsert(registry.Route{Host: "app.example.com", Kind: "instance"}); err != nil { t.Fatalf("upsert instance: %v", err) } if err := reg.Upsert(registry.Route{Host: "bot.eleven.bdm.sidecar.internal", Kind: "sidecar", Region: "fleet/eleven"}); err != nil { t.Fatalf("upsert sidecar: %v", err) } publishResidentFleetBindings(s, "eleven", "box1", reg) if _, _, err := s.Get(ctx, fleetBindingKey("eleven", "app.example.com")); err != nil { t.Fatalf("instance binding missing: %v", err) } if _, _, err := s.Get(ctx, fleetBindingKey("eleven", "bot.eleven.bdm.sidecar.internal")); err == nil { t.Fatalf("a sidecar binding was published — a box must never advertise a sidecar as served") } } // --- Fix round 1: the other two ends of the same chain ------------------------ // TestFleetBindingHookNeverPublishesSidecar covers the WAKE publish path, // which the startup-pass filter does not reach. It matters more than the // startup pass, not less: this hook writes with takeover=true, so an errant // sidecar binding here does not merely appear — it claims the host away from // whichever box legitimately held it. // // Deliberately not resting on "the activator never wakes a sidecar": that is // true of the activator today and it is a fact a later task on this same // branch removes, at which point the hole would open silently. An ordinary // instance host must still be published, so this asserts both directions. // // The kind arrives on the route the wake already holds — no registry read, so // no I/O failure can make this refusal fail open. func TestFleetBindingHookNeverPublishesSidecar(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) hook := fleetBindingHook(s, "pi", "box1") hook(registry.Route{Host: "app.example.com", Kind: "instance", Region: "fleet/pi"}) hook(registry.Route{Host: "bot.eleven.bdm.sidecar.internal", Kind: "sidecar", Region: "fleet/pi"}) if _, _, err := s.Get(ctx, fleetBindingKey("pi", "app.example.com")); err != nil { t.Fatalf("instance binding missing from the wake publish: %v", err) } if _, _, err := s.Get(ctx, fleetBindingKey("pi", "bot.eleven.bdm.sidecar.internal")); err == nil { t.Fatalf("the wake hook published a sidecar binding — with takeover semantics, no less") } } // TestReconcileFleetBindingsNeverStampsSidecar covers the READING side. A // binding is an untrusted claim written by another machine that upgrades on // its own schedule: through a mixed-version roll, a fleet box still on an // older binary keeps publishing sidecar bindings, and this platform box is // what acts on them. SetFleetBox is the act that hands a fleet-placed route to // the edge's fleet dispatch arm, so it must refuse on kind independently of // whoever wrote the object. // // Both routes here are placed on the SAME fleet as their binding, so the // pre-existing placement check passes for both — the kind guard is the only // thing that can distinguish them. func TestReconcileFleetBindingsNeverStampsSidecar(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) reg := testRegistry(t) const sidecarHost = "bot.eleven.bdm.sidecar.internal" if err := reg.Upsert(registry.Route{Host: "app.example.com", Kind: "instance", Region: "fleet/pi"}); err != nil { t.Fatalf("upsert instance: %v", err) } if err := reg.Upsert(registry.Route{Host: sidecarHost, Kind: "sidecar", Region: "fleet/pi"}); err != nil { t.Fatalf("upsert sidecar: %v", err) } // Both bindings written directly, as an older box's publisher would. for _, host := range []string{"app.example.com", sidecarHost} { if err := WriteFleetBinding(ctx, s, FleetBinding{ Host: host, Fleet: "pi", Box: "box1", BoundAt: 100, }, true); err != nil { t.Fatalf("seed binding for %s: %v", host, err) } } if err := ReconcileFleetBindings(ctx, s, reg); err != nil { t.Fatalf("ReconcileFleetBindings: %v", err) } rt, err := reg.Get("app.example.com") if err != nil || rt == nil { t.Fatalf("get instance route: %v", err) } if rt.FleetBox != "box1" { t.Fatalf("instance FleetBox = %q, want box1 — ordinary routes must still be stamped", rt.FleetBox) } sc, err := reg.Get(sidecarHost) if err != nil || sc == nil { t.Fatalf("get sidecar route: %v", err) } if sc.FleetBox != "" { t.Fatalf("sidecar FleetBox = %q, want empty — stamping it hands the row to the edge's fleet arm", sc.FleetBox) } }