carlos / platform Public

hotfix #281: re-check the route for requests near startup, not only those that waited

Astra r3 P2: the edge can pick its proxy, then recovery moves the socket
and Startup closes before serve looks, so the waited check missed it.
Every hibernating request within two minutes of the first time serve sees
Startup closed (at or after the real close) re-checks Socket/Addr; a
moved upstream gets the wake-timeout 503. Regression test for the
already-closed case and for the window's end; both new tests fail with
the check disabled. Census re-recorded, seal re-pinned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 330dfa5487003c288f0bd376a73d695612b66cf3 parent de660a8
4 files changed, +71 −15
  • internal/activator/activator.go +34 −11
  • internal/activator/startup_test.go +33 −0
  • internal/provision/plan14_root_reads_test.go +1 −1
  • internal/provision/testdata/plan14-root-reads-hotfix281.tsv +3 −3

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.011s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok  	github.com/carlosframework/platform	351.163s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	3.372s
ok  	github.com/carlosframework/platform/cmd/moveprobe	93.263s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.383s
ok  	github.com/carlosframework/platform/internal/activator	227.048s
ok  	github.com/carlosframework/platform/internal/agentsock	0.017s
ok  	github.com/carlosframework/platform/internal/alerting	0.004s
ok  	github.com/carlosframework/platform/internal/analytics	0.020s
ok  	github.com/carlosframework/platform/internal/appconfig	2.374s
ok  	github.com/carlosframework/platform/internal/apperr	0.070s
ok  	github.com/carlosframework/platform/internal/applog	0.138s
ok  	github.com/carlosframework/platform/internal/awsinv	0.004s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.004s
ok  	github.com/carlosframework/platform/internal/boxid	0.653s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.008s
ok  	github.com/carlosframework/platform/internal/caps	0.011s
ok  	github.com/carlosframework/platform/internal/certwarm	0.004s
ok  	github.com/carlosframework/platform/internal/cgroup	0.695s
ok  	github.com/carlosframework/platform/internal/changelog	0.303s
ok  	github.com/carlosframework/platform/internal/clamd	0.055s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	367.136s
?   	github.com/carlosframework/platform/internal/controlname	[no test files]
?   	github.com/carlosframework/platform/internal/controlsock	[no test files]
ok  	github.com/carlosframework/platform/internal/dbdoor	5.577s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.009s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.003s
ok  	github.com/carlosframework/platform/internal/docsite	0.004s
ok  	github.com/carlosframework/platform/internal/domains	2.279s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	26.116s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.008s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.126s
ok  	github.com/carlosframework/platform/internal/gate	1.656s
ok  	github.com/carlosframework/platform/internal/genselect	0.015s
ok  	github.com/carlosframework/platform/internal/geo	0.015s
ok  	github.com/carlosframework/platform/internal/geoip	0.030s
ok  	github.com/carlosframework/platform/internal/host	97.513s
ok  	github.com/carlosframework/platform/internal/hostactors	0.417s
ok  	github.com/carlosframework/platform/internal/hostbox	2.225s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.526s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.012s
ok  	github.com/carlosframework/platform/internal/hostexec	5.043s
ok  	github.com/carlosframework/platform/internal/hostfence	0.032s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.552s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.012s
ok  	github.com/carlosframework/platform/internal/hostguard	1.010s
ok  	github.com/carlosframework/platform/internal/hostops	1.481s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.013s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.013s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.472s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.136s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.015s
ok  	github.com/carlosframework/platform/internal/infraview	0.012s
ok  	github.com/carlosframework/platform/internal/instancectl	1.815s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.737s
ok  	github.com/carlosframework/platform/internal/instancefs	0.016s
ok  	github.com/carlosframework/platform/internal/instancekey	0.014s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.020s
ok  	github.com/carlosframework/platform/internal/layout	1.457s
ok  	github.com/carlosframework/platform/internal/lease	9.450s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.017s
ok  	github.com/carlosframework/platform/internal/ledger	0.012s
ok  	github.com/carlosframework/platform/internal/mail	0.168s
ok  	github.com/carlosframework/platform/internal/maintenance	0.081s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.535s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.066s
ok  	github.com/carlosframework/platform/internal/metrics	0.004s
ok  	github.com/carlosframework/platform/internal/moves	21.140s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.757s
ok  	github.com/carlosframework/platform/internal/objstore	0.171s
ok  	github.com/carlosframework/platform/internal/ops	0.170s
ok  	github.com/carlosframework/platform/internal/park	0.216s
ok  	github.com/carlosframework/platform/internal/privatefile	0.004s
ok  	github.com/carlosframework/platform/internal/provision	13.726s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.007s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	4.046s
ok  	github.com/carlosframework/platform/internal/relay	7.244s
ok  	github.com/carlosframework/platform/internal/release	20.114s
ok  	github.com/carlosframework/platform/internal/rollout	3.801s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.003s
ok  	github.com/carlosframework/platform/internal/rootaws	0.007s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.008s
ok  	github.com/carlosframework/platform/internal/rootstate	0.025s
ok  	github.com/carlosframework/platform/internal/s3compat	0.006s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.318s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	9.989s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.810s
ok  	github.com/carlosframework/platform/internal/status	0.040s
ok  	github.com/carlosframework/platform/internal/steering	0.009s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.204s
ok  	github.com/carlosframework/platform/internal/svcrole	0.002s
ok  	github.com/carlosframework/platform/internal/telemetry	0.006s
ok  	github.com/carlosframework/platform/internal/termfence	0.004s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.004s
ok  	github.com/carlosframework/platform/internal/unixdial	0.002s
ok  	github.com/carlosframework/platform/internal/updatewords	0.004s
ok  	github.com/carlosframework/platform/internal/usage	0.006s
ok  	github.com/carlosframework/platform/internal/version	0.002s
ok  	github.com/carlosframework/platform/internal/vet	1.607s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
  minio: downloading https://github.com/minio/minio/releases/download/RELEASE.2025-09-07T16-13-09Z/minio.linux-arm64.RELEASE.2025-09-07T16-13-09Z
  minio: downloaded, digest verified
  mc: downloading https://github.com/minio/mc/releases/download/RELEASE.2025-08-13T08-35-41Z/mc.linux-arm64.RELEASE.2025-08-13T08-35-41Z
  mc: downloaded, digest verified
  minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
  buckets and scoped user ready
