carlos / platform Public

hotfix #281: a held request whose upstream recovery moved is answered as still waking

Astra r2 P2: a request held through startup carries the proxy the edge
chose before layout recovery ran. If recovery moved the host's socket or
address, it now gets the wake-timeout 503 (Retry-After: 5) instead of a
proxy to the old socket, and the retry is routed afresh. Regression test,
mutation-checked. Census: Service.serve re-recorded under
census-ruling-hotfix281 (agent-side, deny-only), activatorStart.Run basis
wording (Opus N5), seal re-pinned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev de660a8221cc7e73ea95ba282488073f0df74a20 parent 494ba40
4 files changed, +71 −6
  • internal/activator/activator.go +16 −0
  • internal/activator/startup_test.go +49 −0
  • internal/provision/plan14_root_reads_test.go +1 −1
  • internal/provision/testdata/plan14-root-reads-hotfix281.tsv +5 −5

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.014s
?   	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	373.100s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	4.209s
ok  	github.com/carlosframework/platform/cmd/moveprobe	94.010s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.419s
ok  	github.com/carlosframework/platform/internal/activator	229.721s
ok  	github.com/carlosframework/platform/internal/agentsock	0.024s
ok  	github.com/carlosframework/platform/internal/alerting	0.005s
ok  	github.com/carlosframework/platform/internal/analytics	0.023s
ok  	github.com/carlosframework/platform/internal/appconfig	2.907s
ok  	github.com/carlosframework/platform/internal/apperr	0.120s
ok  	github.com/carlosframework/platform/internal/applog	0.174s
ok  	github.com/carlosframework/platform/internal/awsinv	0.014s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.013s
ok  	github.com/carlosframework/platform/internal/boxid	0.880s
?   	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.012s
ok  	github.com/carlosframework/platform/internal/caps	0.012s
ok  	github.com/carlosframework/platform/internal/certwarm	0.006s
ok  	github.com/carlosframework/platform/internal/cgroup	0.809s
ok  	github.com/carlosframework/platform/internal/changelog	0.404s
ok  	github.com/carlosframework/platform/internal/clamd	0.057s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	373.372s
?   	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.930s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.014s
ok  	github.com/carlosframework/platform/internal/dbverify	0.005s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.003s
ok  	github.com/carlosframework/platform/internal/docsite	0.010s
ok  	github.com/carlosframework/platform/internal/domains	2.471s
ok  	github.com/carlosframework/platform/internal/doorkey	0.007s
ok  	github.com/carlosframework/platform/internal/economics	0.005s
ok  	github.com/carlosframework/platform/internal/edge	28.485s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.009s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.162s
ok  	github.com/carlosframework/platform/internal/gate	1.696s
ok  	github.com/carlosframework/platform/internal/genselect	0.019s
ok  	github.com/carlosframework/platform/internal/geo	0.020s
ok  	github.com/carlosframework/platform/internal/geoip	0.039s
ok  	github.com/carlosframework/platform/internal/host	103.552s
ok  	github.com/carlosframework/platform/internal/hostactors	0.632s
ok  	github.com/carlosframework/platform/internal/hostbox	3.237s
ok  	github.com/carlosframework/platform/internal/hostclaim	1.273s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.011s
ok  	github.com/carlosframework/platform/internal/hostexec	8.281s
ok  	github.com/carlosframework/platform/internal/hostfence	0.107s
ok  	github.com/carlosframework/platform/internal/hostfiles	11.653s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.040s
ok  	github.com/carlosframework/platform/internal/hostguard	1.112s
ok  	github.com/carlosframework/platform/internal/hostops	2.579s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.002s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.005s
ok  	github.com/carlosframework/platform/internal/hostrelocate	1.005s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.275s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.008s
ok  	github.com/carlosframework/platform/internal/infraview	0.004s
ok  	github.com/carlosframework/platform/internal/instancectl	1.863s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.861s
ok  	github.com/carlosframework/platform/internal/instancefs	0.012s
ok  	github.com/carlosframework/platform/internal/instancekey	0.004s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.011s
ok  	github.com/carlosframework/platform/internal/layout	2.060s
ok  	github.com/carlosframework/platform/internal/lease	9.742s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.018s
ok  	github.com/carlosframework/platform/internal/ledger	0.056s
ok  	github.com/carlosframework/platform/internal/mail	0.469s
ok  	github.com/carlosframework/platform/internal/maintenance	0.146s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.791s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.206s
ok  	github.com/carlosframework/platform/internal/metrics	0.010s
ok  	github.com/carlosframework/platform/internal/moves	24.547s
ok  	github.com/carlosframework/platform/internal/moves/archive	1.511s
ok  	github.com/carlosframework/platform/internal/objstore	0.527s
ok  	github.com/carlosframework/platform/internal/ops	0.248s
ok  	github.com/carlosframework/platform/internal/park	0.516s
ok  	github.com/carlosframework/platform/internal/privatefile	0.003s
ok  	github.com/carlosframework/platform/internal/provision	18.459s
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.925s
ok  	github.com/carlosframework/platform/internal/relay	7.435s
ok  	github.com/carlosframework/platform/internal/release	23.234s
ok  	github.com/carlosframework/platform/internal/rollout	4.907s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.010s
ok  	github.com/carlosframework/platform/internal/rootaws	0.008s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.008s
ok  	github.com/carlosframework/platform/internal/rootstate	0.031s
ok  	github.com/carlosframework/platform/internal/s3compat	0.006s
ok  	github.com/carlosframework/platform/internal/safefile	0.005s
ok  	github.com/carlosframework/platform/internal/schedule	2.666s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	21.494s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	4.232s
ok  	github.com/carlosframework/platform/internal/status	0.098s
ok  	github.com/carlosframework/platform/internal/steering	0.013s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.297s
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.003s
ok  	github.com/carlosframework/platform/internal/updatewords	0.007s
ok  	github.com/carlosframework/platform/internal/usage	0.005s
ok  	github.com/carlosframework/platform/internal/version	0.003s
ok  	github.com/carlosframework/platform/internal/vet	1.694s
=== 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: cached, digest verified
  mc: cached, 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.02s)
    --- 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.01s)
        --- 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.023s
