carlos / platform Public

claim Plan 14 Part 1 fix round 6: no census means main's rollback target

Under merge ruling 6 the rollback target is an effect, so runningVersion
returns nothing without a census and main's cur stands (an interrupted
repoint rolls back to what the link names, as on main). Census-backed
retention is unchanged. Adds the interrupted-repoint row to the
main-equivalence table (it catches the old behaviour) and splits the
crash-before-capture test by census mode. Census overlay fix6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 15fe8df1da518383bee1bdcb1ac608d6843dda85 parent 3246b68
6 files changed, +102 −69
  • internal/host/plan14_fix3_live_linux_test.go +61 −51
  • internal/host/plan14_fix5_live_linux_test.go +23 −1
  • internal/host/rootrestart.go +9 −17
  • internal/provision/plan14_legacy_identity_test.go +3 −0
  • internal/provision/plan14_root_reads_test.go +3 −0
  • internal/provision/testdata/plan14-root-reads-fix6.tsv +3 −0

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.015s
?   	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	341.418s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	4.218s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.900s
ok  	github.com/carlosframework/platform/internal/activator	228.946s
ok  	github.com/carlosframework/platform/internal/agentsock	0.024s
ok  	github.com/carlosframework/platform/internal/alerting	0.004s
ok  	github.com/carlosframework/platform/internal/analytics	0.041s
ok  	github.com/carlosframework/platform/internal/appconfig	2.755s
ok  	github.com/carlosframework/platform/internal/apperr	0.105s
ok  	github.com/carlosframework/platform/internal/applog	0.253s
ok  	github.com/carlosframework/platform/internal/awsinv	0.031s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.043s
ok  	github.com/carlosframework/platform/internal/boxid	0.976s
?   	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.027s
ok  	github.com/carlosframework/platform/internal/caps	0.031s
ok  	github.com/carlosframework/platform/internal/certwarm	0.016s
ok  	github.com/carlosframework/platform/internal/cgroup	0.254s
ok  	github.com/carlosframework/platform/internal/changelog	0.391s
ok  	github.com/carlosframework/platform/internal/clamd	0.060s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	369.276s
?   	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	6.088s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.013s
ok  	github.com/carlosframework/platform/internal/dbverify	0.012s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.013s
ok  	github.com/carlosframework/platform/internal/docsite	0.008s
ok  	github.com/carlosframework/platform/internal/domains	2.539s
ok  	github.com/carlosframework/platform/internal/doorkey	0.028s
ok  	github.com/carlosframework/platform/internal/economics	0.005s
ok  	github.com/carlosframework/platform/internal/edge	28.233s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.013s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.199s
ok  	github.com/carlosframework/platform/internal/gate	1.791s
ok  	github.com/carlosframework/platform/internal/genselect	0.026s
ok  	github.com/carlosframework/platform/internal/geo	0.026s
ok  	github.com/carlosframework/platform/internal/geoip	0.042s
ok  	github.com/carlosframework/platform/internal/host	100.953s
ok  	github.com/carlosframework/platform/internal/hostactors	0.710s
ok  	github.com/carlosframework/platform/internal/hostbox	3.534s
ok  	github.com/carlosframework/platform/internal/hostclaim	1.343s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.024s
ok  	github.com/carlosframework/platform/internal/hostexec	7.952s
ok  	github.com/carlosframework/platform/internal/hostfence	0.052s
ok  	github.com/carlosframework/platform/internal/hostfiles	11.300s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.022s
ok  	github.com/carlosframework/platform/internal/hostguard	1.083s
ok  	github.com/carlosframework/platform/internal/hostops	2.601s
?   	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.004s
ok  	github.com/carlosframework/platform/internal/hostrelocate	1.133s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.334s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.022s
ok  	github.com/carlosframework/platform/internal/infraview	0.021s
ok  	github.com/carlosframework/platform/internal/instancectl	1.809s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.927s
ok  	github.com/carlosframework/platform/internal/instancefs	0.005s
ok  	github.com/carlosframework/platform/internal/instancekey	0.004s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.021s
ok  	github.com/carlosframework/platform/internal/layout	2.333s
ok  	github.com/carlosframework/platform/internal/lease	9.561s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.020s
ok  	github.com/carlosframework/platform/internal/ledger	0.060s
ok  	github.com/carlosframework/platform/internal/mail	0.386s
ok  	github.com/carlosframework/platform/internal/maintenance	0.169s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.989s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.140s
ok  	github.com/carlosframework/platform/internal/metrics	0.034s
ok  	github.com/carlosframework/platform/internal/moves	24.122s
ok  	github.com/carlosframework/platform/internal/moves/archive	1.457s
ok  	github.com/carlosframework/platform/internal/objstore	0.498s
ok  	github.com/carlosframework/platform/internal/ops	0.339s
ok  	github.com/carlosframework/platform/internal/park	0.415s
ok  	github.com/carlosframework/platform/internal/privatefile	0.009s
ok  	github.com/carlosframework/platform/internal/provision	15.676s
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.707s
ok  	github.com/carlosframework/platform/internal/relay	7.297s
ok  	github.com/carlosframework/platform/internal/release	22.656s
ok  	github.com/carlosframework/platform/internal/rollout	4.456s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.005s
ok  	github.com/carlosframework/platform/internal/rootaws	0.012s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.027s
ok  	github.com/carlosframework/platform/internal/rootstate	0.027s
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.931s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	19.285s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	4.556s
ok  	github.com/carlosframework/platform/internal/status	0.092s
ok  	github.com/carlosframework/platform/internal/steering	0.007s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.670s
ok  	github.com/carlosframework/platform/internal/svcrole	0.006s
ok  	github.com/carlosframework/platform/internal/telemetry	0.005s
ok  	github.com/carlosframework/platform/internal/termfence	0.006s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.007s
ok  	github.com/carlosframework/platform/internal/unixdial	0.017s
ok  	github.com/carlosframework/platform/internal/updatewords	0.009s
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.963s
=== 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/13 17:10:08 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DE25EEA26AD9CE, 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.00s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.020s
minio contract: PASS
diff --git a/internal/host/plan14_fix3_live_linux_test.go b/internal/host/plan14_fix3_live_linux_test.go
index c7b3861dc..d539d5836 100644
--- a/internal/host/plan14_fix3_live_linux_test.go
+++ b/internal/host/plan14_fix3_live_linux_test.go
@@ -450,64 +450,74 @@ func TestR2CrashBeforeRestartCapture(t *testing.T) {
if !plan14RootFixture(t) {
return
}
- for _, fail := range []bool{false, true} {
- t.Run(map[bool]string{false: "forward", true: "rollback"}[fail], func(t *testing.T) {
- f := r2Fixture(t, "list")
- crashed := errors.New("crash after repoint before restart capture")
- func() {
- defer func() {
- if recover() != crashed {
- t.Fatal("missing crash")
- }
- }()
- _ = release.Pass(t.Context(), f.e.root.reg, f.e.store, f.pub, f.u.opt, func(string) error { panic(crashed) }, release.WithRunningVersion(f.c.runningVersion))
- }()
- f.wantLive(t, "v2")
- f.wantRunning(t, "v1")
- state, err := f.c.attempts()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := readRollbackAttempt(state, f.rt.Host); !os.IsNotExist(err) {
- t.Fatal("attempt already captured", err)
- }
- m := release.Manifest{App: f.rt.App, Version: "v3", Kind: "binary", Artifacts: map[string]release.Artifact{release.AdoptPlatform(): {}}}
- if err := release.Ship(t.Context(), f.e.store, "bab", m, map[string][]byte{release.AdoptPlatform(): []byte("third build")}); err != nil {
- t.Fatal(err)
- }
- f.promote(t, "v3")
- // A new resolver and restart callback model the next root process.
- f.source.index = nil
- if fail {
- f.u.afterEffect = func() {
- if f.u.exe == release.VersionPath(f.u.opt, "", "v3", f.rt.App) {
- f.u.props.ActiveState, f.u.props.SubState, f.u.props.MainPID = "failed", "failed", 0
- }
+ // Merge ruling 6: without a census the rollback target is main's cur
+ // (the interrupted repoint's v2); only a census-backed box retains the
+ // verified running build (v1) as its rollback target.
+ for _, mode := range []string{"no census", "census"} {
+ for _, fail := range []bool{false, true} {
+ t.Run(mode+"/"+map[bool]string{false: "forward", true: "rollback"}[fail], func(t *testing.T) {
+ f := r2Fixture(t, "list")
+ if mode == "census" {
+ f.c.legacy = func() (bool, error) { return false, nil }
}
- }
- err = f.pass(t)
- if fail {
- if err == nil {
- t.Fatal("failed build accepted")
- }
- f.wantLive(t, "v1")
+ // Main's rollback target, and the retained one with a census.
+ target := map[string]string{"no census": "v2", "census": "v1"}[mode]
+ crashed := errors.New("crash after repoint before restart capture")
+ func() {
+ defer func() {
+ if recover() != crashed {
+ t.Fatal("missing crash")
+ }
+ }()
+ _ = release.Pass(t.Context(), f.e.root.reg, f.e.store, f.pub, f.u.opt, func(string) error { panic(crashed) }, release.WithRunningVersion(f.c.runningVersion))
+ }()
+ f.wantLive(t, "v2")
f.wantRunning(t, "v1")
- } else {
+ state, err := f.c.attempts()
if err != nil {
t.Fatal(err)
}
- f.wantLive(t, "v3")
- f.wantRunning(t, "v3")
- prior, err := os.Readlink(filepath.Join(f.u.opt, "previous", f.rt.Host))
- if err != nil || prior != release.VersionPath(f.u.opt, "", "v1", f.rt.App) {
- t.Fatal("lost running authority", prior, err)
+ if _, err := readRollbackAttempt(state, f.rt.Host); !os.IsNotExist(err) {
+ t.Fatal("attempt already captured", err)
}
- pins, err := release.LivePins(f.u.opt)
- if err != nil || !pins["v1"] {
- t.Fatal("rollback build not pinned", pins, err)
+ m := release.Manifest{App: f.rt.App, Version: "v3", Kind: "binary", Artifacts: map[string]release.Artifact{release.AdoptPlatform(): {}}}
+ if err := release.Ship(t.Context(), f.e.store, "bab", m, map[string][]byte{release.AdoptPlatform(): []byte("third build")}); err != nil {
+ t.Fatal(err)
}
- }
- })
+ f.promote(t, "v3")
+ // A new resolver and restart callback model the next root process.
+ f.source.index = nil
+ if fail {
+ f.u.afterEffect = func() {
+ if f.u.exe == release.VersionPath(f.u.opt, "", "v3", f.rt.App) {
+ f.u.props.ActiveState, f.u.props.SubState, f.u.props.MainPID = "failed", "failed", 0
+ }
+ }
+ }
+ err = f.pass(t)
+ if fail {
+ if err == nil {
+ t.Fatal("failed build accepted")
+ }
+ f.wantLive(t, target)
+ f.wantRunning(t, target)
+ } else {
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.wantLive(t, "v3")
+ f.wantRunning(t, "v3")
+ prior, err := os.Readlink(filepath.Join(f.u.opt, "previous", f.rt.Host))
+ if err != nil || prior != release.VersionPath(f.u.opt, "", target, f.rt.App) {
+ t.Fatal("previous pointer", prior, err)
+ }
+ pins, err := release.LivePins(f.u.opt)
+ if err != nil || !pins[target] {
+ t.Fatal("rollback build not pinned", pins, err)
+ }
+ }
+ })
+ }
}
}
diff --git a/internal/host/plan14_fix5_live_linux_test.go b/internal/host/plan14_fix5_live_linux_test.go
index 0676ce7be..75022acfb 100644
--- a/internal/host/plan14_fix5_live_linux_test.go
+++ b/internal/host/plan14_fix5_live_linux_test.go
@@ -137,6 +137,28 @@ func fix5Run(t *testing.T, kind, history string, main bool) []fix5Result {
u.resetError = true
case "TestR4ForwardTryRestartError":
u.tryError = true
+ case "interrupted repoint":
+ // A root pass crashed after pointing v2 live and before restarting
+ // v1's process; the next build, v3, then fails to come up. Main
+ // rolls back to what the link names (v2), not to the running v1.
+ account := release.RouteAccount(f.rt)
+ if err := release.StageArtifact(f.u.opt, account, "v2", f.rt.App, []byte("new build")); err != nil {
+ t.Fatal(err)
+ }
+ if err := release.PointLive(f.u.opt, f.rt.Host, account, "v2", f.rt.App); err != nil {
+ t.Fatal(err)
+ }
+ m := release.Manifest{App: f.rt.App, Version: "v3", Kind: "binary", Artifacts: map[string]release.Artifact{release.AdoptPlatform(): {}}}
+ if err := release.Ship(t.Context(), f.e.store, "bab", m, map[string][]byte{release.AdoptPlatform(): []byte("third build")}); err != nil {
+ t.Fatal(err)
+ }
+ f.promote(t, "v3")
+ v3 := release.VersionPath(f.u.opt, account, "v3", f.rt.App)
+ target.afterEffect = func() {
+ if target.exe == v3 {
+ target.props.ActiveState, target.props.SubState, target.props.MainPID = "failed", "failed", 0
+ }
+ }
}
getenv := func(string) string { return "5s" }
observer := f.e.root.obs
@@ -246,7 +268,7 @@ func TestPlan14Fix5MainEquivalence(t *testing.T) {
t.Run(map[string]string{"": "claims-off", "1": "claims-on"}[flag], func(t *testing.T) {
t.Setenv("CARLOS_HOST_CLAIM", flag)
for _, kind := range []string{"none", "record", "list", "guard", "different stamp"} {
- for _, history := range []string{"healthy", "crash", "failed-then-reset-failed before rollback", "auto-restart", "inactive", "incident reset before", "incident reset after", "TestR4ForwardResetFailedError", "TestR4ForwardTryRestartError"} {
+ for _, history := range []string{"healthy", "crash", "failed-then-reset-failed before rollback", "auto-restart", "inactive", "incident reset before", "incident reset after", "TestR4ForwardResetFailedError", "TestR4ForwardTryRestartError", "interrupted repoint"} {
t.Run(kind+"/"+history, func(t *testing.T) {
want := fix5Run(t, kind, history, true)
got := fix5Run(t, kind, history, false)
diff --git a/internal/host/rootrestart.go b/internal/host/rootrestart.go
index eb87e6355..93109feb0 100644
--- a/internal/host/rootrestart.go
+++ b/internal/host/rootrestart.go
@@ -114,25 +114,17 @@ func (c rootRestartChecks) runningVersion(ctx context.Context, rt registry.Route
if rt.Unit == "" || rt.Unit == activator.UnitName(rt.Host) {
return "", nil
}
- b, err := c.resolve(ctx, rt)
- if err == nil && b.Route.Unit != rt.Unit {
- // Without a census, retain only the unit main will restart.
- legacy, checkErr := c.allowLegacy()
- if checkErr != nil {
- return "", checkErr
- }
- if legacy {
- return "", nil
- }
+ // Merge ruling 6: without a census the binding never chooses an
+ // effect, and the rollback target is one. Main's cur stands.
+ legacy, err := c.allowLegacy()
+ if err != nil {
+ return "", err
}
+ if legacy {
+ return "", nil
+ }
+ b, err := c.resolve(ctx, rt)
if err != nil {
- legacy, checkErr := c.allowLegacy()
- if checkErr != nil {
- return "", checkErr
- }
- if legacy {
- return "", nil
- }
return "", err
}
if b.PID > 0 {
diff --git a/internal/provision/plan14_legacy_identity_test.go b/internal/provision/plan14_legacy_identity_test.go
index 146f54083..f96fea929 100644
--- a/internal/provision/plan14_legacy_identity_test.go
+++ b/internal/provision/plan14_legacy_identity_test.go
@@ -184,6 +184,9 @@ func TestPlan14Fix5OverlayPreservesLegacySeal(t *testing.T) {
if err := loadRootReadInventory(root+"/internal/provision/testdata/plan14-root-reads-fix5.tsv", &inv, true); err != nil {
t.Fatal(err)
}
+ if err := loadRootReadInventory(root+"/internal/provision/testdata/plan14-root-reads-fix6.tsv", &inv, true); err != nil {
+ t.Fatal(err)
+ }
if errs := checkRootReadLegacy(inv, seal); len(errs) != 0 {
t.Fatal(errs)
}
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go
index 43d6dc9a1..4f2a404c4 100644
--- a/internal/provision/plan14_root_reads_test.go
+++ b/internal/provision/plan14_root_reads_test.go
@@ -637,6 +637,9 @@ func TestPlan14RootReadCensus(t *testing.T) {
if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-fix5.tsv"), &inv, true); err != nil {
t.Fatal(err)
}
+ if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-fix6.tsv"), &inv, true); err != nil {
+ t.Fatal(err)
+ }
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-fix6.tsv b/internal/provision/testdata/plan14-root-reads-fix6.tsv
new file mode 100644
index 000000000..34d429164
--- /dev/null
+++ b/internal/provision/testdata/plan14-root-reads-fix6.tsv
@@ -0,0 +1,3 @@
+# Plan 14 fix6: under merge ruling 6 the rollback target is an effect, so runningVersion retains nothing without a census; earlier overlays and the legacy freeze are unchanged.
+internal/host/rootrestart.go rootRestartChecks.runningVersion authority B3 rev3=merge-ruling-6;basis=Without a census return no running build, so main's cur stays the rollback target and no record is resolved; census-backed retention unchanged. registry:Host@151,registry:Unit@104,registry:Unit@121,whole:Route@439 173ae670856b3e93089b04f45d6356b88910f7cd8eeb7c3bc7770c9ff4ab13c9
+@coverage internal/host efb1aa8d275862827daeacf4955d653fb48bae69e8fccacb0efdb881334e0419