=== RUN   TestDeliveredContractMinio
=== RUN   TestDeliveredContractMinio/put
=== RUN   TestDeliveredContractMinio/get_round-trips_the_body
=== RUN   TestDeliveredContractMinio/list_shows_the_key
=== RUN   TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN   TestDeliveredContractMinio/delete_removes_it
=== RUN   TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.03s)
    --- PASS: TestDeliveredContractMinio/put (0.00s)
    --- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
    --- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
    --- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.02s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
    --- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
    --- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/objstore	0.037s
=== RUN   TestT4StorageProbeMinio
2026/10/13 21:15:19 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DE334FD04EA800, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.01s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.022s
minio contract: PASS
diff --git a/internal/activator/activator.go b/internal/activator/activator.go
index c6604f55b..a3d9866a0 100644
--- a/internal/activator/activator.go
+++ b/internal/activator/activator.go
@@ -67,6 +67,7 @@ import (
"strconv"
"strings"
"sync"
+ "sync/atomic"
"time"
"github.com/carlosframework/platform/internal/cgroup"
@@ -1017,6 +1018,10 @@ var errStartupStopped = errors.New("activator stopped during startup")
type Service struct {
// Startup gates admission while the agent recovers installations. Set before publication.
Startup <-chan struct{}
+ // startupSeenAt is when serve first found Startup closed (unix nanos,
+ // 0 until then): requests in the window after it may carry a proxy the
+ // edge chose before recovery moved their upstream.
+ startupSeenAt atomic.Int64
cfg Config
backend Backend
@@ -1276,6 +1281,27 @@ func New(cfg Config, backend Backend, due DueStore) *Service {
return a
}
+// startupFreshWindow bounds how long after startup completes a request's
+// route is re-checked against the registry (serve). A proxy the edge chose
+// before Startup closed reaches serve within milliseconds; the window is
+// generous, and costs one registry read per hibernating request inside it.
+const startupFreshWindow = 2 * time.Minute
+
+// nearStartup reports whether this request is within startupFreshWindow of
+// the first time serve found Startup closed. The first observation is at or
+// after the real close, so a request racing the close is always inside it.
+func (a *Service) nearStartup() bool {
+ now := time.Now().UnixNano()
+ at := a.startupSeenAt.Load()
+ if at == 0 {
+ if a.startupSeenAt.CompareAndSwap(0, now) {
+ return true
+ }
+ at = a.startupSeenAt.Load()
+ }
+ return now-at < int64(startupFreshWindow)
+}
+
func (a *Service) awaitStartup(ctx context.Context, deadline <-chan time.Time) error {
if a.Startup == nil {
return nil
@@ -4983,23 +5009,20 @@ func (a *Service) ServeScheduled(w http.ResponseWriter, r *http.Request, rt regi
func (a *Service) serve(w http.ResponseWriter, r *http.Request, rt registry.Route, proxy http.Handler, stamp bool) bool {
if rt.Kind == "instance" && rt.Hibernate && a.Startup != nil {
- waited := false
- select {
- case <-a.Startup:
- default:
- waited = true
- }
deadline := time.NewTimer(wakeWaitBound)
err := a.awaitStartup(r.Context(), deadline.C)
deadline.Stop()
if err != nil {
return a.serveWakeError(w, r, rt, err)
}
- // The edge chose rt and its proxy before startup recovery ran. If
- // recovery moved this host's upstream (a layout journal finished),
- // that proxy points at the old socket: answer as a wake still in
- // progress, and the retry is routed afresh.
- if waited && a.Routes != nil {
+ // The edge may have chosen rt and its proxy before startup recovery
+ // ran, whether this request waited here or arrived just as Startup
+ // closed. If recovery moved this host's upstream (a layout journal
+ // finished), that proxy points at the old socket: answer as a wake
+ // still in progress, and the retry is routed afresh. Only requests
+ // near startup are checked; after the window the edge's own
+ // selection is current.
+ if a.nearStartup() && a.Routes != nil {
if fresh, ferr := a.Routes.Get(rt.Host); ferr == nil && fresh != nil &&
(fresh.Socket != rt.Socket || fresh.Addr != rt.Addr) {
return a.serveWakeError(w, r, rt, errWakeTimeout)
diff --git a/internal/activator/startup_test.go b/internal/activator/startup_test.go
index 17e25129c..8a36e7674 100644
--- a/internal/activator/startup_test.go
+++ b/internal/activator/startup_test.go
@@ -124,3 +124,36 @@ func TestStartupHeldRequestRefusesStaleUpstream(t *testing.T) {
}
}
}
+
+// The race the waiting check misses: the edge chose its proxy, recovery
+// moved the socket and Startup closed before serve looked. A request near
+// startup is still checked; one long after it is not.
+func TestStartupClosedNearStartupRefusesStaleUpstream(t *testing.T) {
+ for _, late := range []bool{false, true} {
+ a := New(Config{}, &fakeBackend{}, nil)
+ ready := make(chan struct{})
+ close(ready)
+ a.Startup = ready
+ if late {
+ a.startupSeenAt.Store(time.Now().Add(-2 * startupFreshWindow).UnixNano())
+ }
+ rt := registry.Route{Host: "shop.example.com", Kind: "instance", Hibernate: true, Socket: "/run/carlos/shop.example.com.sock"}
+ fresh := rt
+ fresh.Socket = "/run/carlos/instances/shop.example.com/app.sock"
+ a.Routes = startupRoutes{rt.Host: fresh}
+ proxied := false
+ proxy := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { proxied = true })
+ w := httptest.NewRecorder()
+ ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+ a.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "http://shop.example.com/", nil).WithContext(ctx), rt, proxy)
+ cancel()
+ a.lifeStop()
+ stale := !proxied && w.Code == http.StatusServiceUnavailable && w.Body.String() == "instance is waking; retry shortly\n"
+ if !late && !stale {
+ t.Fatalf("near startup: moved socket not refused: proxied=%v code=%d", proxied, w.Code)
+ }
+ if late && stale {
+ t.Fatal("long after startup: the route was re-checked")
+ }
+ }
+}
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go
index 4142e3494..0033203f3 100644
--- a/internal/provision/plan14_root_reads_test.go
+++ b/internal/provision/plan14_root_reads_test.go
@@ -657,7 +657,7 @@ func TestPlan14RootReadCensus(t *testing.T) {
if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-hotfix281.tsv"), &inv, true); err != nil {
t.Fatal(err)
}
- seal = "264f9a39233de58dbc13bff700e6e5353f8c72b3f266fe00b86d003759531724"
+ seal = "6da50872e15f7f396d23e5b1b2e044a73cd9e5c07d85c8e22252b0a1a5304bbd"
errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...)
errs = append(errs, checkRootReadDeclarations(got, inv)...)
if len(errs) > 0 {
diff --git a/internal/provision/testdata/plan14-root-reads-hotfix281.tsv b/internal/provision/testdata/plan14-root-reads-hotfix281.tsv
index da5893e6b..42b73f8c2 100644
--- a/internal/provision/testdata/plan14-root-reads-hotfix281.tsv
+++ b/internal/provision/testdata/plan14-root-reads-hotfix281.tsv
@@ -5,8 +5,8 @@ internal/activator/activator.go Service.BeginSidecarRequest legacy accepted-lega
internal/activator/activator.go Service.WakeWorker legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=The same host and route reads follow a bounded startup wait; no new source or authority. registry:Host@1242,registry:Host@1652,registry:Host@834,registry:Host@864,whole:Route@1225 bda1e33466a92d540eeb724284aa431550a323e7dbb432cc7b8872498929bec9
@legacy b3e3b36908d945c5c486c57073c911b113966f1cc2fb93347e0057059b4750be internal/activator/activator.go Service.begin legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=The same host and route reads follow a bounded startup wait; no new source or authority. registry:Host@1197,registry:Host@1276,registry:Host@326,registry:Host@405,whole:Route@1206,whole:Route@1213 642ece8b785a5654dc04d4461995b77dffa2073bcad44897581cfa0a390b6016
internal/activator/activator.go Service.begin legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=The same host and route reads follow a bounded startup wait; no new source or authority. registry:Host@1197,registry:Host@1276,registry:Host@326,registry:Host@405,whole:Route@1206,whole:Route@1213 642ece8b785a5654dc04d4461995b77dffa2073bcad44897581cfa0a390b6016
-@legacy 7ee0c5cbf9ee48c401057756d3c6d37b8acabc1ff5570d0bce917ece9e616002 internal/activator/activator.go Service.serve legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=Startup repeats the existing Kind and Hibernate checks; error logging moved to serveWakeError. A request held through startup re-reads its own route's Socket and Addr and can only refuse (wake-timeout 503) when recovery moved them; agent-side, deny-only, no root decision. registry:Addr@837,registry:Addr@851,registry:Hibernate@1353,registry:Hibernate@151,registry:Host@762,registry:Host@962,registry:Kind@126,registry:Kind@1327,registry:Socket@808,registry:Socket@824,whole:Route@2472,whole:Route@3441,whole:Route@3501,whole:Route@428,whole:Route@787,whole:Route@896 dbb435e9058b510bc7348511b209f1bbcf3b54f6fa9df3506ad4783f5ff070e4
-internal/activator/activator.go Service.serve legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=Startup repeats the existing Kind and Hibernate checks; error logging moved to serveWakeError. A request held through startup re-reads its own route's Socket and Addr and can only refuse (wake-timeout 503) when recovery moved them; agent-side, deny-only, no root decision. registry:Addr@837,registry:Addr@851,registry:Hibernate@1353,registry:Hibernate@151,registry:Host@762,registry:Host@962,registry:Kind@126,registry:Kind@1327,registry:Socket@808,registry:Socket@824,whole:Route@2472,whole:Route@3441,whole:Route@3501,whole:Route@428,whole:Route@787,whole:Route@896 dbb435e9058b510bc7348511b209f1bbcf3b54f6fa9df3506ad4783f5ff070e4
+@legacy cbe493713bb70584550a72e54677628ff5ca7af7c4cd1e7a7e4b0a98302de1eb internal/activator/activator.go Service.serve legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=Startup repeats the existing Kind and Hibernate checks; error logging moved to serveWakeError. A request held through startup re-reads its own route's Socket and Addr and can only refuse (wake-timeout 503) when recovery moved them; agent-side, deny-only, no root decision. registry:Addr@952,registry:Addr@966,registry:Hibernate@1468,registry:Hibernate@151,registry:Host@1077,registry:Host@877,registry:Kind@126,registry:Kind@1442,registry:Socket@923,registry:Socket@939,whole:Route@1011,whole:Route@2587,whole:Route@347,whole:Route@3556,whole:Route@3616,whole:Route@902 75fc203da1dfc4c88db100c634a370777928a441480bfc9547cb0fb19a21c9fe
+internal/activator/activator.go Service.serve legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=Startup repeats the existing Kind and Hibernate checks; error logging moved to serveWakeError. A request held through startup re-reads its own route's Socket and Addr and can only refuse (wake-timeout 503) when recovery moved them; agent-side, deny-only, no root decision. registry:Addr@952,registry:Addr@966,registry:Hibernate@1468,registry:Hibernate@151,registry:Host@1077,registry:Host@877,registry:Kind@126,registry:Kind@1442,registry:Socket@923,registry:Socket@939,whole:Route@1011,whole:Route@2587,whole:Route@347,whole:Route@3556,whole:Route@3616,whole:Route@902 75fc203da1dfc4c88db100c634a370777928a441480bfc9547cb0fb19a21c9fe
@legacy bf4ca1db43d8023db08187eb3afdcfbd4599b3f6966c81274b60e2752b6c5fac internal/activator/activator.go Service.serveWakeError legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=Extracted unchanged error mapping and Host log read from Service.serve; no new source or authority. registry:Host@1364 e4ad730f8b463cf112c59bd061c7384ff4afe626d2bedd7f90ccd3df791333ba
internal/activator/activator.go Service.serveWakeError legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=Extracted unchanged error mapping and Host log read from Service.serve; no new source or authority. registry:Host@1364 e4ad730f8b463cf112c59bd061c7384ff4afe626d2bedd7f90ccd3df791333ba
internal/host/agent.go runAgent authority B,C,D,F rev3=census-ruling-hotfix281;basis=Only activator startup and dependent duties run asynchronously; shutdown cancels their child context before the join, preserving the agent lifetime through child and rollout drain. Registry, route, data-directory and backend inputs are unchanged; B,C,D,F authority is retained. file:Exec@13674,file:Open@731,registry:App@10065,whole:Route@10057,whole:Route@21396 5d8ca6215a4f7eddc85fd05a6696494d22922e8c23eab4d4eb91605377d2277c
@@ -14,5 +14,5 @@ internal/host/agent.go runAgent authority B,C,D,F rev3=census-ruling-hotfix281;b
internal/host/instancekeys.go backfillInstanceKeys legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=Cancellation stops the loop early; route reads, per-host preparation and key sources are unchanged. registry:Host@553,registry:Host@701,registry:Host@867,whole:Route@251,whole:Route@320,whole:Route@399 b4d7ba9a5dc32cb04c214adc69184d91bc65d2487c661150fed899f1deba676d
@legacy 8884dca0e95b3cdd016d6c150db8f74a6ad64cc8aa4f0d2fdbfc935fa4947ed2 internal/host/layoutleg.go activatorStart.Run legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=Legacy sweep, recovery and key pass run in the background under a startup context that is a child of the agent lifetime. The same route list and callbacks are used; claim boxes retain Start alone. whole:Route@803 e93d0c33f6ac39f9c644b7bfa2463c30a9980dfd0e23a515ba87f356e8899c57
internal/host/layoutleg.go activatorStart.Run legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=Legacy sweep, recovery and key pass run in the background under a startup context that is a child of the agent lifetime. The same route list and callbacks are used; claim boxes retain Start alone. whole:Route@803 e93d0c33f6ac39f9c644b7bfa2463c30a9980dfd0e23a515ba87f356e8899c57
-@coverage internal/activator 7a8d6707d50423081a86913d5f173064df2f6af884698d19e653ee985409fe75
+@coverage internal/activator d2f7f2c588ae1acd8166c366638f254631c3806dae4b6f523c27b15ddeb3a418
@coverage internal/host ee4e46a4efcbea38cbc81fcdcab7ac4dc5eac2eeecb9ed879aecc4968315cf74