=== RUN   TestT4StorageProbeMinio
2026/10/13 21:01:52 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DE3293EAF238A7, 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 5c2f1780e..c6604f55b 100644
--- a/internal/activator/activator.go
+++ b/internal/activator/activator.go
@@ -4983,12 +4983,28 @@ 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 {
+ 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)
+ }
+ }
}
a.mu.Lock()
diff --git a/internal/activator/startup_test.go b/internal/activator/startup_test.go
index 91563b3ca..17e25129c 100644
--- a/internal/activator/startup_test.go
+++ b/internal/activator/startup_test.go
@@ -75,3 +75,52 @@ func TestStartupShutdownAnswersServiceUnavailable(t *testing.T) {
})
}
}
+
+type startupRoutes map[string]registry.Route
+
+func (s startupRoutes) Get(host string) (*registry.Route, error) {
+ rt, ok := s[host]
+ if !ok {
+ return nil, nil
+ }
+ return &rt, nil
+}
+
+// A request held through startup carries the proxy the edge chose before
+// recovery ran. When recovery moved the host's socket, it is answered as a
+// wake in progress, so the retry is routed afresh, never sent to the old
+// socket.
+func TestStartupHeldRequestRefusesStaleUpstream(t *testing.T) {
+ for _, moved := range []bool{false, true} {
+ a := New(Config{}, &fakeBackend{}, nil)
+ ready := make(chan struct{})
+ a.Startup = ready
+ rt := registry.Route{Host: "shop.example.com", Kind: "instance", Hibernate: true, Socket: "/run/carlos/shop.example.com.sock"}
+ fresh := rt
+ if moved {
+ 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()
+ done := make(chan struct{})
+ go func() {
+ defer close(done)
+ ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+ defer cancel()
+ a.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "http://shop.example.com/", nil).WithContext(ctx), rt, proxy)
+ }()
+ time.Sleep(20 * time.Millisecond)
+ close(ready)
+ <-done
+ a.lifeStop()
+ if moved {
+ if proxied || w.Code != http.StatusServiceUnavailable || w.Header().Get("Retry-After") != "5" {
+ t.Fatalf("moved socket: proxied=%v code=%d", proxied, w.Code)
+ }
+ } else if w.Code == http.StatusServiceUnavailable && w.Body.String() == "instance is waking; retry shortly\n" {
+ t.Fatalf("unchanged socket was refused as stale")
+ }
+ }
+}
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go
index 4cea48bd2..4142e3494 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 = "4b12ba7e94ff1fbc7f2313dbcfb79c5ac1f0455f248f214a8257c08805c1a70f"
+ seal = "264f9a39233de58dbc13bff700e6e5353f8c72b3f266fe00b86d003759531724"
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 a46eb8992..da5893e6b 100644
--- a/internal/provision/testdata/plan14-root-reads-hotfix281.tsv
+++ b/internal/provision/testdata/plan14-root-reads-hotfix281.tsv
@@ -5,14 +5,14 @@ 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 aa4915f25d4d2cb1bc7f896b9808ecfd25fe9c4b5cbb050976e896947a1476aa 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. The same route fields and sources are read. registry:Hibernate@151,registry:Hibernate@788,registry:Host@397,registry:Kind@126,registry:Kind@762,whole:Route@1907,whole:Route@2876,whole:Route@2936,whole:Route@347 44becc931aec04495ff7daae2154fbbf500c4e6d4df65b328a790e33cfa3a0ac
-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. The same route fields and sources are read. registry:Hibernate@151,registry:Hibernate@788,registry:Host@397,registry:Kind@126,registry:Kind@762,whole:Route@1907,whole:Route@2876,whole:Route@2936,whole:Route@347 44becc931aec04495ff7daae2154fbbf500c4e6d4df65b328a790e33cfa3a0ac
+@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 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
@legacy daa120f77654cd1238b494d92b7a4db9ee115f4f56945243b26a7ebe02e584db 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
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 8f772c7c43dec1102a01dbe4a945b8eb0c751f0e2f6efe05dc437e13b21daede internal/host/layoutleg.go activatorStart.Run legacy accepted-legacy accepted-legacy;rev3=census-ruling-hotfix281;basis=Legacy sweep, recovery and key pass run under the agent lifetime in the background. 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 under the agent lifetime in the background. The same route list and callbacks are used; claim boxes retain Start alone. whole:Route@803 e93d0c33f6ac39f9c644b7bfa2463c30a9980dfd0e23a515ba87f356e8899c57
-@coverage internal/activator af5b4506d7b3146462790223d541f6caca55b0606055734733e79accaad9440a
+@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/host ee4e46a4efcbea38cbc81fcdcab7ac4dc5eac2eeecb9ed879aecc4968315cf74