carlos / platform Public

appconfig: narrow BlockedStamps to the written environment

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 467c30282e97768e69ca463d95fe8cbd062429d4 parent efbfb1d
2 files changed, +37 −5
  • internal/appconfig/awaitingrestart_test.go +24 −0
  • internal/appconfig/syncexec.go +13 −5
diff --git a/internal/appconfig/awaitingrestart_test.go b/internal/appconfig/awaitingrestart_test.go
index 91e782d6b..073ecdf10 100644
--- a/internal/appconfig/awaitingrestart_test.go
+++ b/internal/appconfig/awaitingrestart_test.go
@@ -346,3 +346,27 @@ func TestAwaitingRestartBlockedStamps(t *testing.T) {
t.Fatal("Blocked must count as something to say")
}
}
+
+// TestAwaitingRestartBlockedStampsAreNarrowedToTheWrittenEnvironment pins
+// spec §3.4's "the same environment and account narrowing applies as for
+// Hosts": a write to one environment's bundle must not report a blocked
+// stamped route bound to a DIFFERENT environment (or unbound), since that
+// write never reached it.
+func TestAwaitingRestartBlockedStampsAreNarrowedToTheWrittenEnvironment(t *testing.T) {
+ ctx, s, _, _, _, _ := execKit(t)
+ prod := addrRoute("prod.example.com", "relay", "bab", "prod")
+ prod.Unit, prod.Addr, prod.Socket = "prod.service", "", "/run/prod.sock"
+ staging := addrRoute("staging.example.com", "relay", "bab", "staging")
+ staging.Unit, staging.Addr, staging.Socket = "staging.service", "", "/run/staging.sock"
+ routes := []registry.Route{prod, staging}
+
+ got := AwaitingRestart(ctx, s, routes, "bab", "relay", "staging", nil)
+ if len(got.Blocked) != 1 || got.Blocked[0].Host != "staging.example.com" {
+ t.Fatalf("Blocked = %+v, want only staging.example.com for a write to staging", got.Blocked)
+ }
+
+ got = AwaitingRestart(ctx, s, routes, "bab", "relay", "", nil)
+ if len(got.Blocked) != 2 {
+ t.Fatalf("Blocked = %+v, want both prod and staging for a default-bundle write", got.Blocked)
+ }
+}
diff --git a/internal/appconfig/syncexec.go b/internal/appconfig/syncexec.go
index fe5b56ec5..7e5c32fa4 100644
--- a/internal/appconfig/syncexec.go
+++ b/internal/appconfig/syncexec.go
@@ -671,10 +671,15 @@ func (r RestartPending) Empty() bool {
}
// BlockedStamps applies the door-side rails to the stamped routes of
-// (acct, app) in routes. Uniqueness is checked against the FULL list, so
-// a route of another account blocks this one, but only this account's
-// hosts are returned and no other account's host is named in a reason.
-func BlockedStamps(routes []registry.Route, acct, app string) []BlockedHost {
+// (acct, app) in routes. Uniqueness is checked against the FULL list
+// regardless of envName, so a route of another account or another
+// environment still blocks this one's unit — but only this account's
+// hosts are returned, no other account's host is named in a reason, and
+// (spec §3.4: "the same environment and account narrowing applies as for
+// Hosts") only routes bound to envName ("" = every route of the app) are
+// returned, so a write to one environment does not report a blocked route
+// it never reached.
+func BlockedStamps(routes []registry.Route, acct, app, envName string) []BlockedHost {
byUnit := map[string]int{}
for _, rt := range routes {
if Stamped(rt) && StampShape(rt) == nil && DeliverableUnitName(rt.Unit) == nil && BareHostname(rt.Host) {
@@ -686,6 +691,9 @@ func BlockedStamps(routes []registry.Route, acct, app string) []BlockedHost {
if !Stamped(rt) || rt.App != app || release.RouteAccount(rt) != acct {
continue
}
+ if envName != "" && rt.Environment != envName {
+ continue
+ }
switch {
case StampShape(rt) != nil:
out = append(out, BlockedHost{rt.Host, StampShape(rt).Error()})
@@ -728,7 +736,7 @@ func BlockedStamps(routes []registry.Route, acct, app string) []BlockedHost {
// short list read as a complete one.
func AwaitingRestart(ctx context.Context, s release.Store, routes []registry.Route, acct, app, envName string, keys []string) RestartPending {
var out RestartPending
- out.Blocked = BlockedStamps(routes, acct, app)
+ out.Blocked = BlockedStamps(routes, acct, app, envName)
blocked := map[string]bool{}
for _, b := range out.Blocked {
blocked[b.Host] = true