carlos / platform Public

WIP claim Plan 14 Part 3 checkpoint 10 (not for merge)

Narrowed scope: E3 sticky veto off in production, E4 refusal-based protection on both convergence paths, J4 limited to enabled families, H2 boundary witnesses; reboot fixtures pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev e72f7f867020cfa09823546f3022044c7c0bc1ad parent da7f4f7
14 files changed, +730 −25
  • internal/caps/converge.go +11 −0
  • internal/caps/root_authorization_test.go +41 −0
  • internal/caps/rootauthority.go +31 −0
  • internal/host/plan14_root_failures_test.go +38 −4
  • internal/host/rootcaps_root_linux_test.go +154 −0
  • internal/host/rootupdates.go +9 −9
  • internal/host/rootupdates_hardened_root_linux_test.go +129 −0
  • internal/host/rootupdates_legacy_linux_test.go +48 −0
  • internal/host/rootupdates_opener_root_linux_test.go +143 −0
  • internal/host/rootupdates_runtime.go +5 −1
  • internal/host/rootupdates_runtime_test.go +33 −0
  • internal/provision/plan14_input_inventory_test.go +76 −5
  • internal/provision/plan14_p3_census_test.go +1 −1
  • internal/provision/testdata/plan14-root-reads-p3.tsv +11 −5

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	294.649s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.727s
ok  	github.com/carlosframework/platform/cmd/moveprobe	92.263s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.706s
ok  	github.com/carlosframework/platform/internal/activator	249.925s
ok  	github.com/carlosframework/platform/internal/agentsock	0.019s
ok  	github.com/carlosframework/platform/internal/alerting	0.003s
ok  	github.com/carlosframework/platform/internal/analytics	0.038s
ok  	github.com/carlosframework/platform/internal/appconfig	2.455s
ok  	github.com/carlosframework/platform/internal/apperr	0.088s
ok  	github.com/carlosframework/platform/internal/applog	0.158s
ok  	github.com/carlosframework/platform/internal/awsinv	0.005s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.004s
ok  	github.com/carlosframework/platform/internal/boxid	0.507s
?   	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.009s
ok  	github.com/carlosframework/platform/internal/caps	0.022s
ok  	github.com/carlosframework/platform/internal/certwarm	0.002s
ok  	github.com/carlosframework/platform/internal/cgroup	0.702s
ok  	github.com/carlosframework/platform/internal/changelog	0.322s
ok  	github.com/carlosframework/platform/internal/clamd	0.054s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	391.998s
?   	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.358s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.020s
ok  	github.com/carlosframework/platform/internal/dbverify	0.013s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.010s
ok  	github.com/carlosframework/platform/internal/docsite	0.012s
ok  	github.com/carlosframework/platform/internal/domains	2.371s
ok  	github.com/carlosframework/platform/internal/doorkey	0.014s
ok  	github.com/carlosframework/platform/internal/economics	0.014s
ok  	github.com/carlosframework/platform/internal/edge	26.779s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.017s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.145s
ok  	github.com/carlosframework/platform/internal/gate	1.669s
ok  	github.com/carlosframework/platform/internal/genselect	0.035s
ok  	github.com/carlosframework/platform/internal/geo	0.027s
ok  	github.com/carlosframework/platform/internal/geoip	0.047s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.160s
ok  	github.com/carlosframework/platform/internal/host	146.924s
ok  	github.com/carlosframework/platform/internal/hostactors	0.399s
ok  	github.com/carlosframework/platform/internal/hostbox	2.524s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.488s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.013s
ok  	github.com/carlosframework/platform/internal/hostexec	4.918s
ok  	github.com/carlosframework/platform/internal/hostfence	0.044s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.408s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.013s
ok  	github.com/carlosframework/platform/internal/hostguard	1.027s
ok  	github.com/carlosframework/platform/internal/hostops	1.572s
?   	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.455s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.145s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.012s
ok  	github.com/carlosframework/platform/internal/infraview	0.012s
ok  	github.com/carlosframework/platform/internal/instancectl	4.174s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.712s
ok  	github.com/carlosframework/platform/internal/instancefs	0.012s
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.555s
ok  	github.com/carlosframework/platform/internal/lease	9.821s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.018s
ok  	github.com/carlosframework/platform/internal/ledger	0.014s
ok  	github.com/carlosframework/platform/internal/mail	0.164s
ok  	github.com/carlosframework/platform/internal/maintenance	0.082s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.514s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.080s
ok  	github.com/carlosframework/platform/internal/metrics	0.006s
ok  	github.com/carlosframework/platform/internal/moves	21.120s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.799s
ok  	github.com/carlosframework/platform/internal/objstore	0.179s
ok  	github.com/carlosframework/platform/internal/ops	0.186s
ok  	github.com/carlosframework/platform/internal/park	0.203s
ok  	github.com/carlosframework/platform/internal/privatefile	0.004s
ok  	github.com/carlosframework/platform/internal/provision	34.624s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.010s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.503s
ok  	github.com/carlosframework/platform/internal/relay	7.243s
ok  	github.com/carlosframework/platform/internal/release	21.619s
ok  	github.com/carlosframework/platform/internal/rollout	3.774s
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.058s
ok  	github.com/carlosframework/platform/internal/rootstate	0.026s
ok  	github.com/carlosframework/platform/internal/runs	0.582s
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.492s
ok  	github.com/carlosframework/platform/internal/seeds	0.011s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	9.594s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	4.222s
ok  	github.com/carlosframework/platform/internal/status	0.037s
ok  	github.com/carlosframework/platform/internal/steering	0.005s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.217s
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.007s
ok  	github.com/carlosframework/platform/internal/version	0.002s
ok  	github.com/carlosframework/platform/internal/vet	1.555s
=== 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
linux/arm64 carlos binary: 47513762 bytes (ceiling 52000000)
--- 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/16 01:08:21 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DEDD306AB27719, 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/caps/converge.go b/internal/caps/converge.go
index c0e1baaae..adaedf271 100644
--- a/internal/caps/converge.go
+++ b/internal/caps/converge.go
@@ -41,6 +41,8 @@ const (
// the converge tests need no systemd, no root, and no /etc — the same seam
// shape activator.UnitBackend.Systemctl uses.
type Ops struct {
+ Authorize func() error
+
EtcDir string // "" = DefaultEtcDir
StateRoot string // "" = DefaultStateRoot
RunDir string // "" = DefaultRunDir
@@ -187,6 +189,9 @@ func DropInPaths(etcDir, host string) (service, socket string) {
func Converge(o Ops, p Plan) (Result, error) {
var res Result
res.Prev.Host = p.Host
+ if err := o.authorizeMutation(); err != nil {
+ return res, err
+ }
// The host becomes a filename under /etc/systemd/system, written as
// root. Same guard, same reason, same trust level as appconfig.syncHost's
@@ -921,6 +926,9 @@ func CheckClaims(plans []Plan) map[string]error {
// not ours when the snapshot was taken is not ours to write over or delete
// now either.
func Restore(o Ops, prev Snapshot) error {
+ if err := o.authorizeMutation(); err != nil {
+ return err
+ }
if err := validHost(prev.Host); err != nil {
o.logf(" %q: %v", prev.Host, err)
return err
@@ -951,6 +959,9 @@ func Restore(o Ops, prev Snapshot) error {
// not this function's business: rootRestartRoute's try-restart keeps owning them,
// byte-identically.
func Restart(o Ops, p Plan) error {
+ if err := o.authorizeMutation(); err != nil {
+ return err
+ }
if err := validHost(p.Host); err != nil {
o.logf(" %q: %v", p.Host, err)
return err
diff --git a/internal/caps/root_authorization_test.go b/internal/caps/root_authorization_test.go
new file mode 100644
index 000000000..0b58d214f
--- /dev/null
+++ b/internal/caps/root_authorization_test.go
@@ -0,0 +1,41 @@
+package caps
+
+import (
+ "errors"
+ "os"
+ "testing"
+)
+
+func TestPlan14CapabilityMutationAuthorization(t *testing.T) {
+ for _, operation := range []string{"converge", "restore", "restart"} {
+ t.Run(operation, func(t *testing.T) {
+ o, sys, _, users := testOps(t)
+ p := keymailPlan(t, Grants{Ports: map[int]bool{25: true}})
+ if _, err := Converge(o, p); err != nil {
+ t.Fatal(err)
+ }
+ service, socket := DropInPaths(o.EtcDir, p.Host)
+ beforeService, beforeSocket := string(readFile(t, service)), string(readFile(t, socket))
+ sys.reset()
+ *users = nil
+ calls := 0
+ o.Authorize = func() error { calls++; return os.ErrPermission }
+ var err error
+ switch operation {
+ case "converge":
+ p.Grants = Grants{}
+ _, err = Converge(o, p)
+ case "restore":
+ err = Restore(o, Snapshot{Host: p.Host})
+ case "restart":
+ err = Restart(o, p)
+ }
+ if !errors.Is(err, os.ErrPermission) || calls != 1 || len(sys.calls) != 0 || len(*users) != 0 {
+ t.Fatal("unauthorized operation", err, calls, sys.calls, *users)
+ }
+ if string(readFile(t, service)) != beforeService || string(readFile(t, socket)) != beforeSocket {
+ t.Fatal("refusal changed files")
+ }
+ })
+ }
+}
diff --git a/internal/caps/rootauthority.go b/internal/caps/rootauthority.go
new file mode 100644
index 000000000..e2e5e3389
--- /dev/null
+++ b/internal/caps/rootauthority.go
@@ -0,0 +1,31 @@
+package caps
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+
+ "github.com/carlosframework/platform/internal/rootparticipation"
+)
+
+func (o Ops) authorizeMutation() error {
+ if _, err := os.Lstat(filepath.Join(rootparticipation.Namespace, "census.json")); !errors.Is(err, os.ErrNotExist) {
+ if err != nil {
+ return err
+ }
+ ledger, required, err := rootparticipation.OpenForUpdate()
+ if ledger.State != nil {
+ defer ledger.State.Close()
+ }
+ if err != nil {
+ return err
+ }
+ if required {
+ return os.ErrPermission
+ }
+ }
+ if o.Authorize != nil {
+ return o.Authorize()
+ }
+ return nil
+}
diff --git a/internal/host/plan14_root_failures_test.go b/internal/host/plan14_root_failures_test.go
index e3ccc5311..515ebb16c 100644
--- a/internal/host/plan14_root_failures_test.go
+++ b/internal/host/plan14_root_failures_test.go
@@ -120,7 +120,7 @@ func TestPlan14SwapFailureMigrationUnknown(t *testing.T) {
}
}
-func testRootSettingsPass(t *testing.T, book *rollout.RootFailures, attack string) {
+func testRootSettingsPass(t *testing.T, book *rollout.RootFailures, attack string, activation ...bool) {
t.Helper()
ctx := context.Background()
opt := t.TempDir()
@@ -151,7 +151,10 @@ func testRootSettingsPass(t *testing.T, book *rollout.RootFailures, attack strin
census := func() (rootparticipation.Census, error) {
return rootparticipation.Census{Schema: 1, Box: "box", Enrollment: "en", Migration: 1, Complete: true}, nil
}
- p := rootUpdateSource{source: rootRestartRequestSource{state: state, store: store, census: census}, state: state, book: book, store: store, pub: pub, binding: &proofBindingSource{}, data: t.TempDir()}
+ p := rootUpdateSource{enforceFailures: len(activation) == 0, source: rootRestartRequestSource{state: state, store: store, census: census}, state: state, book: book, store: store, pub: pub, binding: &proofBindingSource{}, data: t.TempDir()}
+ if len(activation) != 0 {
+ p.publish = func(string, rootRetryStatusRecord) error { return os.ErrPermission }
+ }
rt := provision.RouteFor(rec, p.data, "", false, nil)
reg, err := registry.Open(filepath.Join(t.TempDir(), "r.db"))
if err != nil {
@@ -192,8 +195,21 @@ func testRootSettingsPass(t *testing.T, book *rollout.RootFailures, attack strin
if err = pass(); err != nil {
t.Fatal(err)
}
- if v, _ := release.LiveVersion(opt, rt.Host); v != "" || effects != 0 {
- t.Fatal("failed build dispatched", v, effects)
+ want := ""
+ if len(activation) != 0 {
+ want = "v1"
+ }
+ if v, _ := release.LiveVersion(opt, rt.Host); v != want || effects != 0 {
+ t.Fatal("wrong activation failure policy", v, want, effects)
+ }
+ if len(activation) != 0 {
+ if !book.HeldDown(rec.Account, rec.App, "v1", rec.Host) {
+ t.Fatal("attribution erased")
+ }
+ if saved, err := p.resolve(ctx, rec.Host); err != nil || saved.Failed("v1") || saved.Failed("v2") {
+ t.Fatal("sticky deny active", err)
+ }
+ return
}
raw, _ := state.Read("settings-" + rt.Host + ".json")
var saved rootUpdateRecord
@@ -296,3 +312,21 @@ func testRootFailureStore(t *testing.T) rootstate.Store {
t.Cleanup(func() { s.Close() })
return s
}
+
+func TestPlan14RootFailureActivationGate(t *testing.T) {
+ for _, attack := range []string{"combined", "served", "channel", "build"} {
+ t.Run(attack, func(t *testing.T) {
+ b, err := rollout.InitializeRootFailures(testRootFailureStore(t), "box/en/1", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err = b.Record(rollout.FailureRecord{Account: "bab", App: "km", Build: "v1", Host: "h.example.com", HeldDown: []string{"h.example.com"}}); err != nil {
+ t.Fatal(err)
+ }
+ if err = b.MarkHoldRan("h.example.com", rollout.HoldRan{Account: "bab", App: "km", Build: "v1", Exec: "unsettled"}); err != nil {
+ t.Fatal(err)
+ }
+ testRootSettingsPass(t, b, attack, true)
+ })
+ }
+}
diff --git a/internal/host/rootcaps_root_linux_test.go b/internal/host/rootcaps_root_linux_test.go
new file mode 100644
index 000000000..2f97212a5
--- /dev/null
+++ b/internal/host/rootcaps_root_linux_test.go
@@ -0,0 +1,154 @@
+//go:build linux && rootrunner
+
+package host
+
+import (
+ "bytes"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "syscall"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/caps"
+ "github.com/carlosframework/platform/internal/release"
+ "github.com/carlosframework/platform/internal/rootparticipation"
+ "github.com/carlosframework/platform/internal/rootstate"
+)
+
+func TestPlan14RootCapabilitiesBothPaths(t *testing.T) {
+ if action := os.Getenv("PLAN14_CAPS_HOSTILE"); action != "" {
+ path := filepath.Join(rootparticipation.Namespace, "census.json")
+ var err error
+ if action == "renamed" {
+ err = os.Rename(path, path+"-old")
+ } else {
+ err = os.Remove(path)
+ }
+ if !os.IsPermission(err) {
+ t.Fatal("service UID mutated census", err)
+ }
+ return
+ }
+ if os.Geteuid() != 0 {
+ t.Fatal("root required")
+ }
+ if _, err := os.Lstat("/var/lib/carlos-claims"); !os.IsNotExist(err) {
+ t.Fatal("existing authority", err)
+ }
+ opt := t.TempDir()
+ o, effects := passOps(t)
+ rt := capsUnitRoute("keymail.dev", "keymail")
+ rt.Caps, rt.Channel = "port:25", "canary/x"
+ reg := openReg(t, rt)
+ set := &caps.Set{V: 1, Daemon: true, Ports: []caps.Port{{Port: 25}}}
+ store := release.NewDirStore(t.TempDir())
+ seed, public, _ := release.GenerateKey()
+ priv, _ := release.ParseSigningKey(seed, "test")
+ pub, _ := release.ParsePublicKey(public, "test")
+ for _, v := range []string{"aaaaaaa", "bbbbbbb"} {
+ m := release.Manifest{Schema: 1, App: rt.App, Version: v, Kind: "binary", Capabilities: set, Artifacts: map[string]release.Artifact{runtimePlatform(): {}}}
+ if err := release.Ship(t.Context(), store, release.OpsAccount, m, map[string][]byte{runtimePlatform(): []byte(v)}); err != nil {
+ t.Fatal(err)
+ }
+ }
+ promote := func(v string) {
+ t.Helper()
+ if err := release.Promote(t.Context(), store, priv, release.OpsAccount, rt.App, rt.Channel, v); err != nil {
+ t.Fatal(err)
+ }
+ }
+ adopt := func() {
+ t.Helper()
+ if err := release.Pass(t.Context(), reg, store, pub, opt, nil, release.WithCapabilities(o)); err != nil {
+ t.Fatal(err)
+ }
+ }
+ promote("aaaaaaa")
+ adopt()
+ if v, _ := release.LiveVersion(opt, rt.Host); v != "aaaaaaa" {
+ t.Fatal("no-census adoption refused", v)
+ }
+ if err := capabilityPass(reg, opt, o); err != nil {
+ t.Fatal(err)
+ }
+ svc, sock := readDropIns(t, o, rt.Host)
+ if sock == "" {
+ t.Fatal("legacy fixture made no drop-in")
+ }
+ if err := os.Mkdir("/var/lib/carlos-claims", 0700); err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll("/var/lib/carlos-claims")
+ state, err := rootstate.Open(rootparticipation.Namespace, true, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer state.Close()
+ if err = state.Write("census.json", []byte(`{"schema":1}`)); err != nil {
+ t.Fatal(err)
+ }
+ callbacks := 0
+ o.Authorize = func() error { callbacks++; return nil }
+ promote("bbbbbbb")
+ for _, grant := range []string{"host-role", "doc-engine", "", "port:25,port:587"} {
+ t.Run(grant, func(t *testing.T) {
+ current, err := reg.Get(rt.Host)
+ if err != nil || current == nil {
+ t.Fatal(err)
+ }
+ if err := reg.SetCaps(rt.Host, current.Caps, grant); err != nil {
+ t.Fatal(err)
+ }
+ effects.calls, effects.users = nil, nil
+ if err := capabilityPass(reg, opt, o); err != nil {
+ t.Fatal(err)
+ }
+ adopt()
+ a, b := readDropIns(t, o, rt.Host)
+ if a != svc || b != sock || len(effects.calls) != 0 || len(effects.users) != 0 || callbacks != 0 {
+ t.Fatal("census permitted unit mutation", effects)
+ }
+ if v, _ := release.LiveVersion(opt, rt.Host); v != "aaaaaaa" {
+ t.Fatal("refused adoption repointed", v)
+ }
+ })
+ }
+ guarded := o
+ if err := caps.Restore(guarded, caps.Snapshot{Host: rt.Host}); err == nil {
+ t.Fatal("restore bypass")
+ }
+ if err := caps.Restart(guarded, caps.Plan{Host: rt.Host}); err == nil {
+ t.Fatal("restart bypass")
+ }
+ for _, hostile := range []string{"corrupt", "renamed", "erased"} {
+ t.Run(hostile, func(t *testing.T) {
+ path := filepath.Join(rootparticipation.Namespace, "census.json")
+ if hostile == "corrupt" {
+ if err := os.WriteFile(path, []byte("corrupt"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ binary, err := os.Open(os.Args[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer binary.Close()
+ cmd := exec.Command("/proc/self/fd/3", "-test.run=^TestPlan14RootCapabilitiesBothPaths$")
+ cmd.ExtraFiles = []*os.File{binary}
+ cmd.Env = append(os.Environ(), "PLAN14_CAPS_HOSTILE="+hostile)
+ cmd.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: 65534, Gid: 65534}}
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatal(err, string(out))
+ }
+ }
+ if _, err := caps.Converge(guarded, caps.Plan{Host: rt.Host}); err == nil {
+ t.Fatal("retained census permitted converge")
+ }
+ })
+ }
+ a, b := readDropIns(t, o, rt.Host)
+ if !bytes.Equal([]byte(a+b), []byte(svc+sock)) {
+ t.Fatal("fixed unit changed")
+ }
+}
diff --git a/internal/host/rootupdates.go b/internal/host/rootupdates.go
index ddef9c9a1..aa87e2f70 100644
--- a/internal/host/rootupdates.go
+++ b/internal/host/rootupdates.go
@@ -18,6 +18,7 @@ import (
"github.com/carlosframework/platform/internal/rollout"
"github.com/carlosframework/platform/internal/rootparticipation"
"github.com/carlosframework/platform/internal/rootstate"
+ "github.com/carlosframework/platform/internal/safefile"
)
type rootUpdateRecord struct {
@@ -36,6 +37,8 @@ type rootServedBuild struct {
}
type rootUpdateSource struct {
+ enforceFailures bool
+
legacy []byte
ready func(registry.Route) error
publish func(string, rootRetryStatusRecord) error
@@ -54,7 +57,7 @@ func rootUpdatesMigration(c rootparticipation.Census) string {
}
func rootFailureLegacy(data string) ([]byte, error) {
- f, err := os.Open(filepath.Join(data, rollout.FailureFile))
+ f, err := safefile.OpenFile(filepath.Join(data, rollout.FailureFile), os.O_RDONLY, 0)
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
@@ -104,13 +107,7 @@ func productionRootUpdates(dd string, probes ...func() (rootparticipation.Ledger
if err != nil {
return fail(err)
}
- legacy, err := rootFailureLegacy(dd)
- if err != nil {
- return fail(err)
- }
- if err = book.ImportLegacy(legacy); err != nil {
- return fail(err)
- }
+ legacy, _ := rootFailureLegacy(dd)
fences, err := rootstate.Open("/var/lib/carlos-claims/authority/fences", false, false)
if err != nil {
return fail(err)
@@ -239,6 +236,9 @@ func (p rootUpdateSource) resolve(ctx context.Context, host string) (release.Roo
}
}
out = release.RootUpdateInput{Route: rt, Build: saved.Build, Consumed: saved.Consumed, Failed: func(build string) bool {
+ if !p.enforceFailures {
+ return false
+ }
served, ok := saved.Served[build]
ok = ok && served.Digest != "" && served.Invocation != "" && served.Source != "" && served.Epoch == p.book.RestrictionEpoch(rec.Account, rec.App, build, host)
if ok {
@@ -257,7 +257,7 @@ func (p rootUpdateSource) resolve(ctx context.Context, host string) (release.Roo
if projectedVersion == "" && ptr != nil {
projectedVersion = ptr.Version
}
- if err = p.publishRetry(saved, projectedVersion); err != nil {
+ if err = p.publishRetry(saved, projectedVersion); err != nil && p.enforceFailures {
return release.RootUpdateInput{}, err
}
if saved.Build.State != nil {
diff --git a/internal/host/rootupdates_hardened_root_linux_test.go b/internal/host/rootupdates_hardened_root_linux_test.go
new file mode 100644
index 000000000..7e7c2709a
--- /dev/null
+++ b/internal/host/rootupdates_hardened_root_linux_test.go
@@ -0,0 +1,129 @@
+//go:build linux && rootrunner
+
+package host
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/rollout"
+ "github.com/carlosframework/platform/internal/rootparticipation"
+ "github.com/carlosframework/platform/internal/rootstate"
+)
+
+func TestPlan14RootRetryHardenedController(t *testing.T) {
+ if os.Getenv("PLAN14_RETRY_HARDENED") == "1" {
+ s, err := rootstate.Open(rollout.RootFailuresDir, false, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer s.Close()
+ b, err := rollout.OpenRootFailures(s, "box/en/1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ p := rootUpdateSource{book: b, publish: publishRootRetry}
+ if err := p.publishRetry(rootUpdateRecord{Route: registry.Route{Host: "h.example.com", Account: "bab", App: "km"}}, "v1"); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }
+ if os.Geteuid() != 0 {
+ t.Fatal("root required")
+ }
+ for _, path := range []string{"/var/lib/carlos-claims", "/var/lib/carlos-release", rootRetryProjection, "/run/carlos-control", "/run/carlos"} {
+ if err := os.Mkdir(path, 0755); err != nil {
+ t.Fatal("existing fixture", path, err)
+ }
+ path := path
+ t.Cleanup(func() { os.RemoveAll(path) })
+ }
+ state, err := rootstate.Open(rootparticipation.Namespace, true, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ raw, _ := json.Marshal(rootparticipation.Census{Schema: 1, Box: "box", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true})
+ if err = state.Write("census.json", raw); err != nil {
+ t.Fatal(err)
+ }
+ state.Close()
+ bookState, err := rootstate.Open(rollout.RootFailuresDir, true, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ book, err := rollout.InitializeRootFailures(bookState, "box/en/1", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err = book.Record(rollout.FailureRecord{Account: "bab", App: "km", Host: "h.example.com", Build: "v1", HeldDown: []string{"h.example.com"}}); err != nil {
+ t.Fatal(err)
+ }
+ id := book.ID()
+ bookState.Close()
+ dir := generationRootRunnerDir(t)
+ config := filepath.Join(dir, "host.env")
+ if err := os.WriteFile(config, []byte("CARLOS_HOST_CLAIM=1\nCARLOS_DATA_DIR="+dir+"\nCARLOS_OPT_DIR="+dir+"\n"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ settings, err := readClaimUnitSettings(config)
+ if err != nil || !settings.rootRetry {
+ t.Fatal("production settings omitted retry scope", settings, err)
+ }
+ binary, err := os.ReadFile(os.Args[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ runner := filepath.Join(dir, "runner.test")
+ if err = os.WriteFile(runner, binary, 0755); err != nil {
+ t.Fatal(err)
+ }
+ raw, err = unitsFS.ReadFile("units/" + claimControlUnit)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var lines []string
+ for _, line := range strings.Split(string(claimUnitContent(claimControlUnit, raw, settings)), "\n") {
+ if strings.HasPrefix(line, "Requires=") || strings.HasPrefix(line, "After=") || strings.HasPrefix(line, "ExecStart=") {
+ continue
+ }
+ lines = append(lines, line)
+ }
+ const unit = "plan14-p3-retry.service"
+ path := filepath.Join("/run/systemd/system", unit)
+ file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+ content := strings.Join(lines, "\n") + fmt.Sprintf("\nType=oneshot\nEnvironment=PLAN14_RETRY_HARDENED=1\nExecStart=%s -test.run=^TestPlan14RootRetryHardenedController$ -test.v\nTimeoutStartSec=60\nIPAddressDeny=any\n", runner)
+ if _, err = file.WriteString(content); err != nil {
+ t.Fatal(err)
+ }
+ file.Close()
+ t.Cleanup(func() {
+ exec.Command("systemctl", "stop", unit).Run()
+ os.Remove(path)
+ exec.Command("systemctl", "daemon-reload").Run()
+ })
+ for _, args := range [][]string{{"daemon-reload"}, {"start", unit}} {
+ if out, err := exec.Command("systemctl", args...).CombinedOutput(); err != nil {
+ logs, _ := exec.Command("journalctl", "-u", unit, "--no-pager").CombinedOutput()
+ t.Fatal(err, string(out), string(logs))
+ }
+ }
+ s, err := rootstate.Open(rootRetryProjection, false, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer s.Close()
+ raw, err = s.Read("h.example.com.json")
+ var report rootRetryStatusRecord
+ if err != nil || json.Unmarshal(raw, &report) != nil || report.HeldBook != id || report.HeldEpoch == 0 {
+ t.Fatal("hardened controller failed delivery", string(raw), err)
+ }
+}
diff --git a/internal/host/rootupdates_legacy_linux_test.go b/internal/host/rootupdates_legacy_linux_test.go
new file mode 100644
index 000000000..ea8dc45ca
--- /dev/null
+++ b/internal/host/rootupdates_legacy_linux_test.go
@@ -0,0 +1,48 @@
+package host
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/rollout"
+ "golang.org/x/sys/unix"
+)
+
+func TestPlan14RootFailureLegacySpecialFiles(t *testing.T) {
+ for _, kind := range []string{"fifo", "symlink", "regular", "absent"} {
+ t.Run(kind, func(t *testing.T) {
+ dir := t.TempDir()
+ path := filepath.Join(dir, rollout.FailureFile)
+ switch kind {
+ case "fifo":
+ if err := unix.Mkfifo(path, 0600); err != nil {
+ t.Fatal(err)
+ }
+ case "symlink":
+ target := filepath.Join(dir, "target")
+ if err := os.WriteFile(target, []byte("legacy"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Symlink(target, path); err != nil {
+ t.Fatal(err)
+ }
+ case "regular":
+ if err := os.WriteFile(path, []byte("legacy"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ }
+ done := make(chan error, 1)
+ go func() { _, err := rootFailureLegacy(dir); done <- err }()
+ select {
+ case err := <-done:
+ if (err == nil) != (kind == "regular" || kind == "absent") {
+ t.Fatal("unsafe legacy reader", kind, err)
+ }
+ case <-time.After(time.Second):
+ t.Fatal("legacy file blocked root")
+ }
+ })
+ }
+}
diff --git a/internal/host/rootupdates_opener_root_linux_test.go b/internal/host/rootupdates_opener_root_linux_test.go
new file mode 100644
index 000000000..24fdb2be7
--- /dev/null
+++ b/internal/host/rootupdates_opener_root_linux_test.go
@@ -0,0 +1,143 @@
+//go:build linux && rootrunner
+
+package host
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/hostclaim"
+ "github.com/carlosframework/platform/internal/hostguard"
+ "github.com/carlosframework/platform/internal/provision"
+ "github.com/carlosframework/platform/internal/release"
+ "github.com/carlosframework/platform/internal/rollout"
+ "github.com/carlosframework/platform/internal/rootparticipation"
+ "github.com/carlosframework/platform/internal/rootstate"
+)
+
+func TestPlan14RootFailureProductionOpener(t *testing.T) {
+ if os.Geteuid() != 0 {
+ t.Fatal("root required")
+ }
+ for _, path := range []string{"/var/lib/carlos-claims", "/var/lib/carlos-release", rootRetryProjection} {
+ if err := os.Mkdir(path, 0755); err != nil {
+ t.Fatal("existing fixture", path, err)
+ }
+ path := path
+ t.Cleanup(func() { os.RemoveAll(path) })
+ }
+ state, err := rootstate.Open(rootparticipation.Namespace, true, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ c := rootparticipation.Census{Schema: 1, Box: "box", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}
+ raw, _ := json.Marshal(c)
+ if err = state.Write("census.json", raw); err != nil {
+ t.Fatal(err)
+ }
+ state.Close()
+ failures, err := rootstate.Open(rollout.RootFailuresDir, true, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer failures.Close()
+ book, err := rollout.InitializeRootFailures(failures, rootUpdatesMigration(c), nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ rec := provision.Record{Schema: 1, Host: "h.example.com", Account: "bab", App: "km", Channel: "canary/x", Pin: "v1", CreatedAt: time.Now().UTC()}
+ if _, err = book.Record(rollout.FailureRecord{Account: rec.Account, App: rec.App, Host: rec.Host, Build: "v1", HeldDown: []string{rec.Host}}); err != nil {
+ t.Fatal(err)
+ }
+ inc, _ := hostclaim.Incarnation(rec.Account, rec.App, rec.Host, rec.CreatedAt)
+ fences, err := rootstate.Open("/var/lib/carlos-claims/authority/fences", true, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer fences.Close()
+ in := hostguard.RootInstallation{Host: rec.Host, Account: rec.Account, App: rec.App, Record: provision.RecordKey(rec.Account, rec.App, rec.Host), Incarnation: inc, Stamp: 1, Box: c.Box, Enrollment: c.Enrollment, Migration: c.Migration, InventoryRevision: 1}
+ if err = hostguard.NewRootAuthority(fences).RetainMigrationDeny(t.Context(), in, 1); err != nil {
+ t.Fatal(err)
+ }
+ store := release.NewDirStore(t.TempDir())
+ plan14PutRecord(t, store, rec)
+ claim := hostclaim.Claim{V: 2, Host: rec.Host, Gen: 1, Holder: &hostclaim.Holder{Family: "instance", Key: in.Record, Account: rec.Account, App: rec.App, Incarnation: inc, CreatedAt: rec.CreatedAt, Location: "instances/" + rec.Host, Stamp: 1, Session: 1, WriterBox: c.Box}, Op: hostclaim.Op{Kind: hostclaim.KindServing, Exec: c.Box}}
+ if err = claim.Validate(); err != nil {
+ t.Fatal(err)
+ }
+ key, _ := hostclaim.Key(rec.Host)
+ raw, _ = json.Marshal(claim)
+ if _, err = store.Put(t.Context(), key, raw, ""); err != nil {
+ t.Fatal(err)
+ }
+ seed, public, _ := release.GenerateKey()
+ priv, _ := release.ParseSigningKey(seed, "test")
+ pub, _ := release.ParsePublicKey(public, "test")
+ m := release.Manifest{Schema: 1, App: rec.App, Version: "v1", Kind: "binary", Artifacts: map[string]release.Artifact{runtimePlatform(): {}}}
+ if err = release.Ship(t.Context(), store, rec.Account, m, map[string][]byte{runtimePlatform(): []byte("v1")}); err != nil {
+ t.Fatal(err)
+ }
+ if err = release.Promote(t.Context(), store, priv, rec.Account, rec.App, rec.Channel, "v1"); err != nil {
+ t.Fatal(err)
+ }
+ data, opt := t.TempDir(), t.TempDir()
+ if err = os.Mkdir(filepath.Join(data, rollout.FailureFile), 0700); err != nil {
+ t.Fatal(err)
+ }
+ rt := provision.RouteFor(rec, data, "", false, nil)
+ rt.Account, rt.App, rt.Channel, rt.GoodVersion = "bad", "foreign", "forged", "forged"
+ reg := openReg(t, rt)
+ if err = release.Pass(t.Context(), reg, store, pub, opt, nil, updatesPassOption(data, true)); err != nil {
+ t.Fatal(err)
+ }
+ if v, _ := release.LiveVersion(opt, rec.Host); v != "v1" {
+ t.Fatal("sticky or legacy state held production adoption", v)
+ }
+ opened, err := productionRootUpdates(data)(t.Context(), store, pub)
+ if err != nil || opened == nil {
+ t.Fatal(err)
+ }
+ resolved, err := opened.Resolve(t.Context(), rec.Host)
+ if err != nil || resolved.Route.Account != rec.Account || resolved.Route.App != rec.App || resolved.Failed("v1") {
+ t.Fatal("canonical activation policy", resolved, err)
+ }
+ opened.Close()
+ wire, err := release.SignHostRetry(release.RootSigner(priv), rec.Account, rec.App, rec.Host, "v1", book.ID(), book.PublishedEpoch(rec.Account, rec.App, "v1", rec.Host), time.Now())
+ if err != nil {
+ t.Fatal(err)
+ }
+ retries := release.ScopedRestartGeneration{Schema: 1, Requests: []release.RestartRequest{{ID: "retry", RequestedAt: time.Now().UTC().Format(time.RFC3339), Scope: release.RestartScope{Kind: release.RestartScopeRetry, Value: rec.Host, Signed: wire}}}}
+ raw, _ = json.Marshal(retries)
+ if _, err = store.Put(t.Context(), release.HostRetryKey(rec.Account, rec.App), raw, ""); err != nil {
+ t.Fatal(err)
+ }
+ opened, err = productionRootUpdates(data)(t.Context(), store, pub)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err = opened.Resolve(t.Context(), rec.Host); err != nil {
+ t.Fatal(err)
+ }
+ opened.Close()
+ book, err = rollout.OpenRootFailures(failures, rootUpdatesMigration(c))
+ if err != nil || book.HeldDown(rec.Account, rec.App, "v1", rec.Host) {
+ t.Fatal("production retry not settled", err)
+ }
+ _, etag, _ := store.Get(t.Context(), key)
+ claim.Op.Exec = "foreign"
+ raw, _ = json.Marshal(claim)
+ if _, err = store.Put(t.Context(), key, raw, etag); err != nil {
+ t.Fatal(err)
+ }
+ opened, err = productionRootUpdates(data)(t.Context(), store, pub)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer opened.Close()
+ if _, err = opened.Resolve(t.Context(), rec.Host); err == nil {
+ t.Fatal("foreign controller authority accepted")
+ }
+}
diff --git a/internal/host/rootupdates_runtime.go b/internal/host/rootupdates_runtime.go
index 8afd0758c..b335f2509 100644
--- a/internal/host/rootupdates_runtime.go
+++ b/internal/host/rootupdates_runtime.go
@@ -86,7 +86,11 @@ func (p rootUpdateSource) beginRuntime(saved *rootUpdateRecord, save func() erro
if err := p.book.End(rt.Host, attempt); err != nil {
return err
}
- return errors.Join(readinessErr, p.publishRetry(*saved, version))
+ deliveryErr := p.publishRetry(*saved, version)
+ if !p.enforceFailures {
+ deliveryErr = nil
+ }
+ return errors.Join(readinessErr, deliveryErr)
}, nil
}
diff --git a/internal/host/rootupdates_runtime_test.go b/internal/host/rootupdates_runtime_test.go
index eaed57b0f..5f0d17679 100644
--- a/internal/host/rootupdates_runtime_test.go
+++ b/internal/host/rootupdates_runtime_test.go
@@ -107,3 +107,36 @@ func TestPlan14RootRetryProjection(t *testing.T) {
t.Fatal("stale projection cleared new failure")
}
}
+
+func TestPlan14RootFailureDeliveryCannotHoldActivation(t *testing.T) {
+ for _, ready := range []bool{false, true} {
+ t.Run(map[bool]string{false: "unready", true: "ready"}[ready], func(t *testing.T) {
+ book, err := rollout.InitializeRootFailures(testRootFailureStore(t), "box/en/1", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ saved := rootUpdateRecord{Route: registry.Route{Host: "h.example.com", Account: "bab", App: "km"}, Installation: "install"}
+ delivered, verified := 0, 0
+ p := rootUpdateSource{book: book, publish: func(string, rootRetryStatusRecord) error { delivered++; return errors.New("delivery unavailable") }, ready: func(registry.Route) error {
+ if !ready {
+ return errors.New("unready")
+ }
+ return nil
+ }}
+ finish, err := p.beginRuntime(&saved, func() error { return nil }, "v1", "digest", "attempt")
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = finish("invocation", nil, func() error { verified++; return nil })
+ if (err == nil) != ready || delivered != 1 {
+ t.Fatal("delivery became launch authority or readiness was waived", err, delivered)
+ }
+ if ready && (verified != 1 || saved.Served["v1"].Invocation != "invocation") {
+ t.Fatal("settlement lost")
+ }
+ if !ready && !book.HeldDown("bab", "km", "v1", "h.example.com") {
+ t.Fatal("failure attribution lost")
+ }
+ })
+ }
+}
diff --git a/internal/provision/plan14_input_inventory_test.go b/internal/provision/plan14_input_inventory_test.go
index a0f1ebb42..360caca2f 100644
--- a/internal/provision/plan14_input_inventory_test.go
+++ b/internal/provision/plan14_input_inventory_test.go
@@ -35,7 +35,6 @@ var inputFamilies = map[string]inputFamily{
"E1": {"E", "TestPlan14RootHistoryProductionPass", "TestPlan14RootHistoryLegacyTampering", "TestPlan14RootHistoryLegacyTampering", "TestPlan14HistoryRetirementProcessCrash", "TestPlan14RootHistoryProductionPass"},
"E2": {"E", "TestPlan14GenerationPrivateJournalRootRunner", "TestPlan14GenerationPayloadPhysicalRootRunner", "TestPlan14GenerationDeletionCannotUnpin", "TestPlan14GenerationCompletenessDurability", "TestPlan14GenerationInstalledUnitRootRunner"},
"E3": {"E", "TestPlan14SwapFailureHostileRootRunner", "TestPlan14SwapFailureSurvivesLegacyTampering", "TestPlan14SwapFailureSurvivesLegacyTampering", "TestPlan14SwapFailureHostileRootRunner", "TestPlan14SwapFailureVerifiedRetry"},
- "E4": {"E", "", "", "", "", ""},
"F": {"F", "TestPlan14RootFloorCrashOrderingRootRunner", "TestPlan14FloorRiseWithoutPlatformUpdateRootRunner", "TestPlan14RootFloorPublicDenySurvivesPrivateLoss", "TestPlan14RootFloorSyscallCrashOrdering", "TestPlan14RootFloorBootSelectors"},
"G": {"G", "TestPlan14RootMigrationHistoricEvidence", "TestPlan14RootMigrationHistoricRootRunner", "TestPlan14RootMigrationHistoricRootRunner", "TestPlan14RootMigrationCrashResume", "TestPlan14RootMigrationBootstrapRootRunner"},
}
@@ -44,7 +43,7 @@ func inputFamilyFor(e classifiedRootRead) ([]string, error) {
var names []string
seen := map[string]bool{}
for _, owner := range strings.Split(e.Task, ",") {
- if owner == "J1" || owner == "J2" || owner == "J3" || owner == "J4" {
+ if owner == "J1" || owner == "J2" || owner == "J3" || owner == "J4" || owner == "E4" {
continue
}
family, ok := inputFamilies[owner]
@@ -378,8 +377,28 @@ func checkInputExemptions(inv rootReadInventory, callers map[string]string, exem
return failures
}
+func inputActivationEnabled(e classifiedRootRead) bool {
+ if inputExemptions[e.File+":"+e.Unit].Ruling != "" {
+ return true
+ }
+ for _, prefix := range []string{
+ "internal/hostrelocate/", "internal/hostclaim/relocat",
+ "internal/host/relocation", "internal/activator/worker",
+ } {
+ if strings.HasPrefix(e.File, prefix) {
+ return false
+ }
+ }
+ for _, owner := range strings.Split(e.Task, ",") {
+ if owner != "E4" && owner != "J1" && owner != "J2" && owner != "J3" && owner != "J4" {
+ return true
+ }
+ }
+ return false
+}
+
func checkInputRow(e classifiedRootRead, callers map[string]string, tests map[string]bool) []string {
- if e.Class != "authority" {
+ if e.Class != "authority" || !inputActivationEnabled(e) {
return nil
}
key := e.File + ":" + e.Unit
@@ -414,16 +433,36 @@ func TestPlan14RootInputInventoryCoverage(t *testing.T) {
t.Fatal(err)
}
failures := checkInputExemptions(inv, callers, inputExemptions, inputTrackEComplete)
+ var report []string
for key, e := range inv.Entries {
if inv.Removed[key] != "" {
continue
}
failures = append(failures, checkInputRow(e, callers, tests)...)
if e.Class == "authority" {
- family, _ := inputFamilyFor(e)
- t.Logf("J4\t%s\t%s\t%s\t%s\t%s", e.File, e.Unit, strings.Join(family, "+"), callers[e.File+":"+e.Unit], e.Basis)
+ family, familyErr := inputFamilyFor(e)
+ if familyErr != nil {
+ family = []string{e.Task}
+ }
+ mode := "enabled"
+ if !inputActivationEnabled(e) {
+ mode = "report-only"
+ }
+ report = append(report, fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s", mode, e.File, e.Unit, strings.Join(family, "+"), callers[e.File+":"+e.Unit], e.Basis))
}
}
+ sort.Strings(report)
+ for _, row := range report {
+ t.Log("J4\t" + row)
+ }
+ path := os.Getenv("PLAN14_J4_REPORT")
+ if path == "" {
+ path = filepath.Join(t.TempDir(), "plan14-root-input-report.tsv")
+ }
+ if err := os.WriteFile(path, []byte("scope\tfile\tunit\tfamily\tcaller\tbasis\n"+strings.Join(report, "\n")+"\n"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("J4 report: %s", path)
sort.Strings(failures)
last := ""
for _, failure := range failures {
@@ -547,3 +586,35 @@ func TestPlan14RootInputExemptionGraphMutation(t *testing.T) {
t.Fatal("production relocation wiring escaped the exemption")
}
}
+
+func TestPlan14ActivationInventoryScope(t *testing.T) {
+ e := classifiedRootRead{rootReadEntry: rootReadEntry{File: "root.go", Unit: "effect"}, Class: "authority", Task: "E4,J4"}
+ if inputActivationEnabled(e) || len(checkInputRow(e, nil, nil)) != 0 {
+ t.Fatal("dropped E4 slots enforced")
+ }
+ for _, owner := range []string{"A", "B", "B2", "B3", "B4", "C", "D", "E1", "E2", "E3", "F", "G"} {
+ e.Task = owner + ",E4,J4"
+ if !inputActivationEnabled(e) || len(checkInputRow(e, nil, nil)) == 0 {
+ t.Fatal("enabled mixed family waived", owner)
+ }
+ f := inputFamilies[owner]
+ for _, absent := range []string{f.Hostile, f.Rename, f.Erasure, f.Retry, f.Positive} {
+ tests := map[string]bool{}
+ for _, name := range []string{f.Hostile, f.Rename, f.Erasure, f.Retry, f.Positive} {
+ tests[name] = true
+ }
+ delete(tests, absent)
+ if len(checkInputRow(e, map[string]string{"root.go:effect": "main.go:main"}, tests)) == 0 {
+ t.Fatal("missing enabled fixture accepted", owner, absent)
+ }
+ }
+ }
+ e.File, e.Unit, e.Task = "internal/hostrelocate/worker.go", "Worker.disabled", "unknown"
+ if inputActivationEnabled(e) || len(checkInputRow(e, nil, nil)) != 0 {
+ t.Fatal("frozen family enforced")
+ }
+ e.Unit, e.Task = "Worker.Start", "C,D"
+ if !inputActivationEnabled(e) || len(checkInputRow(e, nil, nil)) == 0 {
+ t.Fatal("exemption escaped scope")
+ }
+}
diff --git a/internal/provision/plan14_p3_census_test.go b/internal/provision/plan14_p3_census_test.go
index 55cd3bc40..273fba55e 100644
--- a/internal/provision/plan14_p3_census_test.go
+++ b/internal/provision/plan14_p3_census_test.go
@@ -43,7 +43,7 @@ func plan14P3WriteCensusInventory() []censusEntry {
censusEntry{"internal/host/proofnamed.go", "proofBindingSource.record", "key-site", "key:RecordKey", "97f1501b9543e36c6a231170425c5c79c7c22489500aadf119e064abc1abde93"},
censusEntry{"internal/host/rootinventory.go", "ClaimRootHTTP.commitRootInventoryPending", "key-site", "key:RecordKey", "b83ba979dde73b56fbf78b6200edbc5374a3df5bea8150ad26093e64611b69bd"},
censusEntry{"internal/host/rootgenerations_launch.go", "rootGenerationSource.resolve", "key-site", "key:RecordKey", "578d6a9c37683852391c7ddfc612bb7f7d750b9e4316707daac98dfc5dc2fb88"},
- censusEntry{"internal/host/rootupdates.go", "rootUpdateSource.resolve", "key-site", "key:RecordKey", "ef717e41d930053e3eff1301d87335666589d38e420cf8075b91226123bd3ba7"},
+ censusEntry{"internal/host/rootupdates.go", "rootUpdateSource.resolve", "key-site", "key:RecordKey", "e2247ec55dae1342cccc71cb84f8dd8dc901968ba8453bbc2af572c78c8505d3"},
censusEntry{"internal/host/rootmovesfloor.go", "rootMovesFloors.refresh", "key-site", "key:writeRecord", "cb70a1eefbb4f8f243090270f23141c9c9a773ee278af002c79a6c3ef6b7c3b7"},
censusEntry{"internal/host/rootmovesfloor.go", "rootMovesFloors.persist", "key-site", "key:writeRecord", "2c1e0843c5865a31c0fab011d7d4825963bbb5a301727f37f3b1b6c3519eba5e"},
censusEntry{"internal/host/rootmovesfloor.go", "rootMovesFloors.writeRecord", "key-site", "key:writeRecord", "a8a51bdd73f8ab1447edf2241d19a41ba1bcb0084ef045d370e5282aca7e9e66"},
diff --git a/internal/provision/testdata/plan14-root-reads-p3.tsv b/internal/provision/testdata/plan14-root-reads-p3.tsv
index a9b44adc2..f4b13f1a8 100644
--- a/internal/provision/testdata/plan14-root-reads-p3.tsv
+++ b/internal/provision/testdata/plan14-root-reads-p3.tsv
@@ -201,7 +201,7 @@ internal/hostguard/rootmigration.go RootAuthority.CompleteMigrationInventory aut
@coverage internal/activator 95ea5c811cee8a679170f708c30b4446021740977474551c1d22bf8afaacf9be
@coverage internal/console 6f4998c39e6d66cd73ea98500000cbecac251dd21dafd58f51a85c2291e0c533
@coverage internal/edgesfile 7b48368150a427e4620f1c9ded49942b688c9e6c9cd983b7da43d3f2c0810b72
-@coverage internal/host 280444e2c3174a0d9bb07499f47d79a590e5736cb54f7cc7aa7ba679d4af5855
+@coverage internal/host 4d660a8d30b523f1ee4d504d45b48ac8ab791fe8e7483f43da57af6502198bbf
@coverage internal/host/units bf6ae516012701e0ca41908167e4efd60dd3c2c24e9af1555dec077e8be44461
@coverage internal/hostbox 1aeb3bed428b63552f65f92bd7fa40bfbd818e34ebd1abfa169f1ea79a7a5d81
@coverage internal/hostfloor 696e5ea4fa37b924b68e8e262235b99298ee19342d875213ba8209e5287dd3c6
@@ -217,9 +217,9 @@ internal/host/rootgenerations_launch.go productionRootGenerations authority E2,E
internal/host/rootgenerations_launch.go rootGenerationSource.resolve authority E2 E20: read the atomic root installation under the existing host exclusion without recursive lock acquisition; retain fresh claim, canonical record, signed artifact and final reservation checks. Fixtures: TestPlan14GenerationLaunchDoesNotReenterRootLock, TestPlan14GenerationLaunchRefusals, TestPlan14RootDispatchRejectsRegistryMutation. PROPOSED E47: root-owned immutable unit/shape binding, reserved launch placement and exact invocation stop; retained generation binding carries the canonical unit. Default unit paths remain unchanged. Production named-route wiring and unit/env/timer retirement are not complete.;rev3=PROPOSED-p3;run=8;identity=root claim launch uid 0;basis=captures persistent executable and tree proofs only after canonical installation and signed artifact verification under exclusion file:Open@2501 578d6a9c37683852391c7ddfc612bb7f7d750b9e4316707daac98dfc5dc2fb88
internal/host/rootgenerations_launch.go type rootGenerationSource authority E2,E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=binds production runtime authority and persistent file capture to the root launch source ebe058661c48feb225bfb03a6d0b93f116639f06e50a81c51fded1da744043e4
internal/host/rootupdates.go initializeMigrationFailures authority G,E3 rev3=PROPOSED-p3;run=8;identity=offline journal-migrate uid 0;basis=requires complete root census before creating migration-bound root failure book; missing legacy history supplies no known refusal file:Open@254 5f7f652988f3def733e1b525260a52fac3568242960468e5457975ced1cd41d8
-internal/host/rootupdates.go productionRootUpdates authority E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=opens only census-bound root failure state and imports negative legacy evidence; binds root readiness and retry projection producers file:Open@1243,file:Open@765,file:Read@1566,whole:Route@1879,whole:Route@2200 d56da3910afe6909f05c057267caa6b34217ed26d1d1a445d6d9ce72a429d53c
-internal/host/rootupdates.go rootFailureLegacy authority E3 rev3=PROPOSED-p3;run=8;identity=carlos adopt and offline migration uid 0;basis=bounded legacy byte read feeds only failure tuples and unresolved intents to the root deny importer; no legacy book opener or retry grant file:Open@65,file:ReadAll@239 313d92c63ac120966bbe6ad50de446c7a92e2d69d5e18b1b300fd724b3098f78
-internal/host/rootupdates.go rootUpdateSource.resolve authority E3,J1,J3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=resolves canonical settings and signed retries; saves exact old legacy-book acknowledgements only after durable independent root retry file:Read@1368,registry:Account@1543,registry:Account@2428,registry:App@1581,registry:App@2473,registry:BuildState@1210,registry:GoodDemand@1176,registry:GoodDigest@1161,registry:GoodVersion@1145,registry:Host@1515,registry:Host@2393,registry:ServingVersion@1191,registry:Unit@2124,whole:Route@1521,whole:Route@1549,whole:Route@1587,whole:Route@1756,whole:Route@1763,whole:Route@2183,whole:Route@2406,whole:Route@2441,whole:Route@2486,whole:Route@3254,whole:Route@3262,whole:Route@4890,whole:Route@4897 ef717e41d930053e3eff1301d87335666589d38e420cf8075b91226123bd3ba7
+internal/host/rootupdates.go productionRootUpdates authority E3 rev3=PROPOSED-p3;run=10;identity=root claim controller and adopt uid 0;basis=keeps canonical root inputs and retry; leaves sticky failure enforcement disabled; legacy bytes only identify an optional exact retry mirror and cannot block opening or inject fresh denials file:Open@1124,file:Open@765,file:Read@1447,whole:Route@1760,whole:Route@2081 ce81dce48e37f4055efeb3fd8d3562057e504ec5cd74ccbef42c0b8fcd6f20f0
+internal/host/rootupdates.go rootFailureLegacy authority E3 rev3=PROPOSED-p3;run=10;identity=root adoption and offline migration uid 0;basis=bounded nonblocking regular-file read supplies optional exact retry mirror identity or one-time migration deny evidence; refuses symlinks and special files file:OpenFile@65,file:ReadAll@265 efae62d1b996f14baa592e39e0cc22e0f5c8cde5cd94f429498d41039f5a70e4
+internal/host/rootupdates.go rootUpdateSource.resolve authority E3,J1,J3 rev3=PROPOSED-p3;run=10;identity=root claim controller and adopt uid 0;basis=binds canonical settings and retries; gates sticky root failure veto and projection delivery errors off while failure lifecycle acceptance is incomplete file:Read@1368,registry:Account@1543,registry:Account@2428,registry:App@1581,registry:App@2473,registry:BuildState@1210,registry:GoodDemand@1176,registry:GoodDigest@1161,registry:GoodVersion@1145,registry:Host@1515,registry:Host@2393,registry:ServingVersion@1191,registry:Unit@2124,whole:Route@1521,whole:Route@1549,whole:Route@1587,whole:Route@1756,whole:Route@1763,whole:Route@2183,whole:Route@2406,whole:Route@2441,whole:Route@2486,whole:Route@3254,whole:Route@3262,whole:Route@4890,whole:Route@4897 e2247ec55dae1342cccc71cb84f8dd8dc901968ba8453bbc2af572c78c8505d3
internal/host/updatesroot.go updatesPassOption authority E3 rev3=PROPOSED-p3;run=8;identity=root adopt uid 0; unchanged agent opener uid carlos;basis=installs census-only root input provider while preserving the legacy failure-book opener for nonparticipating passes 4f66774059952bda531435d68487fa032a95b8d1c8ca33ec8b71b6639fa117e3
internal/release/adopt.go PassFilter authority B3,E3,J1,E1,E2 Signed named-unit recovery is independent of release and health history. Root rechecks canonical source, unit, process, signed target and trusted live pointer; captures attempt authority before publication; verifies readiness and resumes without repeated effects. Fixtures: TestPlan14SignedActiveFixIgnoresHistory, TestPlan14SignedActiveFixAuthority, TestPlan14SignedFixSupersedesOlderAttempt, TestPlan14ActiveRecoveryProcessCrash, TestPlan14SignedRestartWithoutHealthHistory, TestPlan14CapturedRollbackWithoutHealthHistory, TestPlan14SignedRestartRequiresTrustedLiveLink. E7 applies merge ruling 4: unbound no-census routes use the legacy pass and restart; ordinary candidates make no binding store call. Fixtures: TestR2UnguardedERecoveryUpdate, TestR2UnguardedERecoveryIncidentRecipe, TestPlan14ERecoveryBindingRefusalKeepsPromotedFix. E19: both destructive readers use the root reservation snapshot under existing release exclusion. Exact artifact and containing-tree identities are retained; missing completeness refuses destruction. Production installation/launch/kernel completeness and verified retirement remain unfinished. Fixtures: TestPlan14GenerationDeletionCannotUnpin, TestPlan14GenerationSnapshotCompleteness, TestPlan14GenerationSnapshotPermitsUnreservedRestage, TestPlan14GenerationSnapshotPinsContainingTree, TestPlan14GenerationSnapshotEmptyIsUnknown, TestPlan14GenerationConsumerMode.;rev3=PROPOSED-p3;run=8;identity=carlos adopt uid 0; legacy agent pass uid carlos;basis=resolves protected route inputs before filters, recovery and dispatch on census boxes and bypasses service-owned failure opener only there registry:App@26795,registry:App@29252,registry:App@29744,registry:App@29981,registry:App@31388,registry:App@6590,registry:App@9615,registry:App@9756,registry:Hibernate@29627,registry:Host@1061,registry:Host@1426,registry:Host@29797,registry:Host@30713,registry:Host@31441,registry:Host@3263,registry:Host@3330,registry:Host@3426,registry:Host@3461,registry:UpdateSettings@9493,whole:Route@1046,whole:Route@1164,whole:Route@1188,whole:Route@1461,whole:Route@1622,whole:Route@26790,whole:Route@2726,whole:Route@2768,whole:Route@2877,whole:Route@2901,whole:Route@29247,whole:Route@2932,whole:Route@29505,whole:Route@29527,whole:Route@29574,whole:Route@29592,whole:Route@29602,whole:Route@29645,whole:Route@29666,whole:Route@29679,whole:Route@29739,whole:Route@29765,whole:Route@29839,whole:Route@29878,whole:Route@2989,whole:Route@29894,whole:Route@29922,whole:Route@29976,whole:Route@30002,whole:Route@3075,whole:Route@30755,whole:Route@30809,whole:Route@30825,whole:Route@3097,whole:Route@31332,whole:Route@31383,whole:Route@31409,whole:Route@31483,whole:Route@31518,whole:Route@31534,whole:Route@3197,whole:Route@4280,whole:Route@4927,whole:Route@6512,whole:Route@6534,whole:Route@6585,whole:Route@6611,whole:Route@761,whole:Route@803,whole:Route@9444,whole:Route@950,whole:Route@9558,whole:Route@9603,whole:Route@9751,whole:Route@988 270763804170539d06afd58252d86e1a8ff692ab4d9355be3a2127e9efc9d4a2
internal/release/adopt.go adoptHost authority B3,E1,E2,E3,E4,J1 E16: root first-registration creation evidence under release exclusion; exact installation identity is required for first-program adoption and is consumed before an attempt. Installed-program history comes from the verified claim launch. Fixtures: TestPlan14FreshInstallRegistration, TestPlan14FreshInstallProgramGate, TestPlan14FreshInstallCanonicalIdentity, TestPlan14FreshInstallLaunchObservation, TestPlan14InstalledLaunchProgramEvidence, TestPlan14FreshInstallProcessCrash, TestPlan14FreshInstallVisibleSync, TestClaimControlUnitInstallers. E22: root generation pins guard existing-tree artifact and manifest writes in adoption and moves-held staging/publication. Held snapshot is read under release exclusion before the leaf held-stage lock; failures remain restrictive for writes. Claims-off source returns no root pins. Fresh wholly absent version trees retain signed staging. Fixtures: TestPlan14GenerationSiblingWrites, TestPlan14GenerationVerifiedSiblingWrites, TestPlan14GenerationHeldStage (production source assembly, both flag values without census, lock order, pin/error/fresh cases). Production completeness remains unavailable; no new completeness grant.;rev3=PROPOSED-p3;run=8;identity=carlos adopt uid 0; legacy agent pass uid carlos;basis=applies root settings to canonically resolved routes including named units while legacy settings eligibility remains unchanged registry:App@10936,registry:App@11096,registry:App@12125,registry:App@13051,registry:App@14633,registry:App@19792,registry:App@25268,registry:App@3144,registry:App@33027,registry:App@6758,registry:App@8059,registry:App@8253,registry:Hibernate@33888,registry:Host@10816,registry:Host@10849,registry:Host@10949,registry:Host@11109,registry:Host@11211,registry:Host@11243,registry:Host@11308,registry:Host@11758,registry:Host@11891,registry:Host@12338,registry:Host@12447,registry:Host@12649,registry:Host@13141,registry:Host@13927,registry:Host@14405,registry:Host@14619,registry:Host@14961,registry:Host@15551,registry:Host@19282,registry:Host@19390,registry:Host@19760,registry:Host@20580,registry:Host@20738,registry:Host@2105,registry:Host@21200,registry:Host@21252,registry:Host@21399,registry:Host@21486,registry:Host@22592,registry:Host@22836,registry:Host@2294,registry:Host@23496,registry:Host@2375,registry:Host@23881,registry:Host@24022,registry:Host@25236,registry:Host@25363,registry:Host@26705,registry:Host@27355,registry:Host@30588,registry:Host@31206,registry:Host@31531,registry:Host@3157,registry:Host@31605,registry:Host@31852,registry:Host@32003,registry:Host@33199,registry:Host@33366,registry:Host@33473,registry:Host@33701,registry:Host@33844,registry:Host@34589,registry:Host@34715,registry:Host@34803,registry:Host@4469,registry:Host@4583,registry:Host@5546,registry:Host@5651,registry:Host@6870,registry:Host@6918,registry:Host@7035,registry:Host@7067,registry:Host@7945,registry:Host@8113,registry:Host@8239,registry:Host@8358,registry:Host@8836,registry:Host@9223,registry:Host@9449,registry:Host@9545,registry:Host@9636,registry:Kind@554,registry:Unit@31751,registry:Unit@579,whole:Route@10073,whole:Route@10931,whole:Route@11091,whole:Route@12115,whole:Route@13041,whole:Route@14601,whole:Route@15166,whole:Route@15527,whole:Route@16750,whole:Route@17053,whole:Route@17209,whole:Route@19539,whole:Route@19683,whole:Route@19782,whole:Route@2206,whole:Route@22321,whole:Route@22720,whole:Route@25258,whole:Route@30159,whole:Route@3139,whole:Route@33175,whole:Route@3595,whole:Route@4347,whole:Route@6753 a9672d71a3d70ab0bcc2b257daebb974da0f0bf67731303f51221b216f06eff6
@@ -268,7 +268,7 @@ internal/host/rootupdates_runtime.go beginRootGenerationUpdate authority E2,E3 r
internal/host/rootupdates_runtime.go publishRootRetry authority E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=writes root-owned public retry status only from the independently verified root book file:Open@84 e3c680d5a7d3c423dc183319bb3d0518bbea6cdc752c7bd6b540d57f884b414e
internal/host/rootupdates_runtime.go rootRetryStatus hint E3 rev3=PROPOSED-p3;identity=carlos agent status probe uid carlos;basis=agent reads root-owned public retry status; an exact legacy-book acknowledgement may clear only the corresponding local mirror, never root authority file:Lstat@89,file:Open@183,file:Read@721 7db6b16062bd095634a03c58cfdcd3dde2d49c990a34fe37d260d0908a16aa5f
internal/host/rootupdates_runtime.go rootUpdateReady authority E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=probes the canonical deployment endpoint before root records runtime served/request success whole:Route@95 fe93218ee43c1346b148e4c9f6f55731d8865acde4f72ec85f60c5732a6a7d76
-internal/host/rootupdates_runtime.go rootUpdateSource.beginRuntime authority E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=persists a root intent before effects; independently records failures or readiness plus exact process revalidation and request completion file:Exec@1480,registry:Account@1228,registry:Account@1439,registry:Account@1882,registry:Account@533,registry:App@1245,registry:App@1456,registry:App@1894,registry:App@550,registry:Host@1069,registry:Host@1259,registry:Host@1328,registry:Host@1405,registry:Host@1594,registry:Host@1911,registry:Host@2334,registry:Host@500,whole:Route@180,whole:Route@891 aca5ab8b339bfd54edb2a27d0dc058cc696ff74e669bb17376d426eea0e63eb4
+internal/host/rootupdates_runtime.go rootUpdateSource.beginRuntime authority E3 rev3=PROPOSED-p3;run=10;identity=root claim controller uid 0;basis=retains durable attribution readiness and exact process settlement; diagnostic retry delivery cannot hold a successful launch while sticky enforcement is disabled file:Exec@1480,registry:Account@1228,registry:Account@1439,registry:Account@1882,registry:Account@533,registry:App@1245,registry:App@1456,registry:App@1894,registry:App@550,registry:Host@1069,registry:Host@1259,registry:Host@1328,registry:Host@1405,registry:Host@1594,registry:Host@1911,registry:Host@2334,registry:Host@500,whole:Route@180,whole:Route@891 ea1cb8473da2d46ca21be5c3cc5c1a8108d88b7934427574ea4af55d5d0bfc67
internal/host/rootupdates_runtime.go rootUpdateSource.publishRetry authority E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=projects the root book identity and epoch and includes a mirror acknowledgement only after the root denial is durably cleared registry:Account@284,registry:Account@378,registry:App@305,registry:App@399,registry:Host@333,registry:Host@427,registry:Host@490,whole:Route@290,whole:Route@311,whole:Route@339,whole:Route@384,whole:Route@405,whole:Route@433,whole:Route@496 d18d5c78c905754df748c0bf887a7170f6378a6da76df196af7508f28372be28
internal/host/rootupdates_runtime.go rootUpdateSource.retryStatus authority E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=derives retry identity and restrictions from root-held failures and epoch-bound served observations registry:Account@154,registry:Account@380,registry:Account@518,registry:Account@586,registry:Account@644,registry:Account@795,registry:Account@888,registry:App@171,registry:App@403,registry:App@530,registry:App@598,registry:App@656,registry:App@807,registry:App@900,registry:Host@351,registry:Host@544,registry:Host@612,registry:Host@670,registry:Host@821,registry:Host@914,whole:Route@112 a3eafae0a95559cfb0c7677215d89dbf353e44150453f252fed0e56b575e1f11
internal/hostbox/coldhandover.go ColdHandover.Run authority C,J2,J4 rev3=PROPOSED-p3;identity=root claim retirement or offline migration uid 0; shared helpers also run for legacy agent requests without root authority;basis=carries persistent identity into root-guarded cold retirement without treating old device numbers as filesystem identity file:Exec@938,file:Lstat@10711,file:OpenRoot@10633,file:Read@1290,registry:Addr@3565,registry:Addrs@3583,registry:Backing@3602,registry:Canary@3659,registry:ClaimIncarnation@3443,registry:ClaimIncarnation@3945,registry:ClaimOwner@11461,registry:ClaimOwner@4027,registry:ClaimOwner@4047,registry:ClaimOwner@5922,registry:ClaimOwner@7014,registry:ClaimOwner@8272,registry:ClaimOwnership@11053,registry:ClaimOwnership@3413,registry:ClaimOwnership@4391,registry:ClaimOwnership@4609,registry:ClaimOwnership@4642,registry:ClaimRowID@11351,registry:ClaimRowID@11432,registry:ClaimRowID@4065,registry:ClaimRowID@4673,registry:ClaimRowID@6973,registry:ClaimRowID@8297,registry:ClaimStamp@3488,registry:ClaimStamp@3992,registry:Host@6850,registry:Host@8234,registry:Kind@3521,registry:Name@4727,registry:Name@6865,registry:Name@8253,registry:Platform@3643,registry:Registration@4091,registry:Registration@6930,registry:Registration@8322,registry:Replica@3673,registry:Route@11053,registry:Route@4391,registry:Route@4642,registry:Route@5173,registry:Route@5922,registry:Timers@4590,registry:Timers@4609,registry:Timers@4673,registry:Timers@4727,registry:Timers@6802,registry:Timers@8109,registry:Tunnel@3623,registry:Unit@3547,whole:ClaimOwnership@11067,whole:ClaimOwnership@3417,whole:ClaimOwnership@4403,whole:ClaimOwnership@4625,whole:ClaimOwnership@4654,whole:ColdRows@11055,whole:ColdRows@12167,whole:ColdRows@12177,whole:ColdRows@4888,whole:ColdRows@4896,whole:ColdRows@4904,whole:ColdRows@4911,whole:ColdRows@5175,whole:ColdRows@5924,whole:ColdRows@6804,whole:ColdRows@8111,whole:Route@11061,whole:Route@3398,whole:Route@3758,whole:Route@4190,whole:Route@4198,whole:Route@4249,whole:Route@4257,whole:Route@4397,whole:Route@4648,whole:Route@5182,whole:Route@5931,whole:Timer@3929,whole:Timer@4203,whole:Timer@4211,whole:Timer@4262,whole:Timer@4300,whole:Timer@4596,whole:Timer@4615,whole:Timer@4679,whole:Timer@4733,whole:Timer@6811,whole:Timer@8118 57685ec5d96946512cbf1382e247e8881c564351db594d24d005264eccb03b6b
@@ -291,3 +291,9 @@ internal/host/claimstop_writers_linux.go rootInstallationCoverage authority C,G,
internal/host/claimunit.go claimUnitContent authority E1,E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=adds write permission for exactly the public retry directory only when the root settings reader has verified a complete census and prepared that directory 3082c34490fbf6f95ebace69d97161993635c9cc1fee346641fdc61cb1f2e43f
internal/host/claimunit.go readClaimUnitSettings authority C,E3,G,J4 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=retains exact no-census unit settings; a claims-enabled complete census prepares the root-owned retry directory before the hardened controller starts data-literal@123,data-literal@1708,data-literal@1793 bb6950ff1fff6355dde999d17330e9cd29b49ce051b66bfe2c8d5bc05424d6a5
internal/host/rootupdates_runtime.go prepareRootRetryUnitDirectory authority E3 rev3=PROPOSED-p3;identity=root claim/adoption controller uid 0;basis=root unit installer requires an independently opened complete census before creating the public retry directory or enabling its controller write path file:Lstat@67,file:Open@603 3be70325e77de790f369531610c5889da7af25a185fdde6062c49356ca1a9335
+internal/caps/converge.go type Ops diagnostic H3 rev3=PROPOSED-p3;run=10;identity=compile-time capability operation shape;basis=adds root authorization callback shared by converge restore and restart file:Query@316 43fdc1addb312d7a6f70b140496a0d63731d29c2f41aa8b977b6509b46758c40
+internal/caps/converge.go Converge authority E4,J4 rev3=PROPOSED-p3;run=10;identity=root adopt uid 0;basis=checks root authorization before any capability file or systemd effect; no-census legacy behavior retained 972b6fe7327fcfd33c9ad737546c6f7e1471686125aba81e7146204022029dd6
+internal/caps/converge.go Restore authority E4,J4 rev3=PROPOSED-p3;run=10;identity=root adopt uid 0;basis=checks root authorization before rollback writes or unit verbs add26a1389d77a66bbc1e35ff92196210a0958c08f3607afecdd24912583bb71
+internal/caps/converge.go Restart authority E4,J4 rev3=PROPOSED-p3;run=10;identity=root adopt uid 0;basis=checks root authorization before daemon restart e5f19c2deb81d90947a4759e787d7f2e8a04e20d52737e4078635622d5fd6536
+@coverage internal/caps b964b8931e9d42939a419c2cf3041cac32eda07edc4fbaabdc4e37f56ddd8d01
+internal/caps/rootauthority.go Ops.authorizeMutation authority E4,J4 rev3=PROPOSED-p3;run=10;identity=root adopt uid 0;basis=refuses legacy unit capability mutation on any retained census or unreadable census; root configuration stays fixed; absence preserves legacy operations file:Lstat@55 5549b2317e66dda41d73b23216bfca7b18ab1814607140794eb40124feae3618