WIP files seed checkpoint 12 (not for merge)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 files changed,
+193
−7
internal/host/claimservinghandover_linux_test.go+47 −0internal/host/claimservingpark_transport_linux_test.go+9 −0internal/host/claimservingparkroot_linux.go+7 −1internal/host/claimservingparkroot_linux_test.go+58 −0internal/host/claimservingpeer_rootrunner_linux_test.go+69 −3internal/provision/testdata/plan14-root-reads-seed.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 335.071s
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 3.031s
ok github.com/carlosframework/platform/cmd/moveprobe 93.210s
ok github.com/carlosframework/platform/cmd/zddprobe 0.709s
ok github.com/carlosframework/platform/internal/activator 235.384s
ok github.com/carlosframework/platform/internal/agentsock 0.023s
ok github.com/carlosframework/platform/internal/alerting 0.005s
ok github.com/carlosframework/platform/internal/analytics 0.021s
ok github.com/carlosframework/platform/internal/appconfig 2.298s
ok github.com/carlosframework/platform/internal/apperr 0.077s
ok github.com/carlosframework/platform/internal/applog 0.132s
ok github.com/carlosframework/platform/internal/awsinv 0.008s
ok github.com/carlosframework/platform/internal/boxchoice 0.009s
ok github.com/carlosframework/platform/internal/boxid 0.689s
? 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.012s
ok github.com/carlosframework/platform/internal/certwarm 0.006s
ok github.com/carlosframework/platform/internal/cgroup 0.698s
ok github.com/carlosframework/platform/internal/changelog 0.290s
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 362.628s
? 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.818s
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.006s
ok github.com/carlosframework/platform/internal/domains 2.105s
ok github.com/carlosframework/platform/internal/doorkey 0.008s
ok github.com/carlosframework/platform/internal/economics 0.005s
ok github.com/carlosframework/platform/internal/edge 25.978s
? 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.127s
ok github.com/carlosframework/platform/internal/gate 1.656s
ok github.com/carlosframework/platform/internal/genselect 0.019s
ok github.com/carlosframework/platform/internal/geo 0.018s
ok github.com/carlosframework/platform/internal/geoip 0.032s
ok github.com/carlosframework/platform/internal/host 148.037s
ok github.com/carlosframework/platform/internal/hostactors 0.275s
ok github.com/carlosframework/platform/internal/hostbox 2.253s
ok github.com/carlosframework/platform/internal/hostclaim 0.494s
ok github.com/carlosframework/platform/internal/hostevidence 0.006s
ok github.com/carlosframework/platform/internal/hostexec 4.785s
ok github.com/carlosframework/platform/internal/hostfence 0.035s
ok github.com/carlosframework/platform/internal/hostfiles 10.498s
ok github.com/carlosframework/platform/internal/hostfloor 0.012s
ok github.com/carlosframework/platform/internal/hostguard 1.007s
ok github.com/carlosframework/platform/internal/hostops 1.431s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.005s
ok github.com/carlosframework/platform/internal/hostquarantine 0.005s
ok github.com/carlosframework/platform/internal/hostrelocate 0.455s
ok github.com/carlosframework/platform/internal/hostresolve 0.136s
ok github.com/carlosframework/platform/internal/hostwrite 0.005s
ok github.com/carlosframework/platform/internal/infraview 0.005s
ok github.com/carlosframework/platform/internal/instancectl 1.813s
ok github.com/carlosframework/platform/internal/instanceexec 2.689s
ok github.com/carlosframework/platform/internal/instancefs 0.005s
ok github.com/carlosframework/platform/internal/instancekey 0.007s
ok github.com/carlosframework/platform/internal/instanceupdates 0.009s
ok github.com/carlosframework/platform/internal/layout 1.417s
ok github.com/carlosframework/platform/internal/lease 9.452s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.006s
ok github.com/carlosframework/platform/internal/ledger 0.018s
ok github.com/carlosframework/platform/internal/mail 0.158s
ok github.com/carlosframework/platform/internal/maintenance 0.085s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.502s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.081s
ok github.com/carlosframework/platform/internal/metrics 0.006s
ok github.com/carlosframework/platform/internal/moves 20.910s
ok github.com/carlosframework/platform/internal/moves/archive 0.736s
ok github.com/carlosframework/platform/internal/objstore 0.169s
ok github.com/carlosframework/platform/internal/ops 0.158s
ok github.com/carlosframework/platform/internal/park 0.196s
ok github.com/carlosframework/platform/internal/privatefile 0.005s
ok github.com/carlosframework/platform/internal/provision 11.289s
ok github.com/carlosframework/platform/internal/publicrelease 0.009s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 4.035s
ok github.com/carlosframework/platform/internal/relay 7.219s
ok github.com/carlosframework/platform/internal/release 19.342s
ok github.com/carlosframework/platform/internal/rollout 3.619s
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.005s
ok github.com/carlosframework/platform/internal/safefile 0.004s
ok github.com/carlosframework/platform/internal/schedule 2.282s
ok github.com/carlosframework/platform/internal/sim/hostlife 8.726s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 3.612s
ok github.com/carlosframework/platform/internal/status 0.041s
ok github.com/carlosframework/platform/internal/steering 0.006s
ok github.com/carlosframework/platform/internal/stopevidence 0.285s
ok github.com/carlosframework/platform/internal/svcrole 0.002s
ok github.com/carlosframework/platform/internal/telemetry 0.004s
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.004s
ok github.com/carlosframework/platform/internal/version 0.001s
ok github.com/carlosframework/platform/internal/vet 1.543s
=== 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.022s
=== RUN TestT4StorageProbeMinio
2026/10/14 18:55:41 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DE7A4598E3803E, 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.021s
minio contract: PASS
diff --git a/internal/host/claimservinghandover_linux_test.go b/internal/host/claimservinghandover_linux_test.go| index a7ab85c95..5e3cf4fa3 100644 |
| --- a/internal/host/claimservinghandover_linux_test.go |
| +++ b/internal/host/claimservinghandover_linux_test.go |
| @@ -89,6 +89,53 @@ func servingHandoverRoot(t *testing.T, f *servingRootFixture) ClaimRootHTTP { |
| func TestServingParkColdHandoverPreservesState(t *testing.T) { runServingColdHandoverBoundary(t, nil) } |
| +func TestServingHandoverThenPrelaunchParkReplay(t *testing.T) { |
| + f := newServingRootFixture(t, true) |
| + root := servingHandoverRoot(t, f) |
| + socket := servingRootSocket(t, root) |
| + hook := coldHandoverWith(handoverRecoveryIdentity(t, f.actor), socket, func(context.Context, hostclaim.Observed) (hostguard.FenceReceipt, error) { |
| + return hostguard.FenceReceipt{Target: f.receipt.Target}, nil |
| + }) |
| + admitted := f.f.read() |
| + for i := 0; i < 2; i++ { |
| + if err := hook(t.Context(), f.predecessor, admitted); err != nil { |
| + t.Fatal("handover", err) |
| + } |
| + } |
| + installed, err := f.f.guard.Capture(f.current.Host) |
| + if err != nil || installed != (hostguard.Captured{Incarnation: f.current.Incarnation, Stamp: f.current.Stamp}) { |
| + t.Fatalf("installation: %+v %v", installed, err) |
| + } |
| + target := recoveryTuple(f.f.read(), claimStopLocal{Actor: f.actor, BootID: "boot", ControllerID: "controller"}) |
| + if err := f.f.journal.Prelaunch(t.Context(), hostbox.LaunchInventory{Target: target, ControllerPID: 77, ProcessStart: "edge-start", NoState: true, Groups: []hostbox.LaunchGroup{{RelativePath: "carlos/next", Device: 1, Inode: 99}}}); err != nil { |
| + t.Fatal(err) |
| + } |
| + client := hostbox.ServingParkClient{Call: rootJournalClient(socket)} |
| + a, err := client.Park(t.Context(), f.current, f.prior) |
| + if err != nil { |
| + t.Fatal("park after prelaunch", err) |
| + } |
| + f.tags(f.current.Stamp) |
| + if err := client.Complete(t.Context(), a.ID, f.current); err != nil { |
| + t.Fatal(err) |
| + } |
| + before, err := f.p.ledger.read(a.ID) |
| + if err != nil || !before.Complete { |
| + t.Fatal("completion", err) |
| + } |
| + again, err := client.Park(t.Context(), f.current, f.prior) |
| + if err != nil || again != a { |
| + t.Fatalf("replay after handover and prelaunch: %+v %v", again, err) |
| + } |
| + after, err := f.p.ledger.read(a.ID) |
| + if err != nil || !reflect.DeepEqual(before, after) { |
| + t.Fatal("replay changed record", err) |
| + } |
| + if err := f.p.ledger.fs.CheckParked(&after); err != nil { |
| + t.Fatal(err) |
| + } |
| +} |
| + |
| func runServingColdHandoverBoundary(t *testing.T, mount func(*testing.T, string), revisions ...uint64) { |
| t.Helper() |
| f := newServingRootFixture(t, true) |
diff --git a/internal/host/claimservingpark_transport_linux_test.go b/internal/host/claimservingpark_transport_linux_test.go| index d36c268b6..c5194ca09 100644 |
| --- a/internal/host/claimservingpark_transport_linux_test.go |
| +++ b/internal/host/claimservingpark_transport_linux_test.go |
| @@ -106,6 +106,15 @@ func TestServingParkSocketRetiresAndCompletes(t *testing.T) { |
| if err != nil || !r.Complete { |
| t.Fatalf("completion: %+v %v", r, err) |
| } |
| + if !legacy { |
| + again, err := client.Park(t.Context(), f.current, f.prior) |
| + if err != nil || again != a { |
| + t.Fatalf("fresh request after completion: %+v %v", again, err) |
| + } |
| + if exact.ID == parkRequest.ID { |
| + t.Fatal("fresh request reused cached transport reply") |
| + } |
| + } |
| before, err := f.disk.Read("retirement-" + a.ID + ".json") |
| if err != nil { |
| t.Fatal(err) |
diff --git a/internal/host/claimservingparkroot_linux.go b/internal/host/claimservingparkroot_linux.go| index 97a3d2e4e..1ea58b509 100644 |
| --- a/internal/host/claimservingparkroot_linux.go |
| +++ b/internal/host/claimservingparkroot_linux.go |
| @@ -181,7 +181,13 @@ func (p *servingParkRoot) parkServingDatabase(ctx context.Context, conn net.Conn |
| return err |
| } |
| if requestedPrior != (servingArtifactIdentity{}) { |
| - prior, e := servingPredecessor(o.Claim, current, tags, requestedPrior.Generation) |
| + stamp := tags |
| + if tags == current.Stamp { |
| + // Retagging has consumed the old pair. An original request |
| + // still selects the predecessor saved before that change. |
| + stamp = active.TagsFrom |
| + } |
| + prior, e := servingPredecessor(o.Claim, current, stamp, requestedPrior.Generation) |
| if e != nil || prior != requestedPrior { |
| return hostbox.ErrChanged |
| } |
diff --git a/internal/host/claimservingparkroot_linux_test.go b/internal/host/claimservingparkroot_linux_test.go| index 479e845e8..4b0b588e7 100644 |
| --- a/internal/host/claimservingparkroot_linux_test.go |
| +++ b/internal/host/claimservingparkroot_linux_test.go |
| @@ -68,6 +68,7 @@ type servingRootFixture struct { |
| actor hostbox.Actor |
| current, prior servingArtifactIdentity |
| admitted hostclaim.Observed |
| + predecessor hostclaim.Observed |
| receipt hostguard.FenceReceipt |
| db string |
| trace []string |
| @@ -118,6 +119,7 @@ func newServingRootFixture(t *testing.T, launched bool) *servingRootFixture { |
| } |
| } |
| o := f.read() |
| + fx.predecessor = o |
| fx.admitted, err = hostclaim.Update(t.Context(), f.claims, o, hostclaim.AdmitWriter(hostclaim.Admission{ObservedSession: o.Claim.Holder.Session, ObservedGeneration: o.Claim.Holder.Generation, Box: "box-1", Region: "ie", PredecessorRegion: "ie", Mode: "cold", NewGeneration: "G2"})) |
| if err != nil { |
| t.Fatal(err) |
| @@ -237,6 +239,58 @@ func TestServingParkRootGenerationAuthority(t *testing.T) { |
| }) |
| } |
| } |
| + |
| +func TestServingParkRootReplaysOriginalSelectorAfterRetag(t *testing.T) { |
| + for _, complete := range []bool{false, true} { |
| + t.Run(fmt.Sprintf("complete=%v", complete), func(t *testing.T) { |
| + f := newServingRootFixture(t, true) |
| + a, err := f.park() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + f.tags(f.current.Stamp) |
| + if complete { |
| + if err := f.p.completeServingDatabasePark(t.Context(), f.conn, a.ID, f.current); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + before, err := f.p.ledger.read(a.ID) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + f.f.now = f.f.now.Add(time.Hour) |
| + again, err := f.park() |
| + if err != nil || again != a { |
| + t.Fatalf("original selector after retag: %+v %v", again, err) |
| + } |
| + for _, field := range []string{"generation", "stamp", "session", "box", "incarnation"} { |
| + bad := f.prior |
| + switch field { |
| + case "generation": |
| + bad.Generation = "foreign" |
| + case "stamp": |
| + bad.Stamp = f.current.Stamp |
| + case "session": |
| + bad.Session = f.current.Session |
| + case "box": |
| + bad.Box = "foreign" |
| + case "incarnation": |
| + bad.Incarnation = "foreign" |
| + } |
| + if _, err := f.p.parkServingDatabase(t.Context(), f.conn, f.current, bad); err == nil { |
| + t.Fatalf("accepted changed %s", field) |
| + } |
| + } |
| + after, err := f.p.ledger.read(a.ID) |
| + if err != nil || !reflect.DeepEqual(before, after) { |
| + t.Fatalf("replay changed retirement: %+v %v", after, err) |
| + } |
| + if err := f.p.ledger.fs.CheckParked(&after); err != nil { |
| + t.Fatal(err) |
| + } |
| + }) |
| + } |
| +} |
| func TestServingParkRootRefusesBeforeEffects(t *testing.T) { |
| for _, attack := range []string{"peer", "peer-root", "census", "roster", "quarantine", "enrollment", "revision", "claim", "prior", "tags", "half-tags", "source", "inventory-revision", "receipt", "pending", "root-quarantine", "root-pending", "barrier", "canceled"} { |
| t.Run(attack, func(t *testing.T) { |
| @@ -502,6 +556,10 @@ func TestServingParkRootContinuesOriginalRetirement(t *testing.T) { |
| if err != nil || again != continued { |
| t.Fatalf("replay: %+v %v", again, err) |
| } |
| + again, err = f.p.parkServingDatabase(t.Context(), f.conn, f.current, requested) |
| + if err != nil || again != continued { |
| + t.Fatalf("original continuation selector: %+v %v", again, err) |
| + } |
| }) |
| } |
| } |
diff --git a/internal/host/claimservingpeer_rootrunner_linux_test.go b/internal/host/claimservingpeer_rootrunner_linux_test.go| index 85033015e..c2e348617 100644 |
| --- a/internal/host/claimservingpeer_rootrunner_linux_test.go |
| +++ b/internal/host/claimservingpeer_rootrunner_linux_test.go |
| @@ -13,6 +13,7 @@ import ( |
| "os/exec" |
| "os/user" |
| "path/filepath" |
| + "reflect" |
| "strconv" |
| "strings" |
| "sync/atomic" |
| @@ -34,6 +35,9 @@ type servingPeerCommand struct { |
| Current, Prior servingArtifactIdentity |
| Target hostguard.FenceTarget |
| ID string |
| + Previous hostclaim.Observed |
| + Admitted hostclaim.Observed |
| + Actor hostbox.Actor |
| } |
| type servingPeerReply struct { |
| @@ -97,6 +101,11 @@ func TestRootServingParkPeerPayload(t *testing.T) { |
| var reply servingPeerReply |
| var callErr error |
| switch q.Action { |
| + case "handover": |
| + hook := coldHandoverWith(handoverRecoveryIdentity(t, q.Actor), socket, func(context.Context, hostclaim.Observed) (hostguard.FenceReceipt, error) { |
| + return hostguard.FenceReceipt{Target: q.Target}, nil |
| + }) |
| + callErr = hook(t.Context(), q.Previous, q.Admitted) |
| case "park": |
| reply.Authorization, callErr = client.Park(t.Context(), q.Current, q.Prior) |
| case "complete": |
| @@ -175,6 +184,15 @@ func TestRootServingParkPeerPayload(t *testing.T) { |
| // Claim, installation, predecessor stop/start and roster/census data are fixtures; |
| // peer authentication, prelaunch containment, park authority and IO are real. |
| func TestRootServingParkAuthenticatedPeer(t *testing.T) { |
| + runServingParkAuthenticatedPeer(t, false) |
| +} |
| + |
| +func TestRootServingParkAuthenticatedHandover(t *testing.T) { |
| + runServingParkAuthenticatedPeer(t, true) |
| +} |
| + |
| +func runServingParkAuthenticatedPeer(t *testing.T, handover bool) { |
| + t.Helper() |
| servingRootRunner(t) |
| pid1, err := os.ReadFile("/proc/1/comm") |
| if err != nil || strings.TrimSpace(string(pid1)) != "systemd" { |
| @@ -240,9 +258,20 @@ func TestRootServingParkAuthenticatedPeer(t *testing.T) { |
| } |
| f := newServingRootFixture(t, true) |
| servingRunnerMount(t, f.f.guard.DataDir) |
| - // Refresh the state inventory after the bind mount. This remains a fixture |
| - // installation; it does not exercise the production installation door. |
| - f.install(f.admitted) |
| + for name, body := range map[string]string{"files/upload": "tenant upload", "env": "tenant configuration"} { |
| + path := filepath.Join(filepath.Dir(f.db), name) |
| + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := os.WriteFile(path, []byte(body), 0600); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + if handover { |
| + servingHandoverRoot(t, f) |
| + } else { |
| + f.install(f.admitted) |
| + } |
| state, err := rootstate.Open(filepath.Join(root, "parks"), true, false) |
| if err != nil { |
| t.Fatal(err) |
| @@ -368,6 +397,7 @@ func TestRootServingParkAuthenticatedPeer(t *testing.T) { |
| } |
| command := func(q servingPeerCommand, refused bool) servingPeerReply { |
| t.Helper() |
| + t.Logf("authenticated peer request: action=%s expect-refusal=%v", q.Action, refused) |
| if err := encode.Encode(q); err != nil { |
| t.Fatal(err) |
| } |
| @@ -406,6 +436,36 @@ func TestRootServingParkAuthenticatedPeer(t *testing.T) { |
| t.Fatalf("refusal wrote park authority: %s", name) |
| } |
| } |
| + if handover { |
| + transfer := servingPeerCommand{Action: "handover", Previous: f.predecessor, Admitted: f.f.read(), Actor: f.actor, Target: f.receipt.Target} |
| + captured := hostguard.Captured{Incarnation: f.prior.Incarnation, Stamp: f.prior.Stamp} |
| + oldInventory, _, err := f.p.root.Journals.Inventory.Snapshot(t.Context(), f.current.Host, captured) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + admitted.Store(false) |
| + command(transfer, true) |
| + admitted.Store(true) |
| + bad := transfer |
| + bad.Target.FenceID = "foreign-receipt" |
| + command(bad, true) |
| + command(transfer, false) |
| + command(transfer, false) |
| + installed, err := f.f.guard.Capture(f.current.Host) |
| + if err != nil || installed != (hostguard.Captured{Incarnation: f.current.Incarnation, Stamp: f.current.Stamp}) { |
| + t.Fatalf("authenticated handover installation: %+v %v", installed, err) |
| + } |
| + retained, _, err := f.p.root.Journals.Inventory.Snapshot(t.Context(), f.current.Host, captured) |
| + if err != nil || !reflect.DeepEqual(retained, oldInventory) { |
| + t.Fatal("handover changed predecessor inventory", err) |
| + } |
| + for suffix, want := range map[string]string{"": "served bytes", ".gen": "G", "-wal": "WAL", "-shm": "SHM"} { |
| + body, err := os.ReadFile(f.db + suffix) |
| + if err != nil || string(body) != want { |
| + t.Fatal("handover changed source", suffix, err) |
| + } |
| + } |
| + } |
| target := recoveryTuple(f.f.read(), claimStopLocal{Actor: f.actor}) |
| pre := command(servingPeerCommand{Action: "prelaunch", Target: target}, false) |
| actual, err := f.f.journal.Read(t.Context(), pre.Launch.Target) |
| @@ -438,6 +498,12 @@ func TestRootServingParkAuthenticatedPeer(t *testing.T) { |
| if replay := command(q, false).Authorization; replay != parked { |
| t.Fatal("authenticated replay changed retirement") |
| } |
| + for name, want := range map[string]string{"files/upload": "tenant upload", "env": "tenant configuration"} { |
| + body, err := os.ReadFile(filepath.Join(filepath.Dir(f.db), name)) |
| + if err != nil || string(body) != want { |
| + t.Fatal("retirement changed preserved state", name, err) |
| + } |
| + } |
| } |
| // Transfer the serving installation on the nested mount without moving its |
diff --git a/internal/provision/testdata/plan14-root-reads-seed.tsv b/internal/provision/testdata/plan14-root-reads-seed.tsv| index 84e757af5..6525f6fad 100644 |
| --- a/internal/provision/testdata/plan14-root-reads-seed.tsv |
| +++ b/internal/provision/testdata/plan14-root-reads-seed.tsv |
| @@ -1,4 +1,4 @@ |
| -# Files seed checkpoint 11 overlay; pending Paul’s census ruling. |
| +# Files seed checkpoint 12 overlay; pending Paul’s census ruling. |
| # Database retirement activation remains refused; serving installation transfer uses the existing root transport. |
| # Load after zddr4 and its explicit seal check. No legacy fingerprints are added or removed. |
| # Every classification below is rev3=PROPOSED-seed, not an approved disposition. |
| @@ -69,13 +69,13 @@ internal/host/claimservingparksource_linux.go servingParkStateDirectory authorit |
| internal/hostguard/servingretirement.go Guard.WithServingRetirement authority C,J4 rev3=PROPOSED-seed; fresh serving claim and root installation/fence snapshot under root and shared host locks; refuses pending, quarantined, foreign, retired or already stopped successors; permits an older completed predecessor 3d105c50be7e613777055f561321efadf77fe823d8dc3263a60f2c1211b71108 |
| internal/host/claimservingparkroot_linux.go servingParkRoot.withAuthority authority C,J4 rev3=PROPOSED-seed; live edge peer reauthentication under namespace and host locks before census, inventory and final claim checks; root request entry refuses nil connections d3582f0760b4459721b3e6040cda738b105c1da19cc5d0746d04fea26e313b5c |
| internal/host/claimservingparkroot_linux.go servingRootStops authority C,J4 rev3=PROPOSED-seed; retained exact root serving receipts and known retired installation; same enrollment, session and region, with canonical recorded revision no newer than the active actor d371f01614c0bb64d3f412123fc9a90b8bee43d02b6c48e67fc89d9a35187005 |
| -internal/host/claimservingparkroot_linux.go servingParkRoot.parkServingDatabase authority C,J4 rev3=PROPOSED-seed; fresh paired tags, root slot/record and retained admissions; recorded legacy work dispatches to separately locked fresh legacy authority 61c29d5b37168ab053499553eaf0052fcee134a601c86acb3195fad416d28247 |
| +internal/host/claimservingparkroot_linux.go servingParkRoot.parkServingDatabase authority C,J4 rev3=PROPOSED-seed; fresh paired tags, root slot/record and retained admissions; post-retag replay selects the predecessor from the saved active tag-source stamp and revalidates its exact identity; recorded legacy work dispatches to separately locked fresh legacy authority 4a1d89b8f65ed52e1aedf00595021ec9b42bfd1fed4a3466a512fbdd6078538c |
| internal/host/claimservingparkroot_linux.go servingParkRoot.completeServingDatabasePark authority C,J4 rev3=PROPOSED-seed; fresh paired enrollment, same current writer, root source and original/continuation stop history; legacy additionally authenticates persistent no-process or admitted predecessor evidence 8ca9bdd3c1209c6d5257bc07a7d12d0bab900e9c2911d0698077eb2116e5123c |
| internal/host/claimservingparkroot_linux.go validateServingRootHistory authority C,J4 rev3=PROPOSED-seed; retained local successor admissions and byte-exact root stops for every persisted continuation; rejects relabelled or missing history c85a1be96de5fa61acd48c4adf9393d8e837880f5133a2f965f1104037e02329 |
| internal/host/claimservingpark_records.go servingParkRecord.activeAuthorization authority C,J4 rev3=PROPOSED-seed; last persisted continuation authorization or immutable original authorization; no source or deadline changes fe168fe52317e45ce70c3e97a9bc2c52d2409328ab4844a8d2a34fc4babba36d |
| internal/host/claimservingpark_records.go servingParkLedger.continueLocked authority C,J4 rev3=PROPOSED-seed; namespace/host-protected slot and record locks; pending original retirement, absent source and retained parked identities before durable bounded continuation 87f297a924f7f153624304233218d8b5548a016fc7dbf0f7cb8dedb789324009 |
| internal/host/claimservingpark_records.go servingParkLedger.replaceLocked authority C,J4 rev3=PROPOSED-seed; completed incumbent record under its record lock; resumes its recorded deletion cause before slot replacement 3e67316e959c9a901c32a2a61fda4e6ea5e92fe51bdb7fdb5f5c53d9d5960aad |
| -@coverage internal/host 8b2636a4c0d76b826c438e30b4a433ebf2ce5195e9d6aa21980b72a9bf84caa3 |
| +@coverage internal/host 3b69facca859268158f6e90dc5ff978225d200725716332ae0235ba94a415a5c |
| @coverage internal/hostbox eb1f4980ba609cc886ae4bd73aedbb6be3fb2eb104165bb1d8404da0814ed190 |
| @coverage internal/hostguard a721142f4f3ac5fa17b6b1e2ccfc66ca809cfdcbf2733fdc00fbd0efaa90aeb6 |
| internal/host/claimservinglegacy.go servingLegacyKey authority C,J4 rev3=PROPOSED-seed; box, host and incarnation derive private stop and consumption keys; never a caller path 93c10e40202ab23986b54a366edd7abfab9f8d942736305b5732e8741e96f3ab |