carlos / platform Public

WIP claim Plan 14 track E checkpoint 45 (not for merge)

Review fixes R-E45-1/2/3/5 (lock order, census-first, adopt gated on census, timer hints); permitted-positive running containment; timer identity primitives.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev a84ca9799ee9d33e3b6b04f0fafca7af63f7b338 parent c295f30
41 files changed, +1357 −137
  • internal/host/agent.go +7 −4
  • internal/host/claimadopt_order_test.go +1 −1
  • internal/host/claimlaunch_execpermit.go +1 −1
  • internal/host/claimlaunch_kernel_linux.go +5 −1
  • internal/host/claimlaunch_root.go +1 −0
  • internal/host/claimstop_run.go +18 −3
  • internal/host/movesfloor.go +5 −14
  • internal/host/movesheldrelease_root.go +1 −1
  • internal/host/plan14_fix3_adopt_linux_test.go +1 −1
  • internal/host/plan14_fix4_adopt_linux_test.go +1 −1
  • internal/host/plan14_generation_containment_linux_test.go +112 −0
  • internal/host/plan14_generation_containment_root_linux_test.go +270 −0
  • internal/host/plan14_generation_kernel_linux_test.go +3 −3
  • internal/host/plan14_held_root_test.go +1 −1
  • internal/host/plan14_launch_verifier_test.go +37 −3
  • internal/host/plan14_release_lock_test.go +56 −5
  • internal/host/rootgeneration_census_linux.go +9 −4
  • internal/host/rootgeneration_containment_linux.go +200 −0
  • internal/host/rootgeneration_inventory.go +3 −1
  • internal/host/rootgeneration_kernel_linux.go +22 −1
  • internal/host/rootgenerations_launch.go +5 −1
  • internal/host/roothistory_creation.go +1 −1
  • internal/host/rootlaunchverifier.go +5 −2
  • internal/host/rootreleaselock.go +35 −1
  • internal/host/systemapply.go +59 −50
  • internal/instancectl/claimexec.go +2 −1
  • internal/instancectl/claimexec_rev3_linux_test.go +1 −1
  • internal/instanceexec/claim_generation_linux.go +62 −0
  • internal/instanceexec/claim_generation_linux_test.go +84 −0
  • internal/instanceexec/claimexec_linux.go +7 −2
  • internal/instanceexec/claimexec_rev3_linux_test.go +9 −3
  • internal/provision/plan14_e_write_census_test.go +1 −1
  • internal/provision/testdata/plan14-root-reads-e.tsv +43 −26
  • internal/registry/plan14_timer_materialization_test.go +127 −0
  • internal/registry/remote_dispatch.go +4 −0
  • internal/registry/timer_materialization.go +83 −0
  • internal/release/plan14_release_exclusion_test.go +32 −0
  • internal/release/plan14_root_generations_test.go +23 −0
  • internal/release/releaseexclusion.go +10 −1
  • internal/release/rootgenerations.go +3 −1
  • internal/rootparticipation/ledger.go +7 −1

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.012s
?   	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	309.031s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.929s
ok  	github.com/carlosframework/platform/cmd/moveprobe	93.311s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.711s
ok  	github.com/carlosframework/platform/internal/activator	251.216s
ok  	github.com/carlosframework/platform/internal/agentsock	0.019s
ok  	github.com/carlosframework/platform/internal/alerting	0.004s
ok  	github.com/carlosframework/platform/internal/analytics	0.026s
ok  	github.com/carlosframework/platform/internal/appconfig	2.523s
ok  	github.com/carlosframework/platform/internal/apperr	0.078s
ok  	github.com/carlosframework/platform/internal/applog	0.163s
ok  	github.com/carlosframework/platform/internal/awsinv	0.004s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.005s
ok  	github.com/carlosframework/platform/internal/boxid	0.629s
?   	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.012s
ok  	github.com/carlosframework/platform/internal/certwarm	0.002s
ok  	github.com/carlosframework/platform/internal/cgroup	0.751s
ok  	github.com/carlosframework/platform/internal/changelog	0.336s
ok  	github.com/carlosframework/platform/internal/clamd	0.055s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	393.753s
?   	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.582s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.009s
ok  	github.com/carlosframework/platform/internal/dbverify	0.006s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.003s
ok  	github.com/carlosframework/platform/internal/docsite	0.008s
ok  	github.com/carlosframework/platform/internal/domains	2.394s
ok  	github.com/carlosframework/platform/internal/doorkey	0.006s
ok  	github.com/carlosframework/platform/internal/economics	0.004s
ok  	github.com/carlosframework/platform/internal/edge	26.706s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.008s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.135s
ok  	github.com/carlosframework/platform/internal/gate	1.655s
ok  	github.com/carlosframework/platform/internal/genselect	0.018s
ok  	github.com/carlosframework/platform/internal/geo	0.017s
ok  	github.com/carlosframework/platform/internal/geoip	0.032s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.158s
ok  	github.com/carlosframework/platform/internal/host	147.053s
ok  	github.com/carlosframework/platform/internal/hostactors	0.381s
ok  	github.com/carlosframework/platform/internal/hostbox	2.513s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.527s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.006s
ok  	github.com/carlosframework/platform/internal/hostexec	5.127s
ok  	github.com/carlosframework/platform/internal/hostfence	0.039s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.542s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.013s
ok  	github.com/carlosframework/platform/internal/hostguard	1.001s
ok  	github.com/carlosframework/platform/internal/hostops	1.625s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.002s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.002s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.477s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.161s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.002s
ok  	github.com/carlosframework/platform/internal/infraview	0.002s
ok  	github.com/carlosframework/platform/internal/instancectl	4.171s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.743s
ok  	github.com/carlosframework/platform/internal/instancefs	0.005s
ok  	github.com/carlosframework/platform/internal/instancekey	0.003s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.024s
ok  	github.com/carlosframework/platform/internal/layout	1.559s
ok  	github.com/carlosframework/platform/internal/lease	9.842s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.024s
ok  	github.com/carlosframework/platform/internal/ledger	0.012s
ok  	github.com/carlosframework/platform/internal/mail	0.185s
ok  	github.com/carlosframework/platform/internal/maintenance	0.076s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.522s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.090s
ok  	github.com/carlosframework/platform/internal/metrics	0.005s
ok  	github.com/carlosframework/platform/internal/moves	21.213s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.816s
ok  	github.com/carlosframework/platform/internal/objstore	0.204s
ok  	github.com/carlosframework/platform/internal/ops	0.180s
ok  	github.com/carlosframework/platform/internal/park	0.234s
ok  	github.com/carlosframework/platform/internal/privatefile	0.005s
ok  	github.com/carlosframework/platform/internal/provision	18.806s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.006s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.511s
ok  	github.com/carlosframework/platform/internal/relay	7.265s
ok  	github.com/carlosframework/platform/internal/release	22.752s
ok  	github.com/carlosframework/platform/internal/rollout	3.844s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.003s
ok  	github.com/carlosframework/platform/internal/rootaws	0.008s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.009s
ok  	github.com/carlosframework/platform/internal/rootstate	0.027s
ok  	github.com/carlosframework/platform/internal/runs	0.580s
ok  	github.com/carlosframework/platform/internal/s3compat	0.006s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.494s
ok  	github.com/carlosframework/platform/internal/seeds	0.011s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	10.238s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.626s
ok  	github.com/carlosframework/platform/internal/status	0.042s
ok  	github.com/carlosframework/platform/internal/steering	0.006s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.255s
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.003s
ok  	github.com/carlosframework/platform/internal/updatewords	0.004s
ok  	github.com/carlosframework/platform/internal/usage	0.004s
ok  	github.com/carlosframework/platform/internal/version	0.005s
ok  	github.com/carlosframework/platform/internal/vet	1.573s
=== 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: 47055010 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.025s
=== RUN   TestT4StorageProbeMinio
2026/10/15 15:19:27 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DEBD0D6B670A7B, 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.023s
minio contract: PASS
diff --git a/internal/host/agent.go b/internal/host/agent.go
index c4d19a6c8..aa3263a64 100644
--- a/internal/host/agent.go
+++ b/internal/host/agent.go
@@ -191,6 +191,7 @@ import (
"github.com/carlosframework/platform/internal/provision"
"github.com/carlosframework/platform/internal/registry"
"github.com/carlosframework/platform/internal/release"
+ "github.com/carlosframework/platform/internal/rootparticipation"
"github.com/carlosframework/platform/internal/rootstate"
"github.com/carlosframework/platform/internal/status"
"github.com/carlosframework/platform/internal/version"
@@ -4284,14 +4285,16 @@ func runAdopt(args []string, buildClaims ClaimFactory) {
// runAdoptWithEnsureSystemUnits keeps the root-only unit effect injectable;
// the rest of adopt startup, including its fatal registry diagnostics, is real.
-func runAdoptWithEnsureSystemUnits(args []string, buildClaims ClaimFactory, ensureUnits func(), stores ...func() (rootstate.Store, error)) {
+func runAdoptWithEnsureSystemUnits(args []string, buildClaims ClaimFactory, ensureUnits func(), sources ...adoptRootSource) {
open := func() (rootstate.Store, error) { return rootstate.Open(rootReleasePrivate, true, false) }
- if len(stores) == 1 {
- open = stores[0]
+ probe := rootparticipation.OpenForUpdate
+ if len(sources) == 1 {
+ open = sources[0].OpenRelease
+ probe = sources[0].Census
}
err := withClaimRootRelease(context.Background(), hostwrite.Enabled(os.Getenv), open, func() error {
return runAdoptUnderRootRelease(args, buildClaims, ensureUnits)
- })
+ }, probe)
if err != nil {
log.Fatalf("adopt: %v", err)
}
diff --git a/internal/host/claimadopt_order_test.go b/internal/host/claimadopt_order_test.go
index c67cc27a7..02720c2e5 100644
--- a/internal/host/claimadopt_order_test.go
+++ b/internal/host/claimadopt_order_test.go
@@ -46,7 +46,7 @@ func TestRunAdoptDisabledClaimsArmsSystemUnitsBeforeRegistryOpenFailure(t *testi
)
}
runAdoptWithEnsureSystemUnits([]string{"-registry", os.Getenv("CARLOS_TEST_ADOPT_REGISTRY")}, nil, ensure,
- func() (rootstate.Store, error) { return &plan14ReleaseStore{}, nil })
+ noCensusAdoptSource(func() (rootstate.Store, error) { return &plan14ReleaseStore{}, nil }))
return
}
diff --git a/internal/host/claimlaunch_execpermit.go b/internal/host/claimlaunch_execpermit.go
index 098aa03ed..e9ca25e45 100644
--- a/internal/host/claimlaunch_execpermit.go
+++ b/internal/host/claimlaunch_execpermit.go
@@ -37,5 +37,5 @@ func (h *ClaimLaunchRoot) execPermit(ctx context.Context, q hostbox.RoleLaunchRe
if boot >= until {
return instancectl.ClaimExecPermit{}, execlease.ErrLost
}
- return instancectl.ClaimExecPermit{Reservation: r.ID, Inputs: r.Inputs, BootID: r.Target.BootID, SafeUntil: until}, nil
+ return instancectl.ClaimExecPermit{Reservation: r.ID, Inputs: r.Inputs, BootID: r.Target.BootID, SafeUntil: until, Containment: 1}, nil
}
diff --git a/internal/host/claimlaunch_kernel_linux.go b/internal/host/claimlaunch_kernel_linux.go
index d801220dc..4b2c9f384 100644
--- a/internal/host/claimlaunch_kernel_linux.go
+++ b/internal/host/claimlaunch_kernel_linux.go
@@ -166,7 +166,11 @@ func (k linuxClaimLaunchKernel) Observe(s instancectl.ClaimUnitStarted) (ClaimLa
if group.Mode&unix.S_IFMT != unix.S_IFDIR {
return ClaimLaunchObserved{}, hostbox.ErrChanged
}
- observed := ClaimLaunchObserved{ProcessStart: start, Group: hostbox.LaunchFile{Device: uint64(group.Dev), Inode: group.Ino}}
+ var launcher unix.Stat_t
+ if err := unix.Stat(filepath.Join(k.ProcRoot, strconv.Itoa(s.MainPID), "exe"), &launcher); err != nil {
+ return ClaimLaunchObserved{}, err
+ }
+ observed := ClaimLaunchObserved{Launcher: hostbox.LaunchFile{Device: uint64(launcher.Dev), Inode: launcher.Ino}, ProcessStart: start, Group: hostbox.LaunchFile{Device: uint64(group.Dev), Inode: group.Ino}}
if err := k.member(s.MainPID, s.ControlGroup); err != nil {
return observed, err
}
diff --git a/internal/host/claimlaunch_root.go b/internal/host/claimlaunch_root.go
index ee3dd2b21..fa73dc375 100644
--- a/internal/host/claimlaunch_root.go
+++ b/internal/host/claimlaunch_root.go
@@ -53,6 +53,7 @@ type ClaimLaunchObserved struct {
TenantStart string
Group, Executable hostbox.LaunchFile
Running hostbox.LaunchFile
+ Launcher hostbox.LaunchFile
}
// ClaimLaunchRoot is root's side of the reservation-bearing isolated launch
diff --git a/internal/host/claimstop_run.go b/internal/host/claimstop_run.go
index 8c55ad5e4..3542e2640 100644
--- a/internal/host/claimstop_run.go
+++ b/internal/host/claimstop_run.go
@@ -20,11 +20,10 @@ func RunInstanceControl(args []string) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if err := registerProductionLaunchVerifier(); err != nil {
- log.Print(err)
- return
+ log.Fatal(err)
}
if hostwrite.Enabled(os.Getenv) {
- if err := rootparticipation.WithLaunchExclusion(ctx, rootparticipation.ActivationReady); err != nil {
+ if err := claimActivationReady(ctx, rootparticipation.OpenForUpdate); err != nil {
log.Print(&ClaimDependencyError{Contracts: []string{"census", "registered verifier", "flag must stay off until Part 3 (G)"}})
return
}
@@ -126,3 +125,19 @@ func instanceRegistryHostApp(shared *registry.Registry) func(string) (string, st
return row.Account, row.App, nil
}
}
+
+func claimActivationReady(ctx context.Context, open func() (rootparticipation.Ledger, bool, error)) error {
+ l, required, err := open()
+ if l.State != nil {
+ defer l.State.Close()
+ }
+ if err != nil {
+ return err
+ }
+ if !required {
+ return rootparticipation.ErrUnavailable
+ }
+ ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
+ defer cancel()
+ return l.WithLaunchExclusion(ctx, l.ActivationReady)
+}
diff --git a/internal/host/movesfloor.go b/internal/host/movesfloor.go
index 081203653..0064b7d2c 100644
--- a/internal/host/movesfloor.go
+++ b/internal/host/movesfloor.go
@@ -124,20 +124,11 @@ var movesDirName = filepath.Dir(moves.FloorFile)
// locked: fn runs with a nil dir. An error opening or locking is returned
// without running fn.
//
-// Lock order, everywhere both are taken: the apply flock
-// (system/router/lock, held by systemApplier.Run for the whole run), then
-// the carlos.prev lock (root only), then this one, then the registry's
-// write lock (WithHoldsFrozen), then a host's held-stage lock
-// (withHeldStageLock), which is a leaf: its holder takes no other lock. The
-// agent takes only this one and holds
-// nothing else while it does; the adopt pass (rootJudgePrev) takes the
-// carlos.prev lock and this one and never the apply flock. Nothing that
-// holds this lock or the registry's may then take the apply flock: the
-// claim release floor's guard (installFloorHook, checkGuardRecovery,
-// beginGuard, endGuard) takes only the apply flock, through Run, and none
-// of its steps inside activationGuard (closeGuardJournal) takes a lock.
-// TestActivationGuardLockOrder pins the apply flock and this lock both
-// held at an activation.
+// Lock order: apply flock, root release exclusion (only with a census),
+// carlos.prev lock, moves floor lock, registry write lock, participation
+// transaction lock. A held-stage lock is a leaf. Adoption omits the apply
+// flock. Guarded close and rollback receive the outer release token; they
+// never acquire release exclusion while holding floor or registry locks.
func withFloorLock(dataDir string, create bool, fn func(d *movesDir) error) error {
d, err := openMovesDir(dataDir, create)
if errors.Is(err, os.ErrNotExist) && !create {
diff --git a/internal/host/movesheldrelease_root.go b/internal/host/movesheldrelease_root.go
index 3aaaee554..7303ef981 100644
--- a/internal/host/movesheldrelease_root.go
+++ b/internal/host/movesheldrelease_root.go
@@ -201,7 +201,7 @@ func (h *movesHeldReleaseRoot) Serve(ctx context.Context, peer StopPeer, q heldR
return heldReleaseResponse{}, staleHeldStage("it names region %q and this box is in %q", q.Region, actor.Region)
}
var out heldReleaseResponse
- err = withClaimRootRelease(ctx, true, h.OpenRelease, func() error {
+ err = withRootReleaseStore(ctx, h.OpenRelease, func() error {
var err error
if q.Action == heldReleaseStage {
out, err = h.stageRelease(ctx, actor, q)
diff --git a/internal/host/plan14_fix3_adopt_linux_test.go b/internal/host/plan14_fix3_adopt_linux_test.go
index 0203f07da..ae461fc10 100644
--- a/internal/host/plan14_fix3_adopt_linux_test.go
+++ b/internal/host/plan14_fix3_adopt_linux_test.go
@@ -14,7 +14,7 @@ import (
func TestR2AdoptFoundationFailureExitsOne(t *testing.T) {
if os.Getenv("PLAN14_FIX3_ADOPT_CHILD") == "1" {
- runAdoptWithEnsureSystemUnits([]string{"-stage-only", "-registry", os.Getenv("PLAN14_FIX3_REGISTRY")}, nil, func() {}, func() (rootstate.Store, error) { return &rollbackMemory{files: map[string][]byte{}}, nil })
+ runAdoptWithEnsureSystemUnits([]string{"-stage-only", "-registry", os.Getenv("PLAN14_FIX3_REGISTRY")}, nil, func() {}, noCensusAdoptSource(func() (rootstate.Store, error) { return &rollbackMemory{files: map[string][]byte{}}, nil }))
return
}
if os.Geteuid() == 0 {
diff --git a/internal/host/plan14_fix4_adopt_linux_test.go b/internal/host/plan14_fix4_adopt_linux_test.go
index b32a88e31..0706a9d6f 100644
--- a/internal/host/plan14_fix4_adopt_linux_test.go
+++ b/internal/host/plan14_fix4_adopt_linux_test.go
@@ -19,7 +19,7 @@ func TestPlan14Fix4AdoptPerHostFailureExitsZero(t *testing.T) {
if os.Getenv("PLAN14_FIX4_STAGE") == "1" {
args = append(args, "-stage-only")
}
- runAdoptWithEnsureSystemUnits(args, nil, func() {}, func() (rootstate.Store, error) { return &rollbackMemory{files: map[string][]byte{}}, nil })
+ runAdoptWithEnsureSystemUnits(args, nil, func() {}, noCensusAdoptSource(func() (rootstate.Store, error) { return &rollbackMemory{files: map[string][]byte{}}, nil }))
return
}
if os.Geteuid() == 0 {
diff --git a/internal/host/plan14_generation_containment_linux_test.go b/internal/host/plan14_generation_containment_linux_test.go
new file mode 100644
index 000000000..ded0f0076
--- /dev/null
+++ b/internal/host/plan14_generation_containment_linux_test.go
@@ -0,0 +1,112 @@
+//go:build linux
+
+package host
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/instancectl"
+ "github.com/carlosframework/platform/internal/release"
+)
+
+const containedStatus = "Uid:\t65534\t65534\t65534\t65534\nCapInh:\t0000000000000000\nCapPrm:\t0000000000000000\nCapEff:\t0000000000000000\nCapBnd:\t0000000000000000\nCapAmb:\t0000000000000000\nNoNewPrivs:\t1\nSeccomp:\t2\n"
+
+func TestPlan14GenerationContainedTask(t *testing.T) {
+ for _, attack := range []string{"none", "old record", "retired", "group identity", "main lifetime", "launcher", "namespace", "capability", "privilege gain", "missing filter", "unmasked", "other bind", "wrong app", "task group", "foreign proc", "foreign proc descriptor bind", "unrecognized mount root"} {
+ t.Run(attack, func(t *testing.T) {
+ f := generationProc(t)
+ group := instancectl.ClaimUnitControlGroup(f.reservation.Binding.Binding.Host)
+ cgroups, root := t.TempDir(), t.TempDir()
+ if err := os.MkdirAll(filepath.Join(cgroups, group), 0755); err != nil {
+ t.Fatal(err)
+ }
+ gid, err := generationIdentity(filepath.Join(cgroups, group))
+ if err != nil {
+ t.Fatal(err)
+ }
+ lid, err := generationIdentity(f.unrelated)
+ if err != nil {
+ t.Fatal(err)
+ }
+ r := f.reservation
+ r.Binding.Target.BootID = "boot-fixture"
+ r.Process = release.GenerationProcess{PID: 100, TenantPID: 200, Start: "123", TenantStart: "123", Group: group, Invocation: "invocation", GroupIdentity: gid, LauncherIdentity: lid, Containment: 1}
+ for _, pid := range []string{"100", "200"} {
+ p := filepath.Join(f.proc, pid)
+ if err := os.MkdirAll(p+"/ns", 0755); err != nil {
+ t.Fatal(err)
+ }
+ generationWrite(t, p+"/stat", pid+" (app) S 100 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 123 0\n")
+ generationWrite(t, p+"/cgroup", "0::/"+group+"\n")
+ for _, ns := range []string{"mnt", "pid"} {
+ generationLink(t, "confined-"+ns, p+"/ns/"+ns)
+ }
+ }
+ generationLink(t, f.unrelated, f.proc+"/100/exe")
+ generationLink(t, r.Binding.Path, f.proc+"/200/exe")
+ generationWrite(t, f.task+"/cgroup", "0::/"+group+"\n")
+ generationWrite(t, f.task+"/status", containedStatus)
+ generationRelink(t, root, f.task+"/root")
+ for _, ns := range []string{"mnt", "pid"} {
+ generationRelink(t, "confined-"+ns, f.task+"/ns/"+ns)
+ }
+ if err := os.MkdirAll(root+"/opt/carlos", 0755); err != nil {
+ t.Fatal(err)
+ }
+ generationLink(t, r.Binding.Path, root+"/opt/carlos/app")
+ generationWrite(t, f.task+"/mountinfo", fmt.Sprintf("1 0 %d:%d / / ro - ext4 disk ro\n2 1 %d:%d %s /opt/carlos/app ro - ext4 disk ro\n", generationMajor(gid.Device), generationMinor(gid.Device), generationMajor(r.Binding.Executable.Device), generationMinor(r.Binding.Executable.Device), r.Binding.Path))
+ status := []byte(containedStatus)
+ switch attack {
+ case "old record":
+ r.Process.Containment = 0
+ case "retired":
+ r.Retirement = "retired"
+ case "group identity":
+ r.Process.GroupIdentity.Inode++
+ case "main lifetime":
+ r.Process.Start = "999"
+ case "launcher":
+ r.Process.LauncherIdentity.Inode++
+ case "namespace":
+ generationRelink(t, "namespace-mnt", f.task+"/ns/mnt")
+ case "capability":
+ status = []byte(strings.Replace(containedStatus, "CapEff:\t0000000000000000", "CapEff:\t0000000000000001", 1))
+ case "privilege gain":
+ status = []byte(strings.Replace(containedStatus, "NoNewPrivs:\t1", "NoNewPrivs:\t0", 1))
+ case "missing filter":
+ status = []byte(strings.Replace(containedStatus, "Seccomp:\t2", "Seccomp:\t0", 1))
+ case "unmasked":
+ if err := os.MkdirAll(filepath.Join(root, f.versions), 0755); err != nil {
+ t.Fatal(err)
+ }
+ case "other bind":
+ generationWrite(t, f.task+"/mountinfo", fmt.Sprintf("1 0 0:1 / / ro - tmpfs tmpfs ro\n2 1 %d:%d %s /alias ro - ext4 disk ro\n", generationMajor(r.Binding.Executable.Device), generationMinor(r.Binding.Executable.Device), filepath.Dir(r.Binding.Tree)))
+ case "unrecognized mount root":
+ generationWrite(t, f.task+"/mountinfo", fmt.Sprintf("1 0 %d:%d / / ro - ext4 disk ro\n2 1 %d:%d /versions/v1/shop /opt/carlos/app ro - ext4 disk ro\n", generationMajor(gid.Device), generationMinor(gid.Device), generationMajor(r.Binding.Executable.Device), generationMinor(r.Binding.Executable.Device)))
+ case "wrong app":
+ generationRelink(t, f.unrelated, root+"/opt/carlos/app")
+ case "foreign proc", "foreign proc descriptor bind":
+ source := "/"
+ if attack == "foreign proc descriptor bind" {
+ source = "/777/fd"
+ }
+ generationWrite(t, f.task+"/mountinfo", "1 0 0:1 "+source+" /hostproc ro - proc proc ro\n")
+ case "task group":
+ generationWrite(t, f.task+"/cgroup", "0::/other\n")
+ }
+ k := generationKernel{proc: f.proc, cgroups: cgroups, contained: []release.GenerationReservation{r}, owner: uint32(os.Geteuid())}
+ ok, err := k.containedTask(t.Context(), f.task, status)
+ if attack == "none" {
+ if !ok || err != nil {
+ t.Fatalf("contained tenant refused: %v %v", ok, err)
+ }
+ } else if ok && err == nil {
+ t.Fatal("unsafe containment accepted")
+ }
+ })
+ }
+}
diff --git a/internal/host/plan14_generation_containment_root_linux_test.go b/internal/host/plan14_generation_containment_root_linux_test.go
new file mode 100644
index 000000000..d6801b856
--- /dev/null
+++ b/internal/host/plan14_generation_containment_root_linux_test.go
@@ -0,0 +1,270 @@
+//go:build linux && rootrunner
+
+package host
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/carlosframework/platform/internal/instancectl"
+ "github.com/carlosframework/platform/internal/instanceexec"
+ "github.com/carlosframework/platform/internal/registry"
+ "github.com/carlosframework/platform/internal/release"
+ "github.com/carlosframework/platform/internal/rootstate"
+ "golang.org/x/sys/unix"
+)
+
+func TestPlan14GenerationContainedRunningRootRunner(t *testing.T) {
+ const key = "PLAN14_E45_CONTAINED"
+ dir, mode := os.Getenv("PLAN14_E45_DIR"), os.Getenv(key)
+ if mode == "mount" {
+ runtime.LockOSThread()
+ if err := unix.Mount("", "/", "", unix.MS_REC|unix.MS_PRIVATE, ""); err != nil {
+ t.Fatal(err)
+ }
+ if err := unix.Mount("proc", "/proc", "proc", 0, ""); err != nil {
+ t.Fatal(err)
+ }
+ executable := release.VersionPath(filepath.Join(dir, "opt"), "bab", "v1", "shop")
+ if err := unix.Mount(executable, filepath.Join(dir, "bound-app"), "", unix.MS_BIND, ""); err != nil {
+ t.Fatal(err)
+ }
+ if err := unix.Mount("tmpfs", filepath.Join(dir, "opt"), "tmpfs", unix.MS_RDONLY, "mode=0755"); err != nil {
+ t.Fatal(err)
+ }
+ group := instancectl.ClaimUnitControlGroup(filepath.Base(dir) + ".example")
+ if err := os.WriteFile(filepath.Join("/sys/fs/cgroup", group, "cgroup.procs"), []byte("0"), 0600); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Setenv(key, "init"); err != nil {
+ t.Fatal(err)
+ }
+ env := os.Environ()
+ if err := syscall.Exec("/usr/bin/setpriv", []string{"setpriv", "--reuid=65534", "--regid=65534", "--clear-groups", "--bounding-set=-all", "--no-new-privs", filepath.Join(dir, "helper.test"), "-test.run=^TestPlan14GenerationContainedRunningRootRunner$", "-test.timeout=90s"}, env); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }
+ if mode == "init" {
+ runtime.LockOSThread()
+ if err := instanceexec.ScopeAbstractUnix(); err != nil {
+ t.Fatal(err)
+ }
+ if err := instanceexec.ScopeClaimGeneration(); err != nil {
+ t.Fatal(err)
+ }
+ for _, call := range []uintptr{unix.SYS_RECVMSG, unix.SYS_RECVMMSG, unix.SYS_PIDFD_GETFD, unix.SYS_IO_URING_SETUP} {
+ _, _, e := unix.RawSyscall6(call, ^uintptr(0), 0, 0, 0, 0, 0)
+ if e != unix.EPERM {
+ t.Fatal("descriptor boundary missing", call, e)
+ }
+ }
+ tenant := exec.Command(filepath.Join(dir, "bound-app"), "90")
+ if err := tenant.Start(); err != nil {
+ t.Fatal(err)
+ }
+ if err := tenant.Wait(); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }
+ if mode == "" {
+ dir = generationRootRunnerDir(t)
+ binary, err := os.ReadFile(os.Args[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(filepath.Join(dir, "helper.test"), binary, 0755); err != nil {
+ t.Fatal(err)
+ }
+ cmd := exec.CommandContext(t.Context(), "unshare", "--mount", "--pid", "--fork", "--mount-proc", filepath.Join(dir, "helper.test"), "-test.run=^TestPlan14GenerationContainedRunningRootRunner$", "-test.v", "-test.timeout=2m")
+ cmd.Dir = dir
+ cmd.Env = append(os.Environ(), key+"=census", "PLAN14_E45_DIR="+dir)
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("running containment: %v\n%s", err, out)
+ } else {
+ t.Logf("%s", out)
+ }
+ return
+ }
+ if mode != "census" || os.Geteuid() != 0 {
+ t.Fatal("invalid runner entry")
+ }
+ t.Setenv("CARLOS_OPT_DIR", filepath.Join(dir, "opt"))
+ body, err := os.ReadFile("/bin/sleep")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, version := range []string{"v1", "v2", "v3", "v4"} {
+ if err := release.StageArtifact(filepath.Join(dir, "opt"), "bab", version, "shop", body); err != nil {
+ t.Fatal(err)
+ }
+ old := time.Now().Add(-48 * time.Hour)
+ if err := os.Chtimes(release.VersionPath(filepath.Join(dir, "opt"), "bab", version, "shop"), old, old); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if err := os.WriteFile(filepath.Join(dir, "bound-app"), nil, 0755); err != nil {
+ t.Fatal(err)
+ }
+ host := filepath.Base(dir) + ".example"
+ group := instancectl.ClaimUnitControlGroup(host)
+ var made []string
+ for p := filepath.Join("/sys/fs/cgroup", group); p != "/sys/fs/cgroup"; p = filepath.Dir(p) {
+ made = append(made, p)
+ }
+ var owned []string
+ for i := len(made) - 1; i >= 0; i-- {
+ if err := os.Mkdir(made[i], 0755); err == nil {
+ owned = append(owned, made[i])
+ } else if !os.IsExist(err) {
+ t.Fatal(err)
+ }
+ }
+ defer func() {
+ for i := len(owned) - 1; i >= 0; i-- {
+ if err := os.Remove(owned[i]); err != nil {
+ t.Error(err)
+ }
+ }
+ }()
+ cmd := exec.Command(filepath.Join(dir, "helper.test"), "-test.run=^TestPlan14GenerationContainedRunningRootRunner$", "-test.timeout=90s")
+ cmd.Dir = dir
+ cmd.Env = append(os.Environ(), key+"=mount")
+ cmd.SysProcAttr = &syscall.SysProcAttr{Cloneflags: unix.CLONE_NEWNS | unix.CLONE_NEWPID}
+ cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+ defer func() { cmd.Process.Kill(); cmd.Wait() }()
+ pid := cmd.Process.Pid
+ k := linuxClaimLaunchKernel{ProcRoot: "/proc", CgroupRoot: "/sys/fs/cgroup", BoundApp: filepath.Join(dir, "bound-app")}
+ var observed ClaimLaunchObserved
+ deadline := time.Now().Add(10 * time.Second)
+ for {
+ observed, err = k.Observe(instancectl.ClaimUnitStarted{MainPID: pid, ControlGroup: group})
+ if err == nil {
+ break
+ }
+ if time.Now().After(deadline) {
+ t.Fatalf("tenant observation: %+v %v", observed, err)
+ }
+ time.Sleep(10 * time.Millisecond)
+ }
+ boot, err := (generationKernel{proc: "/proc"}).boot()
+ if err != nil {
+ t.Fatal(err)
+ }
+ controller, err := claimLaunchProcessStart("/proc", os.Getpid())
+ if err != nil {
+ t.Fatal(err)
+ }
+ state, err := rootstate.Open(filepath.Join(dir, "generations"), true, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer state.Close()
+ ledger := &release.RootGenerations{Store: state}
+ x := &release.ReleaseExclusion{Open: func() (rootstate.Store, error) { return rootstate.Open(filepath.Join(dir, "exclusion"), true, false) }}
+ identity := func(path string) rootstate.FileIdentity {
+ id, err := generationIdentity(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return id
+ }
+ digest := sha256.Sum256(body)
+ reserve := func(version string) release.GenerationReservation {
+ path := release.VersionPath(filepath.Join(dir, "opt"), "bab", version, "shop")
+ b := release.GenerationBinding{Binding: release.HistoryBinding{Host: host, Account: "bab", App: "shop"}, Target: release.GenerationOwner{Host: host, Incarnation: "fixture", Stamp: 1, Session: 1, Box: "runner", Enrollment: "fixture", BootID: boot, ControllerID: "fixture"}, Installation: "fixture", Attempt: version, Inputs: "fixture", Invocation: version, Record: "fixture", RecordETag: "fixture", ControllerPID: os.Getpid(), ControllerStart: controller, Version: version, Digest: hex.EncodeToString(digest[:]), Path: path, Tree: filepath.Dir(path), Executable: identity(path), TreeIdentity: identity(filepath.Dir(path))}
+ r, err := ledger.Reserve(b)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return r
+ }
+ var v1, v3 release.GenerationReservation
+ if err := x.With(t.Context(), func(context.Context) error {
+ v1, v3 = reserve("v1"), reserve("v3")
+ return ledger.Started(v1, release.GenerationProcess{PID: pid, Start: observed.ProcessStart, TenantPID: observed.TenantPID, TenantStart: observed.TenantStart, Group: group, Invocation: "fixture", GroupIdentity: identity(filepath.Join("/sys/fs/cgroup", group)), LauncherIdentity: identity(filepath.Join("/proc", strconv.Itoa(pid), "exe")), Containment: 1})
+ }); err != nil {
+ t.Fatal(err)
+ }
+ v1, err = ledger.Read(v1.Binding)
+ if err != nil {
+ t.Fatal(err)
+ }
+ source := generationPinSource{open: func() (rootstate.Store, error) {
+ return rootstate.Open(filepath.Join(dir, "generations"), false, false)
+ }, verify: func(ctx context.Context, rs []release.GenerationReservation) error {
+ // Enrollment/launch-journal agreement is fixture authority in this witness.
+ kernel := generationKernel{proc: "/proc", cgroups: "/sys/fs/cgroup", boundApp: filepath.Join(dir, "bound-app"), contained: rs}
+ _, _, err := kernel.census(ctx, filepath.Join(dir, "opt", "versions"))
+ if err != nil {
+ tasks, _ := filepath.Glob("/proc/[0-9]*/task/[0-9]*")
+ for _, task := range tasks {
+ status, _ := generationRead(task + "/status")
+ ok, e := kernel.containedTask(ctx, task, status)
+ if e != nil {
+ t.Logf("containment %s ok=%v err=%v status=%s", task, ok, e, status)
+ }
+ }
+ }
+ return err
+ }}
+ for _, attack := range []string{"none", "erased legacy", "renamed legacy"} {
+ legacy := filepath.Join(dir, "zdd")
+ if attack == "none" {
+ if err := os.Mkdir(legacy, 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := release.WriteGenerations(filepath.Join(legacy, "generations.json"), release.Generations{}); err != nil {
+ t.Fatal(err)
+ }
+ }
+ for _, name := range []string{"generations.json", "generations.seq"} {
+ p := filepath.Join(legacy, name)
+ if attack == "erased legacy" {
+ if err := os.Remove(p); err != nil {
+ t.Fatal(err)
+ }
+ }
+ }
+ if attack == "renamed legacy" {
+ if err := os.Rename(legacy, legacy+".old"); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if err := x.With(t.Context(), func(ctx context.Context) error {
+ pins, err := source.StagedPins(ctx, filepath.Join(dir, "opt"))
+ if err != nil {
+ return fmt.Errorf("%s: %w", attack, err)
+ }
+ for _, r := range []release.GenerationReservation{v1, v3} {
+ if ok, err := pins.Refers(r.Binding.Path); err != nil || !ok {
+ t.Fatal("missing pin", err)
+ }
+ }
+ sweepStagedVersions(fakeLister{routes: []registry.Route{{Host: host, Account: "bab", App: "shop", Version: "v2"}}}, filepath.Join(dir, "opt"), filepath.Join(legacy, "generations.json"), time.Hour, time.Now(), source)
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
+ if !staged(t, filepath.Join(dir, "opt"), "v1") || !staged(t, filepath.Join(dir, "opt"), "v2") || !staged(t, filepath.Join(dir, "opt"), "v3") || staged(t, filepath.Join(dir, "opt"), "v4") {
+ t.Fatal("running/pending pins or permitted sweep incorrect")
+ }
+ }
+ if got, err := ledger.Read(v3.Binding); err != nil || got != v3 {
+ t.Fatal("pending reservation changed", err)
+ }
+}
diff --git a/internal/host/plan14_generation_kernel_linux_test.go b/internal/host/plan14_generation_kernel_linux_test.go
index 60ad8f0f3..493875da8 100644
--- a/internal/host/plan14_generation_kernel_linux_test.go
+++ b/internal/host/plan14_generation_kernel_linux_test.go
@@ -14,8 +14,8 @@ import (
)
type generationProcFixture struct {
- proc, task, unrelated string
- reservation release.GenerationReservation
+ proc, task, unrelated, versions string
+ reservation release.GenerationReservation
}
func generationProc(t *testing.T) generationProcFixture {
@@ -24,7 +24,7 @@ func generationProc(t *testing.T) generationProcFixture {
if _, err := f.root.generations.reserve(t.Context(), f.root, f.peer, f.req); err != nil {
t.Fatal(err)
}
- g := generationProcFixture{proc: t.TempDir(), unrelated: filepath.Join(t.TempDir(), "unrelated"), reservation: readGenerationLaunch(t, disk)}
+ g := generationProcFixture{versions: filepath.Join(f.root.generations.optDir, "versions"), proc: t.TempDir(), unrelated: filepath.Join(t.TempDir(), "unrelated"), reservation: readGenerationLaunch(t, disk)}
if err := os.WriteFile(g.unrelated, []byte("other"), 0600); err != nil {
t.Fatal(err)
}
diff --git a/internal/host/plan14_held_root_test.go b/internal/host/plan14_held_root_test.go
index 4a5781942..c5eecd44b 100644
--- a/internal/host/plan14_held_root_test.go
+++ b/internal/host/plan14_held_root_test.go
@@ -148,7 +148,7 @@ func TestPlan14AdoptPublishNoLockInversion(t *testing.T) {
return nil, err
}
adopt := func() error {
- return withClaimRootRelease(ctx, true, f.root.OpenRelease, func() error {
+ return withRootReleaseStore(ctx, f.root.OpenRelease, func() error {
captured, err := g.Capture(f.claim.Host)
if err != nil {
return err
diff --git a/internal/host/plan14_launch_verifier_test.go b/internal/host/plan14_launch_verifier_test.go
index 181a0d21e..aead7a7ae 100644
--- a/internal/host/plan14_launch_verifier_test.go
+++ b/internal/host/plan14_launch_verifier_test.go
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
+ "fmt"
"os"
"path/filepath"
"strings"
@@ -23,7 +24,10 @@ func TestPlan14LaunchVerifierToken(t *testing.T) {
disk := &rollbackDisk{dir: t.TempDir()}
reads := 0
v := rootLaunchVerifier{exclude: x, open: func() (rootstate.Store, error) { reads++; return disk, nil }, reconcile: generationReconciler{
- read: func(context.Context) (generationInventory, error) {
+ read: func(ctx context.Context) (generationInventory, error) {
+ if ctx.Value(launchRecoveryCensusKey{}) == true {
+ t.Fatal("ordinary verification admitted pending acquisition")
+ }
return generationInventory{Census: rootparticipation.Census{Schema: 1, Box: "box", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 7, Complete: true}}, nil
},
kernel: func(context.Context) (string, string, error) { return "boot", strings.Repeat("a", 64), nil },
@@ -48,7 +52,7 @@ func TestPlan14LaunchVerifierToken(t *testing.T) {
}
func TestPlan14LaunchVerifierCallerOrder(t *testing.T) {
- for _, name := range []string{"idle", "begin", "end", "recover", "activation", "floor-begin", "floor-end", "system-begin", "system-clear", "system-recovery"} {
+ for _, name := range []string{"idle", "begin", "end", "recover", "activation", "floor-begin", "floor-end", "system-begin", "system-clear", "system-recovery", "guard-close", "guard-rollback"} {
t.Run(name, func(t *testing.T) {
lock := &plan14ReleaseStore{}
x := &release.ReleaseExclusion{Open: func() (rootstate.Store, error) { return lock, nil }}
@@ -58,12 +62,15 @@ func TestPlan14LaunchVerifierCallerOrder(t *testing.T) {
verified := 0
l.VerifyLaunches = func(ctx context.Context, _ uint64) error {
verified++
+ if rootparticipation.IsAcquisitionRecovery(ctx) != (name == "recover") {
+ t.Error("wrong acquisition recovery scope")
+ }
if err := x.Check(ctx); err != nil {
return rootparticipation.ErrUnavailable
}
return nil
}
- if name == "end" || name == "floor-end" || name == "system-clear" {
+ if name == "end" || name == "floor-end" || name == "system-clear" || name == "guard-close" || name == "guard-rollback" {
if err := x.With(t.Context(), func(ctx context.Context) error { return l.BeginUpdate(ctx, "update") }); err != nil {
t.Fatal(err)
}
@@ -101,6 +108,8 @@ func TestPlan14LaunchVerifierCallerOrder(t *testing.T) {
return a.beginExecutableChange(ctx, "new", time.Now())
case "system-clear":
return a.clearUpdateBegun(ctx)
+ case "guard-close", "guard-rollback":
+ return a.activationGuard(ctx, "new", "old", func(ctx context.Context) error { return a.clearUpdateBegun(ctx) })
case "system-recovery":
return a.controllersRecovered(ctx)
}
@@ -111,6 +120,11 @@ func TestPlan14LaunchVerifierCallerOrder(t *testing.T) {
holderDone <- x.With(t.Context(), func(ctx context.Context) error {
close(held)
<-leave
+ if name == "guard-close" || name == "guard-rollback" {
+ if err := withFloorLock(a.dataDir, true, func(*movesDir) error { return nil }); err != nil {
+ return err
+ }
+ }
unlock, err := l.State.Lock(ctx, "transaction.lock")
if err == nil {
unlock()
@@ -196,3 +210,23 @@ func TestPlan14LaunchVerifierNoCensus(t *testing.T) {
t.Fatal(err)
}
}
+
+func TestPlan14SystemDeadlineNoCensusUnchanged(t *testing.T) {
+ for _, census := range []bool{false, true} {
+ t.Run(fmt.Sprint(census), func(t *testing.T) {
+ l := updateParticipationFixture(t)
+ if !census {
+ if err := l.State.Remove("census.json"); err != nil {
+ t.Fatal(err)
+ }
+ }
+ f := newApplyFixture(t, "stable", "old")
+ f.a.participation = l
+ f.a.floorHook = func(*systemApplier) error { return context.DeadlineExceeded }
+ err := f.a.Run(t.Context(), false)
+ if !errors.Is(err, context.DeadlineExceeded) || errors.Is(err, errSystemDeferred) != census {
+ t.Fatal("wrong timeout disposition", err)
+ }
+ })
+ }
+}
diff --git a/internal/host/plan14_release_lock_test.go b/internal/host/plan14_release_lock_test.go
index 93bf1e9b6..0ad84a2e0 100644
--- a/internal/host/plan14_release_lock_test.go
+++ b/internal/host/plan14_release_lock_test.go
@@ -3,6 +3,7 @@ package host
import (
"context"
"errors"
+ "github.com/carlosframework/platform/internal/rootparticipation"
"go/ast"
"go/parser"
"go/token"
@@ -45,21 +46,21 @@ func TestPlan14AdoptReleaseLock(t *testing.T) {
effects++
return nil
}
- if err := withClaimRootRelease(context.Background(), true, open, run); err != nil {
+ if err := withRootReleaseStore(context.Background(), open, run); err != nil {
t.Fatal(err)
}
if effects != 1 || called != 1 {
t.Fatal(effects, called)
}
s.fail = os.ErrPermission
- if err := withClaimRootRelease(context.Background(), true, open, run); !errors.Is(err, os.ErrPermission) || effects != 1 {
+ if err := withRootReleaseStore(context.Background(), open, run); !errors.Is(err, os.ErrPermission) || effects != 1 {
t.Fatal("lock failure admitted effects", err, effects)
}
- if err := withClaimRootRelease(context.Background(), false, open, run); !errors.Is(err, os.ErrPermission) || effects != 1 {
+ if err := withRootReleaseStore(context.Background(), open, run); !errors.Is(err, os.ErrPermission) || effects != 1 {
t.Fatal("flag-off lock failure admitted effects", err, effects)
}
s.fail = nil
- if err := withClaimRootRelease(context.Background(), false, open, run); err != nil || effects != 2 || called != 4 {
+ if err := withRootReleaseStore(context.Background(), open, run); err != nil || effects != 2 || called != 4 {
t.Fatal(err, effects)
}
}
@@ -91,7 +92,7 @@ func TestPlan14AdoptBodyIsUnderRelease(t *testing.T) {
if id.Name == "runAdoptUnderRootRelease" {
calls++
}
- if id.Name == "withClaimRootRelease" && len(call.Args) == 4 {
+ if id.Name == "withClaimRootRelease" && len(call.Args) == 5 {
fn, ok := call.Args[3].(*ast.FuncLit)
if !ok {
t.Fatal("release effect is not the adoption callback")
@@ -111,3 +112,53 @@ func TestPlan14AdoptBodyIsUnderRelease(t *testing.T) {
t.Fatalf("adoption calls=%d; under release=%d", calls, protected)
}
}
+
+func TestPlan14AdoptNoCensusDoesNotOpenRelease(t *testing.T) {
+
+ effects := 0
+ for range 2 {
+ err := withClaimReleaseCensus(t.Context(), func() (rootparticipation.Ledger, bool, error) { return rootparticipation.Ledger{}, false, nil }, func() (rootstate.Store, error) {
+ t.Fatal("opened release without census")
+ return nil, os.ErrPermission
+ }, func() error { effects++; return nil })
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ if effects != 2 {
+ t.Fatal(effects)
+ }
+}
+
+func TestPlan14ActivationNoCensusDoesNotExclude(t *testing.T) {
+ err := claimActivationReady(t.Context(), func() (rootparticipation.Ledger, bool, error) {
+ return rootparticipation.Ledger{ExcludeLaunches: func(context.Context, func(context.Context) error) error {
+ t.Fatal("excluded without census")
+ return nil
+ }}, false, nil
+ })
+ if !errors.Is(err, rootparticipation.ErrUnavailable) {
+ t.Fatal(err)
+ }
+}
+
+func noCensusAdoptSource(open func() (rootstate.Store, error)) adoptRootSource {
+ return adoptRootSource{OpenRelease: open, Census: func() (rootparticipation.Ledger, bool, error) { return rootparticipation.Ledger{}, false, nil }}
+}
+
+func TestPlan14AdoptRetainedCensusBothFlags(t *testing.T) {
+ for _, flag := range []bool{false, true} {
+ store := &plan14ReleaseStore{}
+ called := false
+ err := withClaimRootRelease(t.Context(), flag, func() (rootstate.Store, error) { return store, nil }, func() error {
+ called = true
+ if !store.held {
+ t.Fatal("retained census effect outside release")
+ }
+ return nil
+ }, func() (rootparticipation.Ledger, bool, error) { return rootparticipation.Ledger{}, true, nil })
+ if err != nil || !called {
+ t.Fatal(err, called)
+ }
+ }
+}
diff --git a/internal/host/rootgeneration_census_linux.go b/internal/host/rootgeneration_census_linux.go
index b7c892ba5..e794b8ea5 100644
--- a/internal/host/rootgeneration_census_linux.go
+++ b/internal/host/rootgeneration_census_linux.go
@@ -14,8 +14,8 @@ import (
)
// census checks the whole staged namespace, including unreserved builds. This
-// quiescent verifier refuses uncontained service tasks and foreign namespaces.
-// Supporting running tenants requires independent containment evidence first.
+// verifier accepts running tenants only with root-retained launch containment.
+// Other service tasks and unexplained namespaces still refuse completeness.
func (k generationKernel) census(ctx context.Context, tree string) (string, string, error) {
before, err := k.boot()
if err != nil {
@@ -63,7 +63,7 @@ func checkedGenerationTree(ctx context.Context, tree string) error {
if err := unix.Lstat(path, &st); err != nil {
return err
}
- if st.Uid != 0 || st.Mode&0022 != 0 || (st.Mode&unix.S_IFMT != unix.S_IFREG && st.Mode&unix.S_IFMT != unix.S_IFDIR) {
+ if st.Uid != 0 || st.Mode&0022 != 0 || (st.Mode&unix.S_IFMT == unix.S_IFREG && st.Nlink != 1) || (st.Mode&unix.S_IFMT != unix.S_IFREG && st.Mode&unix.S_IFMT != unix.S_IFDIR) {
return os.ErrPermission
}
return nil
@@ -76,9 +76,14 @@ func productionGenerationKernelCensus(ctx context.Context) (string, string, erro
if err := verifyStopKernelMounts("/sys/fs/cgroup", "/proc"); err != nil {
return "", "", err
}
+ hardlinks, err := generationRead("/proc/sys/fs/protected_hardlinks")
+ if err != nil || string(hardlinks) != "1\n" {
+ return "", "", os.ErrPermission
+ }
tree := filepath.Join(release.OptDir(), "versions")
if err := checkedGenerationTree(ctx, tree); err != nil {
return "", "", err
}
- return (generationKernel{proc: "/proc"}).census(ctx, tree)
+ records, _ := ctx.Value(generationCensusReservationsKey{}).([]release.GenerationReservation)
+ return (generationKernel{proc: "/proc", cgroups: "/sys/fs/cgroup", contained: records}).census(ctx, tree)
}
diff --git a/internal/host/rootgeneration_containment_linux.go b/internal/host/rootgeneration_containment_linux.go
new file mode 100644
index 000000000..a7f098ad5
--- /dev/null
+++ b/internal/host/rootgeneration_containment_linux.go
@@ -0,0 +1,200 @@
+//go:build linux
+
+package host
+
+import (
+ "context"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/carlosframework/platform/internal/instancectl"
+ "github.com/carlosframework/platform/internal/release"
+ "github.com/carlosframework/platform/internal/rootstate"
+ "golang.org/x/sys/unix"
+)
+
+func generationIdentity(path string) (rootstate.FileIdentity, error) {
+ var st unix.Stat_t
+ if err := unix.Stat(path, &st); err != nil {
+ return rootstate.FileIdentity{}, err
+ }
+ return rootstate.FileIdentity{Device: uint64(st.Dev), Inode: st.Ino}, nil
+}
+
+func (k generationKernel) containedTask(ctx context.Context, path string, status []byte) (bool, error) {
+ raw, err := generationRead(path + "/cgroup")
+ if err != nil {
+ return false, err
+ }
+ group := strings.TrimSpace(string(raw))
+ for _, r := range k.contained {
+ p := r.Process
+ if p.Containment != 1 || r.Retirement != "" || group != "0::/"+p.Group {
+ continue
+ }
+ if err := generationContainedStatus(status); err != nil {
+ return false, err
+ }
+ if err := k.containedBinding(ctx, path, r); err != nil {
+ return false, err
+ }
+ return true, nil
+ }
+ return false, nil
+}
+
+func generationContainedStatus(raw []byte) error {
+ fields := map[string]string{}
+ for _, line := range strings.Split(string(raw), "\n") {
+ key, value, ok := strings.Cut(line, ":")
+ if ok {
+ if _, dup := fields[key]; dup {
+ return os.ErrPermission
+ }
+ fields[key] = strings.TrimSpace(value)
+ }
+ }
+ for _, key := range []string{"CapInh", "CapPrm", "CapEff", "CapBnd", "CapAmb"} {
+ n, err := strconv.ParseUint(fields[key], 16, 64)
+ if err != nil || n != 0 {
+ return os.ErrPermission
+ }
+ }
+ if fields["NoNewPrivs"] != "1" || fields["Seccomp"] != "2" {
+ return os.ErrPermission
+ }
+ return nil
+}
+
+func (k generationKernel) containedBinding(ctx context.Context, path string, r release.GenerationReservation) error {
+ if err := ctx.Err(); err != nil {
+ return err
+ }
+ b, p := r.Binding, r.Process
+ boot, err := k.boot()
+ if err != nil || boot != b.Target.BootID || p.Group != instancectl.ClaimUnitControlGroup(b.Binding.Host) {
+ return os.ErrPermission
+ }
+ var group unix.Stat_t
+ if err := unix.Lstat(filepath.Join(k.cgroups, p.Group), &group); err != nil {
+ return err
+ }
+ if group.Uid != k.owner || group.Mode&0022 != 0 || group.Mode&unix.S_IFMT != unix.S_IFDIR || (rootstate.FileIdentity{Device: uint64(group.Dev), Inode: group.Ino}) != p.GroupIdentity {
+ return os.ErrPermission
+ }
+ main, tenant := filepath.Join(k.proc, strconv.Itoa(p.PID)), filepath.Join(k.proc, strconv.Itoa(p.TenantPID))
+ for pid, want := range map[int]string{p.PID: p.Start, p.TenantPID: p.TenantStart} {
+ start, err := claimLaunchProcessStart(k.proc, pid)
+ if err != nil || start != want {
+ return os.ErrPermission
+ }
+ raw, err := generationRead(filepath.Join(k.proc, strconv.Itoa(pid), "cgroup"))
+ if err != nil || strings.TrimSpace(string(raw)) != "0::/"+p.Group {
+ return os.ErrPermission
+ }
+ }
+ launcher, err := generationIdentity(main + "/exe")
+ if err != nil || launcher != p.LauncherIdentity {
+ return os.ErrPermission
+ }
+ privatePID := ""
+ for _, ns := range []string{"mnt", "pid"} {
+ want, err := os.Readlink(tenant + "/ns/" + ns)
+ if err != nil || want == "" {
+ return os.ErrPermission
+ }
+ if ns == "pid" {
+ privatePID = want
+ }
+ outer, err := os.Readlink(k.proc + "/self/ns/" + ns)
+ if err != nil || outer == want {
+ return os.ErrPermission
+ }
+ for _, task := range []string{main, path} {
+ actual, err := os.Readlink(task + "/ns/" + ns)
+ if err != nil || actual != want {
+ return os.ErrPermission
+ }
+ }
+ }
+ bound := strings.TrimPrefix(k.boundApp, "/")
+ if bound == "" {
+ bound = "opt/carlos/app"
+ }
+ id, err := generationIdentity(filepath.Join(path, "root", bound))
+ if err != nil || id != b.Executable {
+ return os.ErrPermission
+ }
+ tree := filepath.Dir(b.Tree)
+ if b.Binding.Account != "" && b.Binding.Account != release.OpsAccount {
+ tree = filepath.Dir(tree)
+ }
+ if filepath.Base(tree) != "versions" || b.Path != release.VersionPath(filepath.Dir(tree), b.Binding.Account, b.Version, b.Binding.App) {
+ return os.ErrPermission
+ }
+ // The canonical version store must be hidden, not merely read-only.
+ if _, err := os.Stat(filepath.Join(path, "root", tree)); !os.IsNotExist(err) {
+ return os.ErrPermission
+ }
+ raw, err := generationRead(path + "/mountinfo")
+ if err != nil {
+ return err
+ }
+ boundMount := false
+ for _, line := range strings.Split(strings.TrimSpace(string(raw)), "\n") {
+ f := strings.Fields(line)
+ if len(f) < 10 {
+ return os.ErrPermission
+ }
+ source, e1 := generationUnescape(f[3])
+ point, e2 := generationUnescape(f[4])
+ if e1 != nil || e2 != nil || !filepath.IsAbs(source) || !filepath.IsAbs(point) {
+ return os.ErrPermission
+ }
+ separator := -1
+ for i := 6; i < len(f); i++ {
+ if f[i] == "-" {
+ separator = i
+ break
+ }
+ }
+ if separator < 6 || len(f) != separator+4 {
+ return os.ErrPermission
+ }
+ if f[separator+1] == "proc" {
+ if source == "/" {
+ visible, err := os.Readlink(filepath.Join(path, "root", point, "1/ns/pid"))
+ if err != nil || visible != privatePID {
+ return os.ErrPermission
+ }
+ } else if source != "/sys" && !strings.HasPrefix(source, "/sys/") {
+ return os.ErrPermission
+ }
+ }
+ major, minor, ok := strings.Cut(f[2], ":")
+ maj, e3 := strconv.ParseUint(major, 10, 32)
+ min, e4 := strconv.ParseUint(minor, 10, 32)
+ if !ok || e3 != nil || e4 != nil {
+ return os.ErrPermission
+ }
+ if unix.Mkdev(uint32(maj), uint32(min)) != b.Executable.Device {
+ continue
+ }
+ // The one reserved file is the only staged bind this namespace may hold.
+ if source == b.Path && point == "/"+bound {
+ boundMount = true
+ continue
+ }
+ below, e1 := filepath.Rel(tree, source)
+ above, e2 := filepath.Rel(source, tree)
+ if (e1 == nil && filepath.IsLocal(below)) || (e2 == nil && filepath.IsLocal(above) && point != "/") {
+ return os.ErrPermission
+ }
+ }
+ if !boundMount {
+ return os.ErrPermission
+ }
+ return nil
+}
diff --git a/internal/host/rootgeneration_inventory.go b/internal/host/rootgeneration_inventory.go
index d84df1024..008c06b72 100644
--- a/internal/host/rootgeneration_inventory.go
+++ b/internal/host/rootgeneration_inventory.go
@@ -25,6 +25,8 @@ type generationInventory struct {
Payloads []hostbox.GenerationPayload
Records []generationRecord
}
+type generationCensusReservationsKey struct{}
+
type generationReconciler struct {
read func(context.Context) (generationInventory, error)
kernel func(context.Context) (string, string, error)
@@ -165,7 +167,7 @@ func (v generationReconciler) reconcile(ctx context.Context, records []release.G
}
}
snapshotDigest := generationInventoryDigest(before)
- boot, kernel, err := v.kernel(ctx)
+ boot, kernel, err := v.kernel(context.WithValue(ctx, generationCensusReservationsKey{}, records))
if err != nil {
return zero, err
}
diff --git a/internal/host/rootgeneration_kernel_linux.go b/internal/host/rootgeneration_kernel_linux.go
index eb3490bbe..5bcbb0fae 100644
--- a/internal/host/rootgeneration_kernel_linux.go
+++ b/internal/host/rootgeneration_kernel_linux.go
@@ -25,7 +25,11 @@ import (
// This reader can prove absence only with launch exclusion held by its caller.
// Uncontained service processes and foreign namespaces remain unavailable until
// their reference-creation boundary can be established independently.
-type generationKernel struct{ proc string }
+type generationKernel struct {
+ proc, cgroups, boundApp string
+ owner uint32
+ contained []release.GenerationReservation
+}
func generationMajor(dev uint64) uint32 { return unix.Major(dev) }
func generationMinor(dev uint64) uint32 { return unix.Minor(dev) }
@@ -353,6 +357,23 @@ func (k generationKernel) task(ctx context.Context, path string, b release.Gener
if err != nil {
return "", err
}
+ if len(k.contained) != 0 {
+ ok, err := k.containedTask(ctx, path, status)
+ if err != nil {
+ return "", err
+ }
+ if ok {
+ again, err := generationRead(path + "/stat")
+ if err != nil {
+ return "", err
+ }
+ end, err := generationStart(again)
+ if err != nil || end != start {
+ return "", os.ErrPermission
+ }
+ return start, nil
+ }
+ }
kernel, err := generationRootTask(status)
if err != nil {
return "", err
diff --git a/internal/host/rootgenerations_launch.go b/internal/host/rootgenerations_launch.go
index b2e7d082a..9761c5b0b 100644
--- a/internal/host/rootgenerations_launch.go
+++ b/internal/host/rootgenerations_launch.go
@@ -161,6 +161,10 @@ func (p *generationLaunchPermit) started(grant hostbox.RoleLaunchGrant, observed
if p == nil {
return nil
}
- return p.ledger.Started(p.reservation, release.GenerationProcess{PID: grant.PID, Start: grant.ProcessStart, TenantPID: observed.TenantPID, TenantStart: observed.TenantStart,
+ containment := uint8(0)
+ if observed.Launcher.Device != 0 && observed.Launcher.Inode != 0 {
+ containment = 1
+ }
+ return p.ledger.Started(p.reservation, release.GenerationProcess{Containment: containment, LauncherIdentity: rootstate.FileIdentity{Device: observed.Launcher.Device, Inode: observed.Launcher.Inode}, PID: grant.PID, Start: grant.ProcessStart, TenantPID: observed.TenantPID, TenantStart: observed.TenantStart,
Group: grant.Group.RelativePath, Invocation: grant.UnitInvocationID, GroupIdentity: rootstate.FileIdentity{Device: observed.Group.Device, Inode: observed.Group.Inode}})
}
diff --git a/internal/host/roothistory_creation.go b/internal/host/roothistory_creation.go
index ddf66355a..18b9ac997 100644
--- a/internal/host/roothistory_creation.go
+++ b/internal/host/roothistory_creation.go
@@ -26,7 +26,7 @@ func productionCreationHistory(ctx context.Context, effect func(*release.RootHis
if legacy {
return os.ErrPermission
}
- return withClaimRootRelease(ctx, true, func() (rootstate.Store, error) {
+ return withRootReleaseStore(ctx, func() (rootstate.Store, error) {
return rootstate.Open(rootReleasePrivate, true, false)
}, func() error {
s, err := rootstate.Open(release.RootHistoryDir, true, false)
diff --git a/internal/host/rootlaunchverifier.go b/internal/host/rootlaunchverifier.go
index 39b757009..d0b11348d 100644
--- a/internal/host/rootlaunchverifier.go
+++ b/internal/host/rootlaunchverifier.go
@@ -3,6 +3,7 @@ package host
import (
"context"
"sync"
+ "time"
"github.com/carlosframework/platform/internal/release"
"github.com/carlosframework/platform/internal/rootparticipation"
@@ -43,7 +44,9 @@ func (v rootLaunchVerifier) verify(ctx context.Context, revision uint64) error {
}
return snapshot, err
}
- ctx = context.WithValue(ctx, launchRecoveryCensusKey{}, true)
+ if rootparticipation.IsAcquisitionRecovery(ctx) {
+ ctx = context.WithValue(ctx, launchRecoveryCensusKey{}, true)
+ }
_, err = ledger.Certify(ctx, records, verifier.reconcile)
return err
}
@@ -52,7 +55,7 @@ var rootLaunchStartup struct {
sync.Once
err error
}
-var participationRelease = &release.ReleaseExclusion{Open: func() (rootstate.Store, error) {
+var participationRelease = &release.ReleaseExclusion{Wait: 5 * time.Second, Open: func() (rootstate.Store, error) {
return rootstate.Open(rootReleasePrivate, true, false)
}}
diff --git a/internal/host/rootreleaselock.go b/internal/host/rootreleaselock.go
index 6a4272dae..32a072037 100644
--- a/internal/host/rootreleaselock.go
+++ b/internal/host/rootreleaselock.go
@@ -2,6 +2,7 @@ package host
import (
"context"
+ "github.com/carlosframework/platform/internal/rootparticipation"
"os"
"github.com/carlosframework/platform/internal/rootstate"
@@ -9,10 +10,38 @@ import (
const rootReleasePrivate = "/var/lib/carlos-release/private"
-func withClaimRootRelease(ctx context.Context, _ bool, open func() (rootstate.Store, error), effect func() error) error {
+func withClaimRootRelease(ctx context.Context, _ bool, open func() (rootstate.Store, error), effect func() error, census ...func() (rootparticipation.Ledger, bool, error)) error {
if open == nil || effect == nil {
return os.ErrPermission
}
+ probe := rootparticipation.OpenForUpdate
+ if len(census) > 1 {
+ return os.ErrPermission
+ }
+ if len(census) == 1 {
+ if census[0] == nil {
+ return os.ErrPermission
+ }
+ probe = census[0]
+ }
+ return withClaimReleaseCensus(ctx, probe, open, effect)
+}
+
+func withClaimReleaseCensus(ctx context.Context, census func() (rootparticipation.Ledger, bool, error), open func() (rootstate.Store, error), effect func() error) error {
+ l, required, err := census()
+ if l.State != nil {
+ defer l.State.Close()
+ }
+ if err != nil {
+ return err
+ }
+ if !required {
+ return effect()
+ }
+ return withRootReleaseStore(ctx, open, effect)
+}
+
+func withRootReleaseStore(ctx context.Context, open func() (rootstate.Store, error), effect func() error) error {
s, err := open()
if err != nil {
return err
@@ -20,3 +49,8 @@ func withClaimRootRelease(ctx context.Context, _ bool, open func() (rootstate.St
defer s.Close()
return rootstate.WithRelease(ctx, s, effect)
}
+
+type adoptRootSource struct {
+ OpenRelease func() (rootstate.Store, error)
+ Census func() (rootparticipation.Ledger, bool, error)
+}
diff --git a/internal/host/systemapply.go b/internal/host/systemapply.go
index 74145dfbb..c9f687a06 100644
--- a/internal/host/systemapply.go
+++ b/internal/host/systemapply.go
@@ -109,8 +109,7 @@ func RunSystemApply(args []string) {
log.Fatal("carlos system apply is the root oneshot behind carlos-system-update.service; it is not for operators")
}
if err := registerProductionLaunchVerifier(); err != nil {
- log.Print(err)
- return
+ log.Fatal(err)
}
ctx := context.Background()
channel, invalidChannel := systemChannel(os.Getenv)
@@ -262,6 +261,13 @@ func (a *systemApplier) file(name string) string { return filepath.Join(a.rootDi
// journal is closed, and re-firing OnFailure= over a finished recovery
// would only start it again with nothing left to do.
func (a *systemApplier) Run(ctx context.Context, recover bool) (retErr error) {
+ defer func() {
+ if errors.Is(retErr, context.DeadlineExceeded) {
+ if required, err := a.participationDisk().ParticipationRequired(context.WithoutCancel(ctx)); err == nil && required {
+ retErr = errors.Join(errSystemDeferred, retErr)
+ }
+ }
+ }()
reapStagingWrites(a.optDir, a.dataDir, a.etcDir, a.ops.logf)
if err := os.MkdirAll(a.rootDir(), 0o755); err != nil {
return err
@@ -515,7 +521,7 @@ func (a *systemApplier) install(ctx context.Context, req systemRequest, p *relea
// raised the floor or held a row since. activationGuard holds the floor
// and the holds still across the rename.
renamed := false
- if err := a.activationGuard(target, running, func() error {
+ if err := a.activationGuard(ctx, target, running, func(ctx context.Context) error {
renamed = true
return os.Rename(staged, systemBinaryPath(a.optDir))
}); err != nil {
@@ -938,59 +944,62 @@ func (e activationDeferred) Unwrap() error { return e.error }
// Lock order (merge of claim Plan 8): Run already holds the apply flock
// when this runs, so it is apply flock, then floor.lock, then the
// registry's write lock (freezeHolds). fn may close the claim release
-// floor's journal (closeGuardJournal), which takes no lock; nothing here
-// may take the apply flock (see withFloorLock).
+// floor's journal (closeGuardJournal), reusing the outer release token
+// before participation exclusion; nothing here may take the apply flock.
//
// running is the release on disk, read by the caller before any lock,
// since reading it runs a binary. A refusal or deferral comes back typed;
// fn's own error comes back as it is.
-func (a *systemApplier) activationGuard(target, running string, fn func() error) error {
- step := func() error {
- if a.beforeSwap != nil {
- a.beforeSwap()
+func (a *systemApplier) activationGuard(ctx context.Context, target, running string, fn func(context.Context) error) error {
+ guard := func(ctx context.Context) error {
+ step := func() error {
+ if a.beforeSwap != nil {
+ a.beforeSwap()
+ }
+ return fn(ctx)
}
- return fn()
- }
- locked := false
- err := withFloorLock(a.dataDir, true, func(*movesDir) error {
- locked = true
- if err := seedsActivationRefusal(target, a.dataDir); err != nil {
+ locked := false
+ err := withFloorLock(a.dataDir, true, func(*movesDir) error {
+ locked = true
+ if err := seedsActivationRefusal(target, a.dataDir); err != nil {
+ return err
+ }
+ floor, err := readLocalFloor(a.dataDir)
+ if err != nil {
+ return refuse("moves protocol floor: the floor on this box cannot be read: %v", err)
+ }
+ if floor == nil {
+ return step()
+ }
+ if err := floorRefusal(target, floor); err != nil {
+ return err
+ }
+ frozen := false
+ err = a.freezeHolds(func(held []registry.Route) error {
+ frozen = true
+ if err := holdRefusal(target, running, held); err != nil {
+ return err
+ }
+ return step()
+ })
+ if !frozen {
+ return refuse("a participant in a held operation does not downgrade, and the registry cannot say whether this box is one: %v", err)
+ }
return err
- }
- floor, err := readLocalFloor(a.dataDir)
- if err != nil {
- return refuse("moves protocol floor: the floor on this box cannot be read: %v", err)
- }
- if floor == nil {
- return step()
- }
- if err := floorRefusal(target, floor); err != nil {
+ })
+ if locked {
return err
}
- frozen := false
- err = a.freezeHolds(func(held []registry.Route) error {
- frozen = true
- if err := holdRefusal(target, running, held); err != nil {
- return err
- }
+ if errors.Is(err, errFloorLockBusy) {
+ return activationDeferred{errors.New("moves protocol floor: another writer holds the floor lock on this box; this waits for the next run")}
+ }
+ if floorLockCannotExist(err) && !a.floorArmed() {
+ a.ops.logf("system apply: moves protocol floor lock: %v (no floor is armed, and none can be on this box; continuing)", err)
return step()
- })
- if !frozen {
- return refuse("a participant in a held operation does not downgrade, and the registry cannot say whether this box is one: %v", err)
}
- return err
- })
- if locked {
- return err
- }
- if errors.Is(err, errFloorLockBusy) {
- return activationDeferred{errors.New("moves protocol floor: another writer holds the floor lock on this box; this waits for the next run")}
- }
- if floorLockCannotExist(err) && !a.floorArmed() {
- a.ops.logf("system apply: moves protocol floor lock: %v (no floor is armed, and none can be on this box; continuing)", err)
- return step()
+ return refuse("moves protocol floor: the floor on this box cannot be read: %v", err)
}
- return refuse("moves protocol floor: the floor on this box cannot be read: %v", err)
+ return a.participationDisk().WithParticipation(ctx, func(ctx context.Context, _ rootparticipation.Ledger) error { return guard(ctx) }, func() error { return guard(ctx) })
}
// floorRefusal is the floor half of the rule.
@@ -1566,7 +1575,7 @@ func (a *systemApplier) rollback(ctx context.Context, req systemRequest, reason
// rule first), closed through closeGuardRollback (the claim release
// floor's completion of a rolled-back transaction) inside it.
var closeErr error
- if gerr := a.activationGuard(restored, restored, func() error {
+ if gerr := a.activationGuard(ctx, restored, restored, func(ctx context.Context) error {
closeErr = a.closeGuardRollback(ctx)
return nil
}); gerr != nil {
@@ -1584,7 +1593,7 @@ func (a *systemApplier) rollback(ctx context.Context, req systemRequest, reason
// rollback failure (the journal is already gone by then, unless the
// armed floor refused to record the rollback, which keeps it).
var closeErr error
- if err := a.activationGuard(restored, restored, func() error {
+ if err := a.activationGuard(ctx, restored, restored, func(ctx context.Context) error {
if err := a.writeResult(req, systemOutcomeRolledBack, reason); err != nil {
return err
}
@@ -1633,7 +1642,7 @@ func (a *systemApplier) restoreFiles(ctx context.Context, j *systemJournal) (boo
return bootstrapPlan{}, err
}
renamed := false
- if err := a.activationGuard(prevVersion, running, func() error {
+ if err := a.activationGuard(ctx, prevVersion, running, func(ctx context.Context) error {
renamed = true
if err := os.Rename(restore, systemBinaryPath(a.optDir)); err != nil {
return err
@@ -1785,7 +1794,7 @@ func (a *systemApplier) recoverIfNeeded(ctx context.Context) (bool, error) {
// rule again, under the guard, at the step itself, since the
// floor can rise while the plan is staged (fix round 2).
finished := false
- err = a.activationGuard(j.Target, j.Target, func() error {
+ err = a.activationGuard(ctx, j.Target, j.Target, func(ctx context.Context) error {
finished = true
return a.finish(ctx, req, j.Target, plan)
})
@@ -1836,7 +1845,7 @@ func (a *systemApplier) recoverIfNeeded(ctx context.Context) (bool, error) {
// the restore's rename keeps the journal, and the next run judges again.
// closeGuardRollback's own error is main's, returned as main returns it.
var closeErr error
- if err := a.activationGuard(restored, restored, func() error {
+ if err := a.activationGuard(ctx, restored, restored, func(ctx context.Context) error {
if err := a.writeResult(req, systemOutcomeRolledBack, "interrupted at "+j.Phase); err != nil {
return err
}
diff --git a/internal/instancectl/claimexec.go b/internal/instancectl/claimexec.go
index 528584872..567ba9f0c 100644
--- a/internal/instancectl/claimexec.go
+++ b/internal/instancectl/claimexec.go
@@ -18,6 +18,7 @@ import (
type ClaimExecPermit struct {
Reservation, Inputs, BootID string
SafeUntil int64
+ Containment uint8 `json:",omitempty"`
}
func (p ClaimExecPermit) Valid() bool {
@@ -29,7 +30,7 @@ const ClaimExecSnapshot = "/run/carlos/claim-exec.json"
const ClaimExecBootID = "/run/carlos/claim-boot-id"
func (u ClaimUnits) StartReserved(ctx context.Context, in ClaimUnit, l Launch, p ClaimExecPermit, check func() error) (ClaimUnitStarted, error) {
- if !p.Valid() || check == nil {
+ if !p.Valid() || p.Containment != 1 || check == nil {
return ClaimUnitStarted{}, ErrClaimUnit
}
return u.start(ctx, in, &l, check, &p)
diff --git a/internal/instancectl/claimexec_rev3_linux_test.go b/internal/instancectl/claimexec_rev3_linux_test.go
index 753c1705b..e3c97f208 100644
--- a/internal/instancectl/claimexec_rev3_linux_test.go
+++ b/internal/instancectl/claimexec_rev3_linux_test.go
@@ -24,7 +24,7 @@ func TestRev3ReservedUnitChecksAtTrampolineBoundary(t *testing.T) {
u.ExeRoot = filepath.Join(dir, "exe")
u.UnitRoot = filepath.Join(dir, "unit")
u.DataRoot = filepath.Join(dir, "data")
- p := ClaimExecPermit{Reservation: "a:main", Inputs: "hash", BootID: "boot", SafeUntil: 200}
+ p := ClaimExecPermit{Reservation: "a:main", Inputs: "hash", BootID: "boot", SafeUntil: 200, Containment: 1}
setup := false
checks := 0
received := make(chan error, 1)
diff --git a/internal/instanceexec/claim_generation_linux.go b/internal/instanceexec/claim_generation_linux.go
new file mode 100644
index 000000000..e38e5990c
--- /dev/null
+++ b/internal/instanceexec/claim_generation_linux.go
@@ -0,0 +1,62 @@
+//go:build linux
+
+package instanceexec
+
+import (
+ "runtime"
+ "unsafe"
+
+ "github.com/carlosframework/platform/internal/instancectl"
+ "golang.org/x/sys/unix"
+)
+
+// ScopeClaimGeneration closes descriptor-import routes before the claim
+// handshake. Mount isolation alone cannot stop SCM_RIGHTS from a pathname
+// socket in the shared instance directory. The locked launching thread and
+// every tenant descendant retain this filter across fork and exec.
+func ScopeClaimGeneration() error {
+ var arch uint32
+ switch runtime.GOARCH {
+ case "amd64":
+ arch = unix.AUDIT_ARCH_X86_64
+ case "arm64":
+ arch = unix.AUDIT_ARCH_AARCH64
+ default:
+ return instancectl.ErrClaimUnit
+ }
+ filter := []unix.SockFilter{
+ {Code: unix.BPF_LD | unix.BPF_W | unix.BPF_ABS, K: 4},
+ {Code: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jt: 1, K: arch},
+ {Code: unix.BPF_RET | unix.BPF_K, K: unix.SECCOMP_RET_KILL_PROCESS},
+ {Code: unix.BPF_LD | unix.BPF_W | unix.BPF_ABS, K: 0},
+ // Reject x32 as well as nonnative audit architectures.
+ {Code: unix.BPF_JMP | unix.BPF_JSET | unix.BPF_K, Jf: 1, K: 0x40000000},
+ {Code: unix.BPF_RET | unix.BPF_K, K: unix.SECCOMP_RET_ERRNO | uint32(unix.EPERM)},
+ }
+ for _, call := range []uint32{unix.SYS_RECVMSG, unix.SYS_RECVMMSG, unix.SYS_PTRACE, unix.SYS_PROCESS_VM_READV, unix.SYS_PROCESS_VM_WRITEV, unix.SYS_PIDFD_GETFD, unix.SYS_OPEN_BY_HANDLE_AT, unix.SYS_IO_URING_SETUP, unix.SYS_IO_URING_ENTER, unix.SYS_IO_URING_REGISTER, unix.SYS_SETNS, unix.SYS_UNSHARE} {
+ filter = append(filter,
+ unix.SockFilter{Code: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jf: 1, K: call},
+ unix.SockFilter{Code: unix.BPF_RET | unix.BPF_K, K: unix.SECCOMP_RET_ERRNO | uint32(unix.EPERM)})
+ }
+ filter = append(filter,
+ unix.SockFilter{Code: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jf: 1, K: unix.SYS_CLONE3},
+ unix.SockFilter{Code: unix.BPF_RET | unix.BPF_K, K: unix.SECCOMP_RET_ERRNO | uint32(unix.ENOSYS)},
+ unix.SockFilter{Code: unix.BPF_JMP | unix.BPF_JEQ | unix.BPF_K, Jf: 3, K: unix.SYS_CLONE},
+ unix.SockFilter{Code: unix.BPF_LD | unix.BPF_W | unix.BPF_ABS, K: 16},
+ unix.SockFilter{Code: unix.BPF_JMP | unix.BPF_JSET | unix.BPF_K, Jf: 1, K: unix.CLONE_NEWCGROUP | unix.CLONE_NEWIPC | unix.CLONE_NEWNET | unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWUSER | unix.CLONE_NEWUTS},
+ unix.SockFilter{Code: unix.BPF_RET | unix.BPF_K, K: unix.SECCOMP_RET_ERRNO | uint32(unix.EPERM)})
+ filter = append(filter, unix.SockFilter{Code: unix.BPF_RET | unix.BPF_K, K: unix.SECCOMP_RET_ALLOW})
+ if err := unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil {
+ return err
+ }
+ program := unix.SockFprog{Len: uint16(len(filter)), Filter: &filter[0]}
+ unsynced, _, errno := unix.Syscall(unix.SYS_SECCOMP, unix.SECCOMP_SET_MODE_FILTER, unix.SECCOMP_FILTER_FLAG_TSYNC, uintptr(unsafe.Pointer(&program)))
+ runtime.KeepAlive(filter)
+ if errno != 0 {
+ return errno
+ }
+ if unsynced != 0 {
+ return instancectl.ErrClaimUnit
+ }
+ return nil
+}
diff --git a/internal/instanceexec/claim_generation_linux_test.go b/internal/instanceexec/claim_generation_linux_test.go
new file mode 100644
index 000000000..a26e4b3ef
--- /dev/null
+++ b/internal/instanceexec/claim_generation_linux_test.go
@@ -0,0 +1,84 @@
+//go:build linux
+
+package instanceexec
+
+import (
+ "errors"
+ "os"
+ "os/exec"
+ "runtime"
+ "testing"
+
+ "golang.org/x/sys/unix"
+)
+
+func TestPlan14ClaimGenerationDescriptorBoundary(t *testing.T) {
+ if os.Getenv("PLAN14_CLAIM_GENERATION_CHILD") == "1" {
+ runtime.LockOSThread()
+ if err := ScopeClaimGeneration(); err != nil {
+ t.Fatal(err)
+ }
+ for _, call := range []uintptr{unix.SYS_RECVMSG, unix.SYS_RECVMMSG, unix.SYS_PTRACE, unix.SYS_PIDFD_GETFD, unix.SYS_OPEN_BY_HANDLE_AT, unix.SYS_IO_URING_SETUP, unix.SYS_IO_URING_ENTER, unix.SYS_IO_URING_REGISTER, unix.SYS_SETNS, unix.SYS_UNSHARE} {
+ _, _, err := unix.RawSyscall6(call, ^uintptr(0), 0, 0, 0, 0, 0)
+ if err != unix.EPERM {
+ t.Fatalf("syscall %d: %v; want EPERM", call, err)
+ }
+ }
+ data, control := make([]byte, 1), make([]byte, unix.CmsgSpace(4))
+ if _, _, _, _, err := unix.Recvmsg(3, data, control, 0); !errors.Is(err, unix.EPERM) {
+ t.Fatal("descriptor receipt permitted", err)
+ }
+ if n, err := unix.Read(3, data); err != nil || n != 1 || data[0] != 'x' {
+ t.Fatal("ordinary socket read failed", n, err)
+ }
+ return
+ }
+ sockets, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ parent, child := os.NewFile(uintptr(sockets[0]), "parent"), os.NewFile(uintptr(sockets[1]), "child")
+ defer parent.Close()
+ defer child.Close()
+ file, err := os.CreateTemp(t.TempDir(), "staged")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer file.Close()
+ if err := unix.Sendmsg(int(parent.Fd()), []byte{'x'}, unix.UnixRights(int(file.Fd())), nil, 0); err != nil {
+ t.Fatal(err)
+ }
+ cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14ClaimGenerationDescriptorBoundary$")
+ cmd.Env = append(os.Environ(), "PLAN14_CLAIM_GENERATION_CHILD=1")
+ cmd.ExtraFiles = []*os.File{child}
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("boundary child: %v\n%s", err, out)
+ }
+}
+
+func TestPlan14ClaimGenerationNamespaceBoundary(t *testing.T) {
+ if os.Getenv("PLAN14_CLAIM_NAMESPACE_CHILD") == "1" {
+ runtime.LockOSThread()
+ if err := ScopeClaimGeneration(); err != nil {
+ t.Fatal(err)
+ }
+ _, _, err := unix.RawSyscall6(unix.SYS_CLONE, uintptr(unix.CLONE_NEWUSER|unix.CLONE_THREAD), 0, 0, 0, 0, 0)
+ if err != unix.EPERM {
+ t.Fatal("namespace clone permitted", err)
+ }
+ _, _, err = unix.RawSyscall6(unix.SYS_CLONE3, 0, 0, 0, 0, 0, 0)
+ if err != unix.ENOSYS {
+ t.Fatal("clone3 permitted", err)
+ }
+ cmd := exec.Command("/bin/true")
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("ordinary fork/exec: %v %s", err, out)
+ }
+ return
+ }
+ cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14ClaimGenerationNamespaceBoundary$")
+ cmd.Env = append(os.Environ(), "PLAN14_CLAIM_NAMESPACE_CHILD=1")
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("namespace boundary: %v\n%s", err, out)
+ }
+}
diff --git a/internal/instanceexec/claimexec_linux.go b/internal/instanceexec/claimexec_linux.go
index 74897413c..aa4156245 100644
--- a/internal/instanceexec/claimexec_linux.go
+++ b/internal/instanceexec/claimexec_linux.go
@@ -16,7 +16,12 @@ import (
)
func scopedClaimInit(argv0 string, argv, env []string, snapshot string) error {
- return runInitChecked(argv0, argv, env, ScopeAbstractUnix, func() error {
+ return runInitChecked(argv0, argv, env, func() error {
+ if err := ScopeAbstractUnix(); err != nil {
+ return err
+ }
+ return ScopeClaimGeneration()
+ }, func() error {
return checkClaimExec(snapshot, instancectl.ClaimExecSocket, 0, claimBootTime)
})
}
@@ -52,7 +57,7 @@ func checkClaimExec(snapshot, socket string, owner uint32, now func() (string, i
if err = json.NewDecoder(io.LimitReader(f, 4096)).Decode(&want); err != nil {
return err
}
- if !want.Valid() {
+ if !want.Valid() || want.Containment != 1 {
return instancectl.ErrClaimUnit
}
conn, err := net.DialTimeout("unix", socket, 5*time.Second)
diff --git a/internal/instanceexec/claimexec_rev3_linux_test.go b/internal/instanceexec/claimexec_rev3_linux_test.go
index 7838677ac..ccfa16cd5 100644
--- a/internal/instanceexec/claimexec_rev3_linux_test.go
+++ b/internal/instanceexec/claimexec_rev3_linux_test.go
@@ -35,7 +35,7 @@ func TestRev3TrampolineExpiresBeforeForkExec(t *testing.T) {
t.Fatal(err)
}
defer os.RemoveAll(dir)
- p := instancectl.ClaimExecPermit{Reservation: "attempt:main", Inputs: "digest", BootID: "boot", SafeUntil: 200}
+ p := instancectl.ClaimExecPermit{Reservation: "attempt:main", Inputs: "digest", BootID: "boot", SafeUntil: 200, Containment: 1}
b, _ := json.Marshal(p)
if err = os.WriteFile(filepath.Join(dir, "permit"), b, 0444); err != nil {
t.Fatal(err)
@@ -74,14 +74,17 @@ func TestRev3TrampolineExpiresBeforeForkExec(t *testing.T) {
}
}
func TestRev3BoundaryTrustAndPermit(t *testing.T) {
- for _, name := range []string{"valid", "expired", "wrong boot", "wrong reservation", "writable", "missing", "symlink"} {
+ for _, name := range []string{"valid", "expired", "wrong boot", "wrong reservation", "old containment", "old snapshot", "writable", "missing", "symlink"} {
t.Run(name, func(t *testing.T) {
dir, err := os.MkdirTemp("", "rp")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
- p := instancectl.ClaimExecPermit{Reservation: "attempt:main", Inputs: "digest", BootID: "boot", SafeUntil: 200}
+ p := instancectl.ClaimExecPermit{Reservation: "attempt:main", Inputs: "digest", BootID: "boot", SafeUntil: 200, Containment: 1}
+ if name == "old snapshot" {
+ p.Containment = 0
+ }
path := filepath.Join(dir, "permit")
b, _ := json.Marshal(p)
if err = os.WriteFile(path, b, 0444); err != nil {
@@ -112,6 +115,9 @@ func TestRev3BoundaryTrustAndPermit(t *testing.T) {
if json.NewDecoder(c).Decode(&q) != nil {
return
}
+ if name == "old containment" {
+ p.Containment = 0
+ }
if name == "wrong reservation" {
p.Reservation = "other"
}
diff --git a/internal/provision/plan14_e_write_census_test.go b/internal/provision/plan14_e_write_census_test.go
index d301ee7e7..c1e32ba31 100644
--- a/internal/provision/plan14_e_write_census_test.go
+++ b/internal/provision/plan14_e_write_census_test.go
@@ -3,7 +3,7 @@ package provision
// Track E overlays the reviewed Part 1 write census without changing its baseline.
var plan14EWriteCensusOverlay = []censusEntry{
{"internal/host/rootgeneration_requirements.go", "generationRequiredInventory", "key-site", "key:RecordKey", "0ceac8b867717d959ccc6a66a44505a177ad56d09bd79d826c89a506eb8d00a8"},
- {"internal/host/rootgeneration_inventory.go", "generationReconciler.reconcile", "key-site", "key:RecordKey", "f3bcd466fa3bc92b50f93f6986b4c566db198247ef0e37fe6daf3e2fee760a92"},
+ {"internal/host/rootgeneration_inventory.go", "generationReconciler.reconcile", "key-site", "key:RecordKey", "9f400d6ea9358dbc0cf8468d4818bb87fc4c3d85fc83876904a5c764979439b8"},
{"internal/host/rootgenerations_launch.go", "rootGenerationSource.resolve", "key-site", "key:RecordKey", "d69a3c0c86b03d5469708ac8bf084f4f516c0b61a18519d46cc3c0a0616f5381"},
{"internal/historyreadback/workflow.go", "Operation.deliverGrant", "nonrecord", "sel:Put", "fb410ef6274ef7de0997a804c837ad1ecf29d8826ba1c4dcda00c2ae986fc434"},
{"internal/host/rootinventory.go", "ClaimRootHTTP.commitRootInventoryPending", "key-site", "key:RecordKey", "b08d3c55f02e0b9e9c39455ce37f854a254c5f65af9fc74443fee414e2756f1b"},
diff --git a/internal/provision/testdata/plan14-root-reads-e.tsv b/internal/provision/testdata/plan14-root-reads-e.tsv
index 3e02081d0..e57ed2779 100644
--- a/internal/provision/testdata/plan14-root-reads-e.tsv
+++ b/internal/provision/testdata/plan14-root-reads-e.tsv
@@ -1,6 +1,6 @@
# Track E integration after merge3. Earlier baselines, overlays and legacy fingerprints are immutable.
internal/host/agent.go runAdoptUnderRootRelease authority B3,E1,E2 E13: signed recovery allows unknown history but preserves usable refusals, including retained originals; verified later promotion clears only D1. Health recording is independent of program recording; minting requires a census. Fixtures: TestPlan14RecoveryUnknownHistory, TestPlan14RecoveryKnownRefusal, TestPlan14RecoveryOriginalRefusalWithUnknownHistory, TestPlan14RootHistoryVerifiedRetryBeforeAttempt, TestPlan14ProgramFailureStillRecordsHealth, TestPlan14RootHistoryMintCensus. E15: census-only root unit-restart consumption uses retained installations, canonical records, exact requests, invocation evidence and admission after durable intent under release exclusion. No-census effects use the original consumer. Fixtures: TestPlan14RootRestartProductionConsumption, TestPlan14RootRestartProductionHeldRollover, TestPlan14RootRestartProductionAdmissionAfterIntent, TestPlan14RootRestartProductionUnknown, TestPlan14RootRestartProductionSyncFailure, TestPlan14RootRestartProductionCrash, TestPlan14RootRestartRequestCensusGate. 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. whole:Route@10766,whole:Route@18194,whole:Route@18230,whole:Route@8068 71b09c1fdcab8e9dbb9c6f255cfa72acbf8a17f4ab2d959d8d2b7d7d0afe3ca9
-internal/host/agent.go runAdoptWithEnsureSystemUnits authority B3,C,D,E1,E2,E3,E4,F,J1 E1 selects the checked private history, canonical named-unit provider and root mint inventory under release exclusion with claims off or on; reconstruction and restart consumption remain incomplete. file:Open@191 9cdd0a958f5924c9c8d8c5613fd665f2c96a194cd11322adb6f0050fb183b541
+internal/host/agent.go runAdoptWithEnsureSystemUnits authority B3,C,D,E1,E2,E3,E4,F,J1 PROPOSED E45 / R-E45-3: root checks census presence before opening release exclusion; absent census uses main adoption with no release directory or lock. Retained census remains protected with either flag value. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. file:Open@176 51a47bdd9d229f9177ef5e278b4e8b6d3bba125b2db6c1b96b81317a8f495373
internal/host/namedrecovery.go namedUnitRecovery.repair authority E1,J1 E13: signed recovery allows unknown history but preserves usable refusals, including retained originals; verified later promotion clears only D1. Health recording is independent of program recording; minting requires a census. Fixtures: TestPlan14RecoveryUnknownHistory, TestPlan14RecoveryKnownRefusal, TestPlan14RecoveryOriginalRefusalWithUnknownHistory, TestPlan14RootHistoryVerifiedRetryBeforeAttempt, TestPlan14ProgramFailureStillRecordsHealth, TestPlan14RootHistoryMintCensus. file:Open@2642,registry:Channel@1814,whole:Route@1060,whole:Route@1822 566bc61eafb118d9f6fba29228c372b18c8729549ed9a0bccf32c61b360eb9c3
internal/host/namedrecovery_active.go namedUnitRecovery.recover authority B3,E1 E13: signed recovery allows unknown history but preserves usable refusals, including retained originals; verified later promotion clears only D1. Health recording is independent of program recording; minting requires a census. Fixtures: TestPlan14RecoveryUnknownHistory, TestPlan14RecoveryKnownRefusal, TestPlan14RecoveryOriginalRefusalWithUnknownHistory, TestPlan14RootHistoryVerifiedRetryBeforeAttempt, TestPlan14ProgramFailureStillRecordsHealth, TestPlan14RootHistoryMintCensus. file:Open@5504,registry:Channel@2558,registry:Unit@216,registry:Unit@233,registry:Version@3618,registry:Version@3724,whole:Route@1478,whole:Route@202,whole:Route@2560,whole:Route@3544 dabec20a8ea93c7d72b6d6c3a30a1ae7c99ffcee4de4c0416fba699734739b92
internal/host/proofbinding.go proofBinding.authorizedRecovery authority B3,E1 Independent signed recovery authority and retained attempt outcomes. Fixtures: TestPlan14SignedRestartWithoutHealthHistory, TestPlan14CapturedRollbackWithoutHealthHistory, TestPlan14IndependentRecoveryRetainsMintBarrier, TestPlan14ActiveRecoveryProcessCrash. a7a7cc9edf8437ed2a1fd4d4ed3d54e9ffab78b21ed3d2867a2254c7836e4f13
@@ -54,8 +54,8 @@ internal/release/roothistory_replace.go replacementReconstruction authority E1 S
internal/release/signedhead.go StageSignedHead authority E1 Canonical route is supplied by the root unit verifier. Verifies signed channel, digest ratchet and manifest/artifact before staging; unavailable pin census never authorizes restaging differing bytes. Fixtures: TestPlan14Incident20261006WithLedger and TestPlan14SignedNamedRecoveryAuthority. registry:Account@202,registry:Account@302,registry:Account@403,registry:App@214,registry:App@415,registry:Channel@222,registry:Host@434 ca02f04e55556ba0233dfa0848b0c533a9d9aa06cad6abd33cfd2968b3bd838a
internal/rootstate/link_linux.go ReadTrustedLink authority A,E1 Checks the full root-owned parent chain before descriptor-based live-link reading. Fixture: TestPlan14TrustedLiveLink; privileged acceptance remains required. file:Open@156 90f8e8de4e2c71f07b5d749cbe4bb1c1dfa426576a82138823449f56fc9ec5ab
internal/rootstate/link_linux.go directory.readLink authority A,E1 Pins the symlink inode with O_PATH and checks owner/type/link count before readlinkat. Fixture: TestPlan14TrustedLiveLink. file:Fstat@356,file:Openat@183,file:Readlinkat@554 a8559fe63dce48830916004ecfe7fbbe5d419568c7daf7a3073cf1b5bb12c3d7
-@coverage internal/host b5495e98a2ccc36bbb3e242aabfc623afa5a5ef248f778604c23607c002b88b0
-@coverage internal/release 23bf042522655b27434395d03f05f6ab5e0d86025f58431b842d286a58bda0b7
+@coverage internal/host ccde94dd6c888c98f4f78e71beb82af7f64f8fbaddc1c9cb0de032fe610426a7
+@coverage internal/release 6517cdba66a04ddf585b6bceb510402516a9bbb6ef37922b8d616c52ae781edf
@coverage internal/rootstate a773d6dbc0e4150e0054f7a94d0e1493c88d0d57f4f4530138b3427e2c217bc5
internal/release/roothistory_status.go RootHistory.MigrationStatus authority E1 Exact completion binds the accepted authorization digest from durable private history to enrolled-root signed readback. Pending originals, changed evidence and canonical sources cannot supply retirement completion. Fixtures: TestPlan14HistoryReconstructionCompletion, TestPlan14HistoryRetirementCompletion, TestPlan14ReadReconstructionCompletion, TestPlan14ReadRetirementCompletion, TestPlan14RecoveryRetirementProductionCallback, TestPlan14RootHistoryReconstructionCrash, TestPlan14HistoryRetirementProcessCrash. file:Read@1062,file:Read@3891 f0ab7ad2d2d70f8c22ebfd575f5a1c6931f7eba30ccdba80ad43d0efffc88154
internal/host/legacyrestart.go restartEffectFailed authority B3 E8 merge ruling 5: optional identity failures preserve the same restart outcome; no second restart; real not-up takes main rollback. Fixtures: TestPlan14ERecoveryRestartOutcome, TestPlan14ERecoveryBindingRefusalKeepsPromotedFix, TestPlan14ERecoveryNotUpPrecedesBinding. b68bb65884a14fe0cf78a65f56ded4ea41584bba99af9195827d1ea191b340a9
@@ -161,13 +161,13 @@ internal/host/rootrestart_requests_source.go type rootRestartRequestSource autho
internal/release/rootrestart_history.go RootRestarts.Bindings authority E1 E15: census-only root unit-restart consumption uses retained installations, canonical records, exact requests, invocation evidence and admission after durable intent under release exclusion. No-census effects use the original consumer. Fixtures: TestPlan14RootRestartProductionConsumption, TestPlan14RootRestartProductionHeldRollover, TestPlan14RootRestartProductionAdmissionAfterIntent, TestPlan14RootRestartProductionUnknown, TestPlan14RootRestartProductionSyncFailure, TestPlan14RootRestartProductionCrash, TestPlan14RootRestartRequestCensusGate. file:Read@324 d22b7e6b5710ca0025639395101933bec5ce0bd38768ebcf44a0805f1f159b08
internal/host/claimretire_transport.go type ClaimRootHTTP authority E1 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. e87bb168975b9a7c4334de1c1fc027c556d69597122a6134770e770c719a45be
internal/host/claimunit.go claimUnitContent authority E1 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. 38dcfeb754659895bc28b024bc3186c6b12edeb17d087546507fa1299eb8e331
-internal/host/roothistory_creation.go productionCreationHistory authority E1 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. file:Open@295,file:Open@374 b4f4f82422cf0b1d874be7a0d9ee4a2032a4704b022bf73b8264097c2a53705c
+internal/host/roothistory_creation.go productionCreationHistory authority E1 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. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. file:Open@289,file:Open@368 a26026dd565e4d1d046591fa1534ded1b64ccb8eafbef65a4d4ec899c0c471d3
internal/host/roothistory_creation.go rootHistoryCreation authority E1 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. file:Open@190,file:Open@300,whole:Route@535 49c28b2886b2ef03b818b2ad7d9a7f6fec65637f86e4d2f94e54394cac032bcc
internal/host/roothistory_creation.go rootHistoryInstallation authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. 5ca3a1163ea109b6e0ed1a9477023340cf39e6a810761133f1117d311e903b84
internal/host/roothistory_creation.go rootRestartRequestSource.creation authority E1 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. registry:Account@219,registry:App@243,registry:Host@133,registry:Host@194 af79a66b746b0f42388010b38ea8130dca6a8d29c4ba5623bfc9c3cc40dd9601
internal/host/roothistory_creation.go type rootHistoryAccess authority E1 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. 9c97bcd7878d79022ed942d8e6c00e944a00e0fa27a1bc5c732b4f4c10f4e9dd
internal/host/roothistory_launch.go ClaimLaunchRoot.rootInstalledProgram authority E1 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. file:OpenFile@541,file:Read@1043 e8d530dd1b8f613dfbd41d34a1b08b19aa3f74e0522c8f41db768ccd4e9e80d2
-internal/host/rootinventory.go ClaimRootHTTP.commitRootInventoryPending authority E2 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. file:Read@1383,file:Read@3423,registry:Account@4787,registry:Account@4802,registry:App@4823,registry:App@4834,registry:ClaimIncarnation@4293,registry:ClaimOwner@4343,registry:ClaimOwner@5398,registry:ClaimOwnership@4885,registry:ClaimOwnership@4907,registry:ClaimOwnership@5712,registry:ClaimOwnership@7795,registry:ClaimRowID@4507,registry:ClaimRowID@4584,registry:ClaimStamp@4321,registry:DBPath@4851,registry:DBPath@4865,registry:Host@4757,registry:Host@4769,whole:ClaimOwnership@4889,whole:ClaimOwnership@4917,whole:ClaimOwnership@5722,whole:ClaimOwnership@7806,whole:Route@3615,whole:Route@3998,whole:Route@4014,whole:Route@4069,whole:Route@4720,whole:Route@4733,whole:Route@4742,whole:Route@4980,whole:Route@5212,whole:Route@5960,whole:Route@6096,whole:Route@6320,whole:Route@7639,whole:Route@9046,whole:Route@9107,whole:Route@9647 b08d3c55f02e0b9e9c39455ce37f854a254c5f65af9fc74443fee414e2756f1b
+internal/host/rootinventory.go ClaimRootHTTP.commitRootInventoryPending authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. file:Read@1383,file:Read@3423,registry:Account@4787,registry:Account@4802,registry:App@4823,registry:App@4834,registry:ClaimIncarnation@4293,registry:ClaimOwner@4343,registry:ClaimOwner@5398,registry:ClaimOwnership@4885,registry:ClaimOwnership@4907,registry:ClaimOwnership@5712,registry:ClaimOwnership@7795,registry:ClaimRowID@4507,registry:ClaimRowID@4584,registry:ClaimStamp@4321,registry:DBPath@4851,registry:DBPath@4865,registry:Host@4757,registry:Host@4769,whole:ClaimOwnership@4889,whole:ClaimOwnership@4917,whole:ClaimOwnership@5722,whole:ClaimOwnership@7806,whole:Route@3615,whole:Route@3998,whole:Route@4014,whole:Route@4069,whole:Route@4720,whole:Route@4733,whole:Route@4742,whole:Route@4980,whole:Route@5212,whole:Route@5960,whole:Route@6096,whole:Route@6320,whole:Route@7639,whole:Route@9046,whole:Route@9107,whole:Route@9647 b08d3c55f02e0b9e9c39455ce37f854a254c5f65af9fc74443fee414e2756f1b
internal/release/roothistory_creation.go RootHistory.ConsumeCreation authority E1 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. 7ec65199b5afc894ae4aefded6a40f485086d0f8a4c86391eca193f473dca2a1
internal/release/roothistory_creation.go RootHistory.FreshInstallation authority E1 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. 71b326a36350139c9c592b0b5b4b91409831fcffdd0c61d68cf5ec2bf54bbd5b
internal/release/roothistory_creation.go RootHistory.RecordCreation authority E1 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. 0b545ab63902eb8f2c8988991786c964a2af53d65eff46d8e53a0747da492b6d
@@ -190,14 +190,14 @@ internal/host/claimlaunch_root.go ClaimLaunchRoot.start authority E1,E2 E18: cla
internal/host/claimlaunch_root.go ClaimLaunchRoot.startRecorded authority E1,E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. file:Read@2813 f24033673d1d9b8c36304c204a271a7729e94b0e61620f3df6ea1f80537da4c2
internal/host/claimlaunch_root.go type ClaimLaunchRoot authority E1,E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. 14f50101580ce4b094e0a321db3db200994da9c6708745c2f7b3a8fd3409adfd
internal/host/rootgenerations_launch.go generationLaunchPermit.check authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. 788e600dd37fec60c5b98dc42b23c182c337df064088a041e2e20b68573942b5
-internal/host/rootgenerations_launch.go generationLaunchPermit.started authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. 8f8b0ab8295f39fd54dd8a7d78ef2653eb7bc3b2ac581c47f8657ed59de84326
+internal/host/rootgenerations_launch.go generationLaunchPermit.started authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. a7635d02d9cb682be64a0df3f8932336f5e874260898df9013ff7ffd7e3d7462
internal/host/rootgenerations_launch.go productionRootGenerations authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. file:Open@346 a1132403ace7213de9e12720d5a393a3bb85045d1d2c53f0c37ea2ae8e933b04
internal/host/rootgenerations_launch.go rootGenerationSource.reserve authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. c4269e65bcac751cd1a51a439e48eda77b055320554b3489d605fea47fa7f3a8
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. file:Open@2501 d69a3c0c86b03d5469708ac8bf084f4f516c0b61a18519d46cc3c0a0616f5381
internal/host/rootgenerations_launch.go type generationLaunchPermit authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. 52b30a79d7ced7875f435eff76d3c924ff4e460d3a2360cda5a9f12c407a2442
internal/host/rootgenerations_launch.go type rootGenerationSource authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. 59a336027ef7750d36f1a36a27cf3d0449395493c05789291c4ed9bc16724e5f
internal/release/rootgenerations.go GenerationBinding.valid authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. 636d5eb63f80ec3f3f2b87b859c96ce09700948ea3f3a5d58f04e5b1cf5ac3d3
-internal/release/rootgenerations.go GenerationProcess.valid authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. 6032100296677f5397c44226c3158eb75f336f0317af6994c4f3ebbfb2894861
+internal/release/rootgenerations.go GenerationProcess.valid authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. f5790b759daeea8f3dee287a84f2061d72927ebb5a48ea303917392061e180ea
internal/release/rootgenerations.go RootGenerations.Check authority E2 E20: exact reservation closure precedes retirement verification; retained tombstones forbid replay and require sync recommit. Retired records release only their own pins; completeness remains a separate mandatory verifier, still unavailable in production. Fixtures: TestPlan14GenerationRetirementReleasesExactPin, TestPlan14GenerationRetirementRefusals, TestPlan14GenerationClosedReplayRefused, TestPlan14GenerationRetirementProcessCrash, TestPlan14GenerationRetirementKeepsIndependentReference, TestPlan14GenerationClaimsOffRestageMatchesMain. file:Read@78 8ee72f46a5a45879f0625b90e11057d758b73586e5913067d2cd9493705fff83
internal/release/rootgenerations.go RootGenerations.Read authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. file:Read@206,file:Read@26 d77a3e6d7f8bdc2c16973910b3089aa7fb7c3c40b2d0b94f2ca7cdf32f9463b6
internal/release/rootgenerations.go RootGenerations.Reserve authority E2 E20: exact reservation closure precedes retirement verification; retained tombstones forbid replay and require sync recommit. Retired records release only their own pins; completeness remains a separate mandatory verifier, still unavailable in production. Fixtures: TestPlan14GenerationRetirementReleasesExactPin, TestPlan14GenerationRetirementRefusals, TestPlan14GenerationClosedReplayRefused, TestPlan14GenerationRetirementProcessCrash, TestPlan14GenerationRetirementKeepsIndependentReference, TestPlan14GenerationClaimsOffRestageMatchesMain. file:Read@409,file:Read@99 b6d4fe53dbd011641d69789f8f7926a6234fd819b9c455670b57e3385ffb0e58
@@ -207,7 +207,7 @@ internal/release/rootgenerations.go const RootGenerationsDir authority E2 E18: c
internal/release/rootgenerations.go generationKey authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. cdd7e0d8cdff508772ab54e421a4ab914dbb15ea5d97847f35a93544f0a9259a
internal/release/rootgenerations.go type GenerationBinding authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. 9bd76e1f6ef4d809df1a59719a1880215c910b0cab78449ad8cd79a678da401a
internal/release/rootgenerations.go type GenerationOwner authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. 6a6c3576622656c2bc4b65075c8f5d70b29968203101a1916926adc48b20bce5
-internal/release/rootgenerations.go type GenerationProcess authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. eeaafe23c23e4c95e58c45797065baa9ca6c784fab593e361e0d647ddaaa2957
+internal/release/rootgenerations.go type GenerationProcess authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. 81994fee584ed8780e94cf1f8e471316a04154176c5d29cd96acbe0870daadd5
internal/release/rootgenerations.go type GenerationReservation authority E2 E20: exact reservation closure precedes retirement verification; retained tombstones forbid replay and require sync recommit. Retired records release only their own pins; completeness remains a separate mandatory verifier, still unavailable in production. Fixtures: TestPlan14GenerationRetirementReleasesExactPin, TestPlan14GenerationRetirementRefusals, TestPlan14GenerationClosedReplayRefused, TestPlan14GenerationRetirementProcessCrash, TestPlan14GenerationRetirementKeepsIndependentReference, TestPlan14GenerationClaimsOffRestageMatchesMain. 1917db4836b3b18b45ce6bca1a55f6d5a8082e5de26af1710d0da25db0809551
internal/release/rootgenerations.go type RootGenerations authority E2 E18: claims-on launch records the canonical installation, deployment revision, signed build, executable and containing tree before start under release exclusion; both final spawn checks reread the same reservation and identities. Root process evidence never retires the reservation. This is the launch prerequisite only; complete pins and retirement remain pending. Fixtures: TestPlan14GenerationReservedBeforeClaimSpawn, TestPlan14GenerationLaunchRefusals, TestPlan14GenerationStartedSyncFailureRetainsPin, TestPlan14GenerationReservationProcessCrash, TestPlan14GenerationProductionWiring, TestPlan14GenerationReservationDurability, TestPlan14GenerationReservationConflicts, TestPlan14GenerationObservationKeepsReservation, TestPlan14GenerationRecordRefusals. 240860014efc91f51ca7e3e920ffb5cfa98076340dc0c9e9972c35051e5eca84
internal/release/rootgenerations.go validGeneration authority E2 E20: exact reservation closure precedes retirement verification; retained tombstones forbid replay and require sync recommit. Retired records release only their own pins; completeness remains a separate mandatory verifier, still unavailable in production. Fixtures: TestPlan14GenerationRetirementReleasesExactPin, TestPlan14GenerationRetirementRefusals, TestPlan14GenerationClosedReplayRefused, TestPlan14GenerationRetirementProcessCrash, TestPlan14GenerationRetirementKeepsIndependentReference, TestPlan14GenerationClaimsOffRestageMatchesMain. 25b4b51c18290f5e169e4c8225710cf3d27078b7a89101ab069bc7a838e75335
@@ -233,7 +233,7 @@ internal/host/rootgeneration_kernel_linux.go generationIDRef authority E2 E21: b
internal/host/rootgeneration_kernel_linux.go generationKernel.absent authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. file:Readlink@312 18269bf4b64e05138dff05e7894d4925bc24bcc012fa64a336723d981a9c52dd
internal/host/rootgeneration_kernel_linux.go generationKernel.boot authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. c3cc84ed2af21ee0e57e91160561a3e4996e75990df2617a14865b084b176ced
internal/host/rootgeneration_kernel_linux.go generationKernel.controllerGone authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. fa19ddebc7bf12297313e1431e4d8e534949032ff495b37be0038252bad355d3
-internal/host/rootgeneration_kernel_linux.go generationKernel.task authority E2 E25: keep the descriptor-listing handle open through inspection, including when scanning the current process. Check every listed descriptor against paths and artifact identities. Fixtures: TestPlan14GenerationKernelOwnDescriptors and TestPlan14GenerationKernelReferences. Root-runner acceptance passed at E27: TestPlan14GenerationKernelCensusRootRunner and TestPlan14GenerationKernelRetirementRootRunner. file:Readlink@607 f965b29343f3e5e5e464eaf1f4929f9ac4e5ff3c76b0e87750dad64839caece8
+internal/host/rootgeneration_kernel_linux.go generationKernel.task authority E2 E25: keep the descriptor-listing handle open through inspection, including when scanning the current process. Check every listed descriptor against paths and artifact identities. Fixtures: TestPlan14GenerationKernelOwnDescriptors and TestPlan14GenerationKernelReferences. Root-runner acceptance passed at E27: TestPlan14GenerationKernelCensusRootRunner and TestPlan14GenerationKernelRetirementRootRunner. PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. file:Readlink@962 cd86153e98742158e481fd838360b34fd357458531feeb9931a5c865ae120bd6
internal/host/rootgeneration_kernel_linux.go generationLinkRef authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. file:Readlink@128,file:Stat@423 1b9492ac2f9066f0ab6de429fafd707a7c826db76f3ac7b244d924385130ef5c
internal/host/rootgeneration_kernel_linux.go generationMajor authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. 48d037aef3349cd11492bb1cf7e9608f05bfde15e004f67f3bcad62a0518ab82
internal/host/rootgeneration_kernel_linux.go generationMaps authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. 7549bed808c1431fb382c48cddde8d1730713aba73b59467ad0677901d72becc
@@ -248,7 +248,7 @@ internal/host/rootgeneration_kernel_linux.go generationStart authority E2 E21: b
internal/host/rootgeneration_kernel_linux.go generationTree authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. file:Lstat@529,file:WalkDir@216 be154099cae5ba8d363bf4ec43d7debe437867aff98fbbd7c335b8ac11e5e6ac
internal/host/rootgeneration_kernel_linux.go generationUnescape authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. 4809282a7fbd2413b10e72886eb22d73abe245a5e267e43605982934e1cb6cc8
internal/host/rootgeneration_kernel_linux.go productionGenerationAbsence authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. bb7efb2afc24229ad55129c4342bb1a6fc98fbadb42267d7039b5f549c4bdbf6
-internal/host/rootgeneration_kernel_linux.go type generationKernel authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. 5a8673c8cf4c7b2e660688c4cd0a1043271159cebb11835da2b00bdfef798f69
+internal/host/rootgeneration_kernel_linux.go type generationKernel authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. 36ff9458289dc2ae5e168017bcd1788470a967e508dad3a26687151bdb36f284
internal/host/rootgeneration_kernel_linux.go type generationMount authority E2 E21: bounded kernel reader retains executable, sibling, descriptor, mapping and mount references; checks controller boot/start and cgroup emptiness. Unknown nonroot tasks, foreign namespaces, missing kernel state or renamed trees refuse. Fixtures: TestPlan14GenerationKernelReferences, TestPlan14GenerationKernelMappingIdentity, TestPlan14GenerationKernelControllerIdentity, TestPlan14GenerationKernelRunningExecutable, TestPlan14GenerationKernelCgroup, TestPlan14GenerationKernelRetainedFiles, TestPlan14GenerationKernelSiblingAlias. Real proc executable tests are not full privileged namespace acceptance. b4cd115a225b26a7906357404f8d04692a8d1b062662f80672280972049a8acb
internal/host/rootgeneration_kernel_other.go productionGenerationAbsence authority E2 E21: non-Linux production generation retirement refuses. Darwin, Windows and Linux ARM64 compile gates; no non-Linux retirement authority. de47d997019075326e1d5513d7576390d085566fd69edc35efd00f067fb1db24
internal/host/rootgeneration_retirement.go generationRetirement.closed authority E2 E25: validate installation, controller, exact unit and process before retaining an active or prelaunch pin. Only closed writers plus a settled unit reach durable retirement and independent kernel absence. Use the checked read-only journal and continue past active pins. Fixtures: TestPlan14GenerationRetirementKeepsActivePin, TestPlan14GenerationRetirementKeepsPrelaunchPin, TestPlan14GenerationRetirementContinuesPastActivePin, TestPlan14GenerationRetirementBindingProcessCrash, TestPlan14GenerationRetirementCensusGate. Running-tenant completeness remains unfinished. e69c69a74695791f8432e24a33397e49c22c47288e1bba66e56f11e605bf9881
@@ -290,18 +290,18 @@ internal/release/materializeheld.go ServeHeldStageRequests authority E2 E23: old
internal/release/materializeheld.go ServeHeldStageRequestsWithPins authority E2 E23: old held-stage relay forwards the root generation source before artifact writes and outside publication exclusion. Request fields retain their prior hint/authorization checks; only root pins authorize destructive staging on census boxes. No-census source yields empty pins. Fixtures: TestPlan14GenerationLegacyRelay, TestHeldStagePassIsANoOpWithoutRequests, TestHeldStagePassDropsAnUnauthorizedBuild, TestServeHeldStageRequestsRefusesStaleAndTraversal. Production source wiring is inspected; privileged completeness is not claimed. file:ReadDir@429 edb9927c53af09311b4dd3e277a34d5eba7ab048d343b9b05e25f0b24a48e79d
internal/release/materializeheld.go serveHeldStageRequest authority E2 E23: old held-stage relay forwards the root generation source before artifact writes and outside publication exclusion. Request fields retain their prior hint/authorization checks; only root pins authorize destructive staging on census boxes. No-census source yields empty pins. Fixtures: TestPlan14GenerationLegacyRelay, TestHeldStagePassIsANoOpWithoutRequests, TestHeldStagePassDropsAnUnauthorizedBuild, TestServeHeldStageRequestsRefusesStaleAndTraversal. Production source wiring is inspected; privileged completeness is not claimed. file:OpenFile@615,file:ReadAll@812,whole:Route@1676 bbda896335311f5e09a75542d82c57fb993da72cbc27875b74bda79bf0239280
internal/host/heldstage.go heldStagePass authority E2 E23: old held-stage relay forwards the root generation source before artifact writes and outside publication exclusion. Request fields retain their prior hint/authorization checks; only root pins authorize destructive staging on census boxes. No-census source yields empty pins. Fixtures: TestPlan14GenerationLegacyRelay, TestHeldStagePassIsANoOpWithoutRequests, TestHeldStagePassDropsAnUnauthorizedBuild, TestServeHeldStageRequestsRefusesStaleAndTraversal. Production source wiring is inspected; privileged completeness is not claimed. 759f041ad504375f3000acccc10b5fba80aaa44ab574c3491d1df2cb49a35087
-internal/host/rootgeneration_census_linux.go checkedGenerationTree authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. file:Lstat@467,file:Open@84,file:WalkDir@192 2a9f530f41143ce64723797ef4c5628b536caa6fefb11bcbe57adbec38f9a012
+internal/host/rootgeneration_census_linux.go checkedGenerationTree authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. file:Lstat@467,file:Open@84,file:WalkDir@192 edac1268de33c8cc16a7250792714aac92069abb0720b2c34d4edfd5b5e0d699
internal/host/rootgeneration_census_linux.go generationKernel.census authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. file:Open@170 5c43de2497824076701fb6275b86d629f0a1035be212d41754d90ba39c431140
internal/host/rootgeneration_inventory.go generationInventoryAt authority E2 E26: checked read-only payload enumeration includes inventory and revision orphans; completeness binds current and retained predecessor inventories, exact current revision and both source reads. Fixtures: TestPlan14GenerationPayloadCompleteness, TestPlan14GenerationPayloadRetainedPredecessor, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationPayloadInventory, TestPlan14GenerationPayloadReaderDoesNotInitialize. Checked root reader acceptance passed at E27; running-tenant containment remains unfinished. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Open@173,file:Open@533 57f4c32cd65afcefad93a41d5266e2b12acc624ff808f47d40e31a79bbc7a827
internal/hostbox/generationinventory.go LaunchJournal.GenerationLaunches authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. file:QueryContext@270 6694bd3a35b8d91f53e67134145c44854086f08593110ed1b905bca9587be290
internal/hostbox/generationinventory.go OpenGenerationLaunchReader authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. file:Lstat@218,file:Open@283 552d8629a7932c4ae5dca9d2beef4559f7e8a2864259d11e2ff98c699fdcd999
@coverage internal/hostbox c3d05fdb9b9e5ed56d29df815e73bd857e8a2a6f31168deecf628c0bf82ccf1a
-internal/host/rootgeneration_census_linux.go productionGenerationKernelCensus authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. 85f085029e0cd557c8ff8eb2d522fdf131c388229c14515fda0f45453d40eebb
+internal/host/rootgeneration_census_linux.go productionGenerationKernelCensus authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. e0c2041104c3a10eebc0183e67602069fd33954f8d0c8efb058261cd54551900
internal/host/rootgeneration_census_other.go productionGenerationKernelCensus authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. a1217cf881e9d05336fc16d6050f7f7a1a2184a0c20ff2988d0cf0f9012eaa45
internal/host/rootgeneration_inventory.go generationInstallation authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. 3e5e15d50752510acb44b10eb29621757a87bff54ceae015f704dd7f3a848ecb
internal/host/rootgeneration_inventory.go generationInventoryDigest authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. 36ff5a31c6a013ac907d85e59b754cf4088df4600646ba7df177cf14e990177a
internal/host/rootgeneration_inventory.go generationOwner authority E2 E24: independent root installation and launch enumeration, exact reservation reconciliation and fresh quiescent whole-version-tree kernel census; missing sources, open unreserved launches, foreign namespaces and uncontained tasks refuse. Production pin source certifies this evidence before returning pins. Fixtures: TestPlan14GenerationInventoryCompleteness, TestPlan14GenerationInventoryPinConsumer, TestPlan14GenerationInventoryLifecycle, TestPlan14GenerationInventoryProcessCrash, TestPlan14GenerationKernelCensusUnknownReference, TestPlan14GenerationLaunchInventory, TestPlan14GenerationReaderDoesNotInitialize, TestPlan14GenerationInstallationInventory. Checked production source/kernel witnesses passed on the E27 root runner. E29 physical-payload and installed-unit witnesses passed on the controller runner; E30 required-artifact witness pending. 69f89153837761f13116937b330ffbf406891299d5580296ea4aade905aa02ce
-internal/host/rootgeneration_inventory.go generationReconciler.reconcile authority E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. f3bcd466fa3bc92b50f93f6986b4c566db198247ef0e37fe6daf3e2fee760a92
+internal/host/rootgeneration_inventory.go generationReconciler.reconcile authority E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. 9f400d6ea9358dbc0cf8468d4818bb87fc4c3d85fc83876904a5c764979439b8
internal/host/rootgeneration_inventory.go openGenerationLaunchInventory authority E2 E33: census-only generation journal reads accept private root-owned ancestry without chmod or repair. Every ancestor remains descriptor-checked, root-owned and non-writable by group/other; leaves remain single-link regular root-owned files, checked for DB/WAL/SHM. Public-file callers remain unchanged. Fixtures: TestPlan14AuthorityFilePrivateAncestors, TestPlan14AuthorityFileAbsoluteBoundary; TestPlan14GenerationPrivateJournalRootRunner and TestPlan14GenerationRequiredArtifactsRootRunner are runner-pending. Pre-flight 76, 78, 82 and 89. 2b54531e16282caeb0ddbc1dc92b18d6cb2db652589eb3757daefe10daa63a53
internal/host/rootgeneration_inventory.go productionGenerationInventory authority E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. 32fe8188ec5a22297f4e0aa68f2741839461637a2ef2e7a282f9c5115141db6d
internal/host/rootgeneration_inventory.go type generationInventory authority E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. 7a1e16370c99823e83e11e7824162082f85abf82e5dd5e18f029c82ab9e26f08
@@ -318,10 +318,10 @@ internal/host/rootgeneration_payload_other.go generationPayloadFiles authority E
internal/host/rootgeneration_requirements.go generationRequiredInventory authority E2 E37: retain independently initialized state-directory and timer requirements in the root installation transaction; preserve them through crash/reopen and rebind. Unknown history cannot certify completeness. Recommit under root-host exclusion before generation use. Canonical record and root retained obligations constrain agent candidate lists; timer enumeration is deny-only. Pre-flight 33, 80, 83. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationRegistryRows, TestPlan14RootTimerInventory, TestPlan14InventoryRequirementsRegistration, TestPlan14InventoryRequirementsUnknownAndRatchet, TestPlan14InventoryArtifactsCrash, TestPlan14InventoryRequirementsHistoryIdentity. Changed RequiredArtifacts and ExactRetirement root-runner witnesses await acceptance; remaining E2 launch/parked/containment integration is incomplete. 0ceac8b867717d959ccc6a66a44505a177ad56d09bd79d826c89a506eb8d00a8
internal/host/rootgeneration_requirements.go type generationRecord authority E2 E30: derive route and ordinary-instance state-container requirements from schema- and shape-validated canonical deployment records bound to retained installation incarnation; include record contents and ETag in both source reads and the certificate digest. Missing, mismatched, omitted or changed evidence refuses without retiring pins. Fixtures: TestPlan14GenerationRequiredArtifacts, TestPlan14GenerationProductionModeGate. Production fixed-path witness TestPlan14GenerationRequiredArtifactsRootRunner is runner-pending. Timer classes, parked artifacts, positive empty-installation evidence and running-tenant containment remain incomplete. ff50bc959af62afbd73b01e9640346f9c79d6b6578e03cb3a7d6bdff4d90fc28
internal/host/rootgeneration_rows.go generationRegistryInventory authority E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. registry:ClaimOwnership@2199,registry:ClaimOwnership@2578,registry:ClaimRowID@2631,registry:Host@2177,registry:Host@2556,registry:Name@2497,registry:Registration@2609,whole:ClaimOwnership@1475,whole:ClaimOwnership@1539,whole:ClaimOwnership@1553,whole:ClaimOwnership@1567,whole:ClaimOwnership@2076,whole:ClaimOwnership@2205,whole:ClaimOwnership@2223,whole:ClaimOwnership@2278,whole:ClaimOwnership@2527,whole:ClaimOwnership@2584,whole:ClaimOwnership@2602,whole:ClaimOwnership@2697,whole:ClaimOwnership@2725,whole:Route@2094,whole:Timer@2383,whole:Timer@2461 b4105502dc32425e8566a988cb45f11289b32c4ca4d5d78ab249c31f8d164ec7
-@coverage internal/registry 9693eedd8f0fe1dd275637df93be53398ccb4ab7e2b9934f8a0e046a1535c8f6
+@coverage internal/registry 3e9a5115c72f6fec616b43e24630d5f9d60bffbc591c7b3ebd178f2a4855f7ff
internal/registry/remote.go serveRemote authority E2 Mixed declaration: E2 adds only the ArtifactHosts read operation; all existing dispatch and write behavior stays legacy. Original immutable legacy fingerprints retained. E32: census-only E2 completeness discovers the bounded union of route and timer hosts, including inactive orphan timers, through the service-user read-only parser. Unknown hosts refuse even with an empty root installation list; discovery supplies no authority and never removes a retained obligation. Pre-flight 33, 80, 82, 83, 89 and 90. Fixtures: TestArtifactHosts, TestRemoteReadOnlyWALRegistry, TestPlan14GenerationRegistryRows, TestPlan14GenerationRegistryProductionReader; extended TestPlan14GenerationRequiredArtifactsRootRunner remains runner-pending. file:Open@332,registry:CallbackError@1712,registry:CallbackFailed@1669,registry:Deadline@1295,registry:Deadline@1369,registry:Method@1022,registry:Method@1043,registry:Method@1070,registry:Method@1604,whole:remoteRequest@1426,whole:remoteRequest@835,whole:remoteRequest@922,whole:remoteResponse@1148,whole:remoteResponse@1194,whole:remoteResponse@1488,whole:remoteResponse@1786,whole:remoteResponse@1822,whole:remoteResponse@392,whole:remoteResponse@431 40254c291f7b1a89b28e39eb1e4ffb3aa5852fd00de076ced1d3f7c48885e6dd
internal/registry/artifacthosts.go Registry.ArtifactHosts authority E2 E32: census-only E2 completeness discovers the bounded union of route and timer hosts, including inactive orphan timers, through the service-user read-only parser. Unknown hosts refuse even with an empty root installation list; discovery supplies no authority and never removes a retained obligation. Pre-flight 33, 80, 82, 83, 89 and 90. Fixtures: TestArtifactHosts, TestRemoteReadOnlyWALRegistry, TestPlan14GenerationRegistryRows, TestPlan14GenerationRegistryProductionReader; extended TestPlan14GenerationRequiredArtifactsRootRunner remains runner-pending. file:Query@196,registry:Hosts@164,registry:remote@59,registry:remote@91,registry:sql@196,whole:remoteClient@61,whole:remoteClient@93 7310bda43df947e7b2d237dce83291dfe15596802d9db404f6ec0bb457cd6540
-internal/registry/remote_dispatch.go Registry.dispatchRemote authority E2 Mixed declaration: E2 adds only the ArtifactHosts read operation; all existing dispatch and write behavior stays legacy. Original immutable legacy fingerprints retained. E32: census-only E2 completeness discovers the bounded union of route and timer hosts, including inactive orphan timers, through the service-user read-only parser. Unknown hosts refuse even with an empty root installation list; discovery supplies no authority and never removes a retained obligation. Pre-flight 33, 80, 82, 83, 89 and 90. Fixtures: TestArtifactHosts, TestRemoteReadOnlyWALRegistry, TestPlan14GenerationRegistryRows, TestPlan14GenerationRegistryProductionReader; extended TestPlan14GenerationRequiredArtifactsRootRunner remains runner-pending. registry:A@6140,registry:Account@3603,registry:Accounts@2616,registry:Addr@4180,registry:Addrs@4188,registry:Alias@6188,registry:Alias@6252,registry:Alias@6340,registry:Aliases@6062,registry:Alive@7882,registry:Args@5764,registry:At@5205,registry:At@6271,registry:At@6349,registry:At@7630,registry:Attempts@7652,registry:B@2001,registry:Backing@3999,registry:BuildState@4406,registry:Changed@1809,registry:Changed@2972,registry:Changed@3059,registry:Changed@3148,registry:Changed@3219,registry:Changed@3301,registry:Changed@7562,registry:Channel@3663,registry:Channel@4646,registry:Channel@5050,registry:CheckedAt@5131,registry:Class@4118,registry:DoneBefore@7891,registry:Due@5534,registry:Due@5598,registry:Due@7674,registry:Env@4714,registry:Env@4961,registry:Env@5061,registry:ExcludeHosts@7357,registry:ExcludeHosts@7452,registry:ExpectGood@5330,registry:ExpectOp@3027,registry:Expected@1063,registry:Expected@289,registry:Expected@752,registry:F@1781,registry:F@1842,registry:Fleet@1867,registry:Fleet@2059,registry:Fleet@2141,registry:Fleet@2222,registry:Fleet@2315,registry:FleetBoxes@2276,registry:Fleets@1925,registry:From@3774,registry:From@3835,registry:FromDB@3910,registry:FromOp@3115,registry:FromSock@3920,registry:Hash@4586,registry:Health@4197,registry:Host@272,registry:Host@2800,registry:Host@2841,registry:Host@2927,registry:Host@3003,registry:Host@3089,registry:Host@3181,registry:Host@3253,registry:Host@3345,registry:Host@3595,registry:Host@3655,registry:Host@3713,registry:Host@3766,registry:Host@3827,registry:Host@3902,registry:Host@3991,registry:Host@4045,registry:Host@4110,registry:Host@4172,registry:Host@4250,registry:Host@4325,registry:Host@4398,registry:Host@4463,registry:Host@4524,registry:Host@4578,registry:Host@4706,registry:Host@4760,registry:Host@4821,registry:Host@4883,registry:Host@5113,registry:Host@5197,registry:Host@5254,registry:Host@5322,registry:Host@5411,registry:Host@5471,registry:Host@5526,registry:Host@5590,registry:Host@5705,registry:Host@5756,registry:Host@5823,registry:Host@5898,registry:Host@5961,registry:Host@6558,registry:Host@6705,registry:Host@6993,registry:Host@7057,registry:Host@7116,registry:Host@7199,registry:Host@7282,registry:Host@7513,registry:Host@7598,registry:Host@7730,registry:Host@7811,registry:Host@7974,registry:Host@837,registry:Host@948,registry:Hosts@187,registry:Hosts@2679,registry:Hosts@4637,registry:Hosts@4952,registry:Hosts@5041,registry:Id@1057,registry:Id@1368,registry:Id@1467,registry:Id@1547,registry:Id@1698,registry:IdValue@2435,registry:Idle@5831,registry:Journal@1075,registry:Journal@1374,registry:Journal@1553,registry:Journal@442,registry:Keep@7990,registry:Kind@7819,registry:Kind@7982,registry:Label@2068,registry:Label@2150,registry:Label@2231,registry:Label@4829,registry:Limit@7348,registry:Limit@7443,registry:MaxRun@5839,registry:Method@151,registry:Ms@7646,registry:Name@1895,registry:Name@2454,registry:Name@7065,registry:Name@7124,registry:Name@7207,registry:Name@7521,registry:Name@7606,registry:Name@7738,registry:Name@845,registry:Name@956,registry:Next@301,registry:NextAt@7664,registry:NextGood@5344,registry:NextValue@1627,registry:NextValue@1725,registry:Now@6032,registry:Now@7341,registry:Now@7436,registry:Now@7529,registry:Now@980,registry:Number64@2542,registry:Number@7779,registry:Number@7850,registry:Number@7935,registry:Observed@365,registry:ObservedValue@643,registry:Old@1620,registry:Old@1718,registry:On@5479,registry:On@5906,registry:On@5969,registry:Op@2945,registry:Op@3021,registry:Op@3189,registry:Op@3261,registry:Op@3353,registry:Owner@280,registry:Owner@377,registry:Owner@660,registry:P@6659,registry:Path@6890,registry:Paths@6587,registry:Prefix@6713,registry:Prev@5269,registry:Registration@7215,registry:Registration@7614,registry:Registration@853,registry:Registration@964,registry:Replica@4532,registry:Replicas@4471,registry:Retirement@1704,registry:Route@2777,registry:Routes@2735,registry:Routes@3379,registry:Routes@5632,registry:Routes@5999,registry:Rows@7001,registry:Seed@5713,registry:Sqid@2446,registry:Sqid@2509,registry:Sqid@2588,registry:Status@2240,registry:Status@2935,registry:Status@3011,registry:Status@3097,registry:Status@3721,registry:Status@5121,registry:Status@6261,registry:Status@7636,registry:Status@7746,registry:Status@869,registry:Steering@4258,registry:StringMap@2352,registry:T@6941,registry:T@747,registry:TValue@2882,registry:Tags@2159,registry:Target@4891,registry:Targets@453,registry:Text@1273,registry:Text@1420,registry:Text@2556,registry:Text@6746,registry:Text@6809,registry:Timer@7029,registry:Timer@704,registry:Timers@7252,registry:Timers@7311,registry:Timers@7400,registry:Times@8024,registry:To@3782,registry:To@3843,registry:ToDB@3932,registry:ToOp@3107,registry:ToSock@3940,registry:TokenHash@2077,registry:Tunnel@4768,registry:Unit@4053,registry:UpdateSettings@4333,registry:Ver@5262,registry:Ver@5419,registry:W@6492,registry:Wildcards@6386,whole:Account@2620,whole:ClaimOwnership@282,whole:ClaimOwnership@379,whole:ClaimOwnership@662,whole:ClaimRow@455,whole:ClaimRowArchive@561,whole:ClaimRowArchive@571,whole:ColdRows@1622,whole:ColdRows@1629,whole:ColdRows@1720,whole:ColdRows@1727,whole:Fleet@1783,whole:Fleet@1844,whole:Fleet@1871,whole:Fleet@1929,whole:FleetBox@2003,whole:FleetBox@2280,whole:GoodBuild@5332,whole:GoodBuild@5346,whole:Route@2739,whole:Route@2781,whole:Route@2884,whole:Route@3383,whole:Route@3538,whole:Route@3544,whole:Route@367,whole:Route@5636,whole:Route@6003,whole:RouteAlias@6066,whole:RouteAlias@6142,whole:RoutePath@6591,whole:RoutePath@6661,whole:Timer@645,whole:Timer@6943,whole:Timer@7003,whole:Timer@7033,whole:Timer@708,whole:Timer@7256,whole:Timer@7315,whole:Timer@7404,whole:Timer@749,whole:WildcardDomain@6390,whole:WildcardDomain@6494 90e0bb84b5febab93f6eaa0edb324090c8d6c45db9e9b75e261649a778425ff4
+internal/registry/remote_dispatch.go Registry.dispatchRemote hint E2 Mixed declaration: E2 adds only the ArtifactHosts read operation; all existing dispatch and write behavior stays legacy. Original immutable legacy fingerprints retained. E32: census-only E2 completeness discovers the bounded union of route and timer hosts, including inactive orphan timers, through the service-user read-only parser. Unknown hosts refuse even with an empty root installation list; discovery supplies no authority and never removes a retained obligation. Pre-flight 33, 80, 82, 83, 89 and 90. Fixtures: TestArtifactHosts, TestRemoteReadOnlyWALRegistry, TestPlan14GenerationRegistryRows, TestPlan14GenerationRegistryProductionReader; extended TestPlan14GenerationRequiredArtifactsRootRunner remains runner-pending. PROPOSED E45: service registry child executes explicit timer row materialization/removal; service-writable SQL is deny-only to root and cannot confer ownership. Root lifecycle producer wiring is pending. registry:A@6327,registry:Account@3790,registry:Accounts@2803,registry:Addr@4367,registry:Addrs@4375,registry:Alias@6375,registry:Alias@6439,registry:Alias@6527,registry:Aliases@6249,registry:Alive@8069,registry:Args@5951,registry:At@5392,registry:At@6458,registry:At@6536,registry:At@7817,registry:Attempts@7839,registry:B@2188,registry:Backing@4186,registry:BuildState@4593,registry:Changed@1996,registry:Changed@3159,registry:Changed@3246,registry:Changed@3335,registry:Changed@3406,registry:Changed@3488,registry:Changed@7749,registry:Channel@3850,registry:Channel@4833,registry:Channel@5237,registry:CheckedAt@5318,registry:Class@4305,registry:DoneBefore@8078,registry:Due@5721,registry:Due@5785,registry:Due@7861,registry:Env@4901,registry:Env@5148,registry:Env@5248,registry:ExcludeHosts@7544,registry:ExcludeHosts@7639,registry:ExpectGood@5517,registry:ExpectOp@3214,registry:Expected@1250,registry:Expected@289,registry:Expected@762,registry:Expected@939,registry:F@1968,registry:F@2029,registry:Fleet@2054,registry:Fleet@2246,registry:Fleet@2328,registry:Fleet@2409,registry:Fleet@2502,registry:FleetBoxes@2463,registry:Fleets@2112,registry:From@3961,registry:From@4022,registry:FromDB@4097,registry:FromOp@3302,registry:FromSock@4107,registry:Hash@4773,registry:Health@4384,registry:Host@1024,registry:Host@1135,registry:Host@272,registry:Host@2987,registry:Host@3028,registry:Host@3114,registry:Host@3190,registry:Host@3276,registry:Host@3368,registry:Host@3440,registry:Host@3532,registry:Host@3782,registry:Host@3842,registry:Host@3900,registry:Host@3953,registry:Host@4014,registry:Host@4089,registry:Host@4178,registry:Host@4232,registry:Host@4297,registry:Host@4359,registry:Host@4437,registry:Host@4512,registry:Host@4585,registry:Host@4650,registry:Host@4711,registry:Host@4765,registry:Host@4893,registry:Host@4947,registry:Host@5008,registry:Host@5070,registry:Host@5300,registry:Host@5384,registry:Host@5441,registry:Host@5509,registry:Host@5598,registry:Host@5658,registry:Host@5713,registry:Host@5777,registry:Host@5892,registry:Host@5943,registry:Host@6010,registry:Host@6085,registry:Host@6148,registry:Host@6745,registry:Host@6892,registry:Host@7180,registry:Host@7244,registry:Host@7303,registry:Host@7386,registry:Host@7469,registry:Host@7700,registry:Host@7785,registry:Host@7917,registry:Host@7998,registry:Host@8161,registry:Hosts@187,registry:Hosts@2866,registry:Hosts@4824,registry:Hosts@5139,registry:Hosts@5228,registry:Id@1244,registry:Id@1555,registry:Id@1654,registry:Id@1734,registry:Id@1885,registry:IdValue@2622,registry:Idle@6018,registry:Journal@1262,registry:Journal@1561,registry:Journal@1740,registry:Journal@442,registry:Keep@8177,registry:Kind@8006,registry:Kind@8169,registry:Label@2255,registry:Label@2337,registry:Label@2418,registry:Label@5016,registry:Limit@7535,registry:Limit@7630,registry:MaxRun@6026,registry:Method@151,registry:Ms@7833,registry:Name@1032,registry:Name@1143,registry:Name@2082,registry:Name@2641,registry:Name@7252,registry:Name@7311,registry:Name@7394,registry:Name@7708,registry:Name@7793,registry:Name@7925,registry:Next@301,registry:NextAt@7851,registry:NextGood@5531,registry:NextValue@1814,registry:NextValue@1912,registry:Now@1167,registry:Now@6219,registry:Now@7528,registry:Now@7623,registry:Now@7716,registry:Number64@2729,registry:Number@7966,registry:Number@8037,registry:Number@8122,registry:Observed@365,registry:ObservedValue@643,registry:Old@1807,registry:Old@1905,registry:On@5666,registry:On@6093,registry:On@6156,registry:Op@3132,registry:Op@3208,registry:Op@3376,registry:Op@3448,registry:Op@3540,registry:Owner@280,registry:Owner@377,registry:Owner@660,registry:P@6846,registry:Path@7077,registry:Paths@6774,registry:Prefix@6900,registry:Prev@5456,registry:Registration@1040,registry:Registration@1151,registry:Registration@7402,registry:Registration@7801,registry:Replica@4719,registry:Replicas@4658,registry:Retirement@1891,registry:Route@2964,registry:Routes@2922,registry:Routes@3566,registry:Routes@5819,registry:Routes@6186,registry:Rows@7188,registry:Seed@5900,registry:Sqid@2633,registry:Sqid@2696,registry:Sqid@2775,registry:Status@1056,registry:Status@2427,registry:Status@3122,registry:Status@3198,registry:Status@3284,registry:Status@3908,registry:Status@5308,registry:Status@6448,registry:Status@7823,registry:Status@7933,registry:Steering@4445,registry:StringMap@2539,registry:T@7128,registry:T@757,registry:T@851,registry:T@934,registry:TValue@3069,registry:Tags@2346,registry:Target@5078,registry:Targets@453,registry:Text@1460,registry:Text@1607,registry:Text@2743,registry:Text@6933,registry:Text@6996,registry:Timer@709,registry:Timer@7216,registry:Timer@891,registry:Timers@7439,registry:Timers@7498,registry:Timers@7587,registry:Times@8211,registry:To@3969,registry:To@4030,registry:ToDB@4119,registry:ToOp@3294,registry:ToSock@4127,registry:TokenHash@2264,registry:Tunnel@4955,registry:Unit@4240,registry:UpdateSettings@4520,registry:Ver@5449,registry:Ver@5606,registry:W@6679,registry:Wildcards@6573,whole:Account@2807,whole:ClaimOwnership@282,whole:ClaimOwnership@379,whole:ClaimOwnership@662,whole:ClaimRow@455,whole:ClaimRowArchive@561,whole:ClaimRowArchive@571,whole:ColdRows@1809,whole:ColdRows@1816,whole:ColdRows@1907,whole:ColdRows@1914,whole:Fleet@1970,whole:Fleet@2031,whole:Fleet@2058,whole:Fleet@2116,whole:FleetBox@2190,whole:FleetBox@2467,whole:GoodBuild@5519,whole:GoodBuild@5533,whole:Route@2926,whole:Route@2968,whole:Route@3071,whole:Route@3570,whole:Route@367,whole:Route@3725,whole:Route@3731,whole:Route@5823,whole:Route@6190,whole:RouteAlias@6253,whole:RouteAlias@6329,whole:RoutePath@6778,whole:RoutePath@6848,whole:Timer@645,whole:Timer@713,whole:Timer@7130,whole:Timer@7190,whole:Timer@7220,whole:Timer@7443,whole:Timer@7502,whole:Timer@759,whole:Timer@7591,whole:Timer@853,whole:Timer@895,whole:Timer@936,whole:WildcardDomain@6577,whole:WildcardDomain@6681 d1343daea2d7ea81eabd31ec2a0026c4d4831636618903ec7a719c20f1633646
internal/rootstate/authorityfile_linux.go OpenAuthorityFile authority E2 E33: census-only generation journal reads accept private root-owned ancestry without chmod or repair. Every ancestor remains descriptor-checked, root-owned and non-writable by group/other; leaves remain single-link regular root-owned files, checked for DB/WAL/SHM. Public-file callers remain unchanged. Fixtures: TestPlan14AuthorityFilePrivateAncestors, TestPlan14AuthorityFileAbsoluteBoundary; TestPlan14GenerationPrivateJournalRootRunner and TestPlan14GenerationRequiredArtifactsRootRunner are runner-pending. Pre-flight 76, 78, 82 and 89. file:Open@181 39a50bbf5bc21a32c76647548c19af937055158ec583b1620a21645f9391500c
internal/rootstate/authorityfile_linux.go openAuthorityFile authority E2 E33: census-only generation journal reads accept private root-owned ancestry without chmod or repair. Every ancestor remains descriptor-checked, root-owned and non-writable by group/other; leaves remain single-link regular root-owned files, checked for DB/WAL/SHM. Public-file callers remain unchanged. Fixtures: TestPlan14AuthorityFilePrivateAncestors, TestPlan14AuthorityFileAbsoluteBoundary; TestPlan14GenerationPrivateJournalRootRunner and TestPlan14GenerationRequiredArtifactsRootRunner are runner-pending. Pre-flight 76, 78, 82 and 89. file:Openat@505 c7a9d1950ab15676b18afb750939c3e7e7204d4396be07959a61c97836720f99
internal/rootstate/authorityfile_other.go OpenAuthorityFile authority E2 E33: census-only generation journal reads accept private root-owned ancestry without chmod or repair. Every ancestor remains descriptor-checked, root-owned and non-writable by group/other; leaves remain single-link regular root-owned files, checked for DB/WAL/SHM. Public-file callers remain unchanged. Fixtures: TestPlan14AuthorityFilePrivateAncestors, TestPlan14AuthorityFileAbsoluteBoundary; TestPlan14GenerationPrivateJournalRootRunner and TestPlan14GenerationRequiredArtifactsRootRunner are runner-pending. Pre-flight 76, 78, 82 and 89. a22c1b26fc03197a6cb312ac5c92bb1038d8fde1df00f444f232a0cd7a54878c
@@ -341,8 +341,8 @@ internal/host/rootgeneration_parked.go generationReadRetirements authority E2 E3
internal/host/rootgeneration_parked.go generationRetirementRecords authority E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. file:Read@707 02eca3d34294974c09e93199efb37a223e7615eaa32faffdafb982b5d4ba53e7
internal/host/rootgeneration_parked.go generationRetirements authority E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. be9fd54e443994c423e0e315637b8cf05a361cbb32913b9bc9f891fbb65ad59f
internal/host/rootgeneration_parked.go productionGenerationRetirements authority E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. file:Open@221,file:Open@306 fe83ff81bb9966f50de7fb3a049b8acd8e87f5d86346809090fc46b5c1e65189
-internal/hostbox/reconcile_adapter.go ReconcileAdapter.Retire authority C,E2 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. registry:ClaimIncarnation@1309,registry:ClaimIncarnation@322,registry:ClaimIncarnation@3417,registry:ClaimIncarnation@3439,registry:ClaimOwner@1366,registry:ClaimOwner@1434,registry:ClaimOwner@2190,registry:ClaimOwner@2596,registry:ClaimOwner@3497,registry:ClaimOwner@3513,registry:ClaimOwner@3751,registry:ClaimOwnership@147,registry:ClaimRowID@3531,registry:ClaimRowID@3774,registry:ClaimStamp@1338,registry:ClaimStamp@3463,registry:ClaimStamp@3479,registry:ClaimStamp@362,registry:Host@1257,registry:Host@2095,registry:Host@2641,registry:Host@2698,registry:Host@288,registry:Host@3336,registry:Host@3719,registry:Name@3736,registry:Registration@3553,registry:Registration@3795,whole:ClaimOwnership@151,whole:Route@1069,whole:Route@1152,whole:Route@231,whole:Timer@3402 42d0e63edc29cf8a83f8265fb1b0260a88334a7ebc3f7b9530d938d9ffa5e4f1
-internal/hostbox/retirement_timers.go retirementWithTimers authority C,E2 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. 67060d1e6077a5cb6a15ea2749e070e97f830177987570037564916f5ed9bda0
+internal/hostbox/reconcile_adapter.go ReconcileAdapter.Retire authority C,E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. registry:ClaimIncarnation@1309,registry:ClaimIncarnation@322,registry:ClaimIncarnation@3417,registry:ClaimIncarnation@3439,registry:ClaimOwner@1366,registry:ClaimOwner@1434,registry:ClaimOwner@2190,registry:ClaimOwner@2596,registry:ClaimOwner@3497,registry:ClaimOwner@3513,registry:ClaimOwner@3751,registry:ClaimOwnership@147,registry:ClaimRowID@3531,registry:ClaimRowID@3774,registry:ClaimStamp@1338,registry:ClaimStamp@3463,registry:ClaimStamp@3479,registry:ClaimStamp@362,registry:Host@1257,registry:Host@2095,registry:Host@2641,registry:Host@2698,registry:Host@288,registry:Host@3336,registry:Host@3719,registry:Name@3736,registry:Registration@3553,registry:Registration@3795,whole:ClaimOwnership@151,whole:Route@1069,whole:Route@1152,whole:Route@231,whole:Timer@3402 42d0e63edc29cf8a83f8265fb1b0260a88334a7ebc3f7b9530d938d9ffa5e4f1
+internal/hostbox/retirement_timers.go retirementWithTimers authority C,E2 E38: reconcile completed root retirement against the retained stop receipt and exact inventory; inspect parked identities, require retired rows absent or an independently inventoried successor, and retain generation pins. Fixtures: TestPlan14GenerationParkedPayload, TestPlan14GenerationParkedCrash, TestPlan14GenerationParkedKeepsReservation, TestPlan14GenerationParkedReadback; TestPlan14GenerationParkedRootRunner is runner-pending. Timer producer matches retained entries once and preserves successors on replay: TestPlan14GenerationTimerRetirement. No claims-off path changes. PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. 67060d1e6077a5cb6a15ea2749e070e97f830177987570037564916f5ed9bda0
internal/activator/activator.go Service.respawnWorkerLocked authority E2 E39: close the unsupported claimed worker path before backend entry, replacement or supervised stop. Claims-off paths remain unchanged. This is refusal coverage, not positive root-reserved worker launch support. Fixtures: TestPlan14GenerationWorkerStartBoundary, TestPlan14GenerationWorkerRealBackendRefusal, TestPlan14GenerationWorkerReplacementBoundary, TestPlan14GenerationWorkerSupervisionBoundary. Pre-flight 29 and E39 worker launch audit. whole:Route@11828,whole:Route@13334,whole:Route@360,whole:Route@4164,whole:Route@6157,whole:Route@6338 706f2a4268a06dae2eca22093f841750139c5bd9dcaa0e91657621c5eea1bbe9
internal/activator/workerbuild.go Service.startWorker authority E2 E39: close the unsupported claimed worker path before backend entry, replacement or supervised stop. Claims-off paths remain unchanged. This is refusal coverage, not positive root-reserved worker launch support. Fixtures: TestPlan14GenerationWorkerStartBoundary, TestPlan14GenerationWorkerRealBackendRefusal, TestPlan14GenerationWorkerReplacementBoundary, TestPlan14GenerationWorkerSupervisionBoundary. Pre-flight 29 and E39 worker launch audit. registry:Host@1201,registry:Host@1379,registry:Host@2314,whole:Route@2420,whole:Route@931 be27c7d049a6009a8a22798569e457f5126f14d5e7c56aafb5b1bd260717f6f5
internal/activator/workerswap.go Service.ReplaceWorker authority E2 E39: close the unsupported claimed worker path before backend entry, replacement or supervised stop. Claims-off paths remain unchanged. This is refusal coverage, not positive root-reserved worker launch support. Fixtures: TestPlan14GenerationWorkerStartBoundary, TestPlan14GenerationWorkerRealBackendRefusal, TestPlan14GenerationWorkerReplacementBoundary, TestPlan14GenerationWorkerSupervisionBoundary. Pre-flight 29 and E39 worker launch audit. whole:Route@2163 2666d54bdc876904d9c2c3d6de5c2f82dc688f07082d92d84ddfc7e01cda6a7e
@@ -357,22 +357,39 @@ internal/host/guardtransaction.go systemApplier.beginGuard authority D Claims-on
internal/host/guardtransaction.go systemApplier.checkGuardRecovery authority D Claims-on or retained root census uses root exclusion; claims-off with no census preserves main participation and recovery behavior. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:ReadFile@433,file:ReadFile@660 25070bd97c1b3c9ce9fbdc8f44cec9680db72abf4ab9f50e603aad9b3a1ea4e2
internal/host/rootexecution_runtime.go newClaimExecutionRuntime authority D Positive claim or fence mismatches are terminal; transport failures retain bounded retry eligibility. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Read@205 91514737432c25a2256b449c97d70fc61f9deb32726b9aea90e7ffdeb962a9db
internal/host/rootlaunchverifier.go registerProductionLaunchVerifier authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Open@191 20e6bdeb66c580f64b9a315a65bb647c17dde0fe49c4f2bc45adfcd2bdc6989f
-internal/host/rootlaunchverifier.go var participationRelease authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Open@101,file:Open@53 03650562dfb07973650f2ee6f1797353fac444893f36180654ab0707f6738fc4
-internal/host/systemapply.go systemApplier.Run authority D,F,J4 root-input;kept=D,F;scope=protected-decision-only PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:OpenFile@229 8151becbeaaf35a6eb8a03fcc30faae5016124e55566dd75a37f193acd7fee7e
+internal/host/rootlaunchverifier.go var participationRelease authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. file:Open@124,file:Open@76 5f8ff567328f13a1ddc69ed82af4037c18c2a83d48a369a144fe14e1824b3b14
+internal/host/systemapply.go systemApplier.Run authority D,F,J4 root-input;kept=D,F;scope=protected-decision-only PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. file:OpenFile@483 6a5704be83e35979db7f2811b8ea7fd3d4c2c74c9b532f9f94f1d5482cbeb827
internal/host/systemparticipation.go systemApplier.beginExecutableChange authority D Claims-on or retained root census uses root exclusion; claims-off with no census preserves main participation and recovery behavior. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:ReadFile@565 f4cacc520429d698655295d87a14326a4a3f3d11e70fa16536e2c3f554d6c307
internal/host/systemparticipation.go systemApplier.clearUpdateBegun authority D Claims-on or retained root census uses root exclusion; claims-off with no census preserves main participation and recovery behavior. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Lstat@263,file:Lstat@511 6ac36ce598723cf4af81fbe36c6c53408e8c34a1202173796cc95e5e8bef0c3f
internal/hostfloor/disk.go Disk.Begin authority D Claims-on or retained root census uses root exclusion; claims-off with no census preserves main participation and recovery behavior. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Stat@172 e298c9bc261cc9240341987c2604dd86ecbf7e3590c4926f6f47d69821d60ac8
-internal/release/releaseexclusion.go ReleaseExclusion.With authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Open@109,file:Open@300 aa1a252eb198065d42e97014bd416257756337819b2788f94f8823ee26a749f3
-internal/release/releaseexclusion.go type ReleaseExclusion authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Open@32 831e83a39af9484c139388aac8e0c39ee7566f19dd56384ccad73da5f17f3e5c
+internal/release/releaseexclusion.go ReleaseExclusion.With authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. file:Open@109,file:Open@300 3cf97efb39ee3499c0760e993fc7d1cd6a55fd2e8a474ccb70e484bdeda7ddcc
+internal/release/releaseexclusion.go type ReleaseExclusion authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. file:Open@32 a4026f7a3110a647eaaeabecd5f0a255ae91b5e183f431be3b017433ee40eba3
internal/rootparticipation/production.go ActivationReady authority D,G Refuse claims activation without complete census and registered verifier; Part 3 G remains mandatory. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Open@61 0103a8635bfc3a4ab17998aab202eebcc9ed8dc1a9d7c41ac8fabad435ff5c05
internal/rootparticipation/production.go Open authority D Claims-on or retained root census uses root exclusion; claims-off with no census preserves main participation and recovery behavior. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. file:Open@45,file:Open@5 e7feda4757ec1a808e42729cc91ba57c37a34f48640f72dcdb9de706d8a55a24
-internal/host/claimstop_run.go RunInstanceControl authority C,D,G Refuse premature claim activation and continue serving recovery after per-host startup failures. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. 9cb1257df4cf5800111b0d8828acbe131886bdc5642febe0ad6dcdd811b8cc36
+internal/host/claimstop_run.go RunInstanceControl authority C,D,G Refuse premature claim activation and continue serving recovery after per-host startup failures. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. ff3a133b81f96edcd658819bf2832c58240b5ce0b39eabd1d8d9c1e1beca4749
internal/hostfloor/disk.go Disk.WithParticipation authority D Claims-on or retained root census uses root exclusion; claims-off with no census preserves main participation and recovery behavior. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. f06b9c85d99dc0fe071f4b2bf0eced7ec8f118c7df5c1871040df3ea7323b3a3
internal/hostfloor/participation.go CheckParticipation authority D Claims-on or retained root census uses root exclusion; claims-off with no census preserves main participation and recovery behavior. PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. d5390731acd74a7ce0ef5e89a1cb1af769d35433c1bf4cf4bd196a2b4da650d2
-internal/host/rootlaunchverifier.go rootLaunchVerifier.verify authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. 5183759149e649b5dd61a5b833a3b9dafcfa59878d0220882aad3c8c55e0daf1
+internal/host/rootlaunchverifier.go rootLaunchVerifier.verify authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. ff4241ff35d8dd8f7d08c2d5622ce88040791d34839bcecb22ac0d52436bfafa
internal/rootparticipation/production.go Ledger.ActivationReady authority E2 PROPOSED E43: root; release-held context and D3 reservation/census verification under release-before-participation exclusion; no-census effects stay on the legacy path. TestPlan14LaunchVerifierToken, TestPlan14LaunchVerifierCallerOrder, TestPlan14LaunchVerifierNoCensus, TestPlan14LaunchVerifierRootRunner. 99a3a8f61a8b769da5bce734d6d8a937b861ed38ef1e4f9cc1eb2b4d473286fb
@coverage internal/hostfloor 37459f6d8d47ed5ec3892e22483e34abde2dabc0775cf0846b6fe00fb66dcd75
-@coverage internal/rootparticipation 24ff9d273269e9be6c54794c23f1183ce0535c253e714158e34a9bca9e3fdd81
+@coverage internal/rootparticipation d867cfb3ad2b0432f7cee72f29805bc3420e28fe0edd5b89b469f154e4371694
internal/host/claimretire_transport.go ClaimRootHTTP.retire authority C,G,J1 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. registry:ClaimOwnership@1081,registry:Host@1062,registry:Host@792,whole:ClaimOwnership@1085,whole:ClaimOwnership@894,whole:Route@1830,whole:Route@854 425059d65adaafa5680ad92974559fbeb56ba0a3a7ff3867bb63937648989e06
-internal/host/rootinventory_timer.go ClaimRootHTTP.verifyInventoryTimer authority C,J1 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. registry:ClaimIncarnation@943,registry:ClaimIncarnation@969,registry:ClaimOwner@1035,registry:ClaimOwner@299,registry:ClaimRowID@1070,registry:ClaimStamp@1015,registry:ClaimStamp@995,registry:Host@899,registry:Name@921,registry:Registration@1107,whole:Timer@709 f54f9aeaf48b8a258b9102d630719552a7e3ff495a53ab53123b79420723365f
-internal/host/rootinventory_timer.go ClaimRootHTTP.verifyNewTimerIntentions authority E2 PROPOSED E44: root timer intention must precede materialization; exact registration and row identity required; root retirement cannot infer ownership from registry enumeration. Tests: TestPlan14TimerIntentionBeforeMaterialization, TestPlan14GenerationTimerRetirement; root timer intention, sync-failure retry and retirement acceptance. Scheduler producer wiring remains incomplete. whole:Timer@609 179376ffb25fdc973418cfeae238cf1c69e3ec95495d6ac145ac6fa660f8a1c0
+internal/host/rootinventory_timer.go ClaimRootHTTP.verifyInventoryTimer hint C,J1 PROPOSED E45 / R-E45-5: root executes a deny-only consistency check of service-writable timer rows. Registry absence is not ordering authority. Root intention authority resides in the retained inventory and fence; producer lifecycle remains incomplete. registry:ClaimIncarnation@943,registry:ClaimIncarnation@969,registry:ClaimOwner@1035,registry:ClaimOwner@299,registry:ClaimRowID@1070,registry:ClaimStamp@1015,registry:ClaimStamp@995,registry:Host@899,registry:Name@921,registry:Registration@1107,whole:Timer@709 f54f9aeaf48b8a258b9102d630719552a7e3ff495a53ab53123b79420723365f
+internal/host/rootinventory_timer.go ClaimRootHTTP.verifyNewTimerIntentions hint E2 PROPOSED E45 / R-E45-5: root executes a deny-only consistency check of service-writable timer rows. Registry absence is not ordering authority. Root intention authority resides in the retained inventory and fence; producer lifecycle remains incomplete. whole:Timer@609 179376ffb25fdc973418cfeae238cf1c69e3ec95495d6ac145ac6fa660f8a1c0
+internal/host/claimlaunch_execpermit.go ClaimLaunchRoot.execPermit authority D Root participation bounds the executable permit; TestPlan14ExecutionFinalLaunchUsesRootParticipation covers initial and final checks. PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. file:Read@157 cf0aa8db12ea1d451ce4e729572dc8c628ee22453bee25dae62c5ff13e85e769
+internal/host/claimlaunch_kernel_linux.go linuxClaimLaunchKernel.Observe authority C,G,J4 root-input;kept=C,G;scope=protected-decision-only PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. file:Lstat@430,file:Stat@1766,file:Stat@1862,file:Stat@1961,file:Stat@683 ec9cda265914cb10785a3a37cfa6929eb625be4575e98ace147640a26c984337
+internal/host/rootgeneration_containment_linux.go generationIdentity authority E2 PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. file:Stat@102 13091ead40faf400cbd20f1d5d308fdfd4a3d7c605ef3eba4f5f279af8203258
+internal/host/rootgeneration_containment_linux.go generationKernel.containedBinding authority E2 PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. file:Lstat@393,file:Readlink@1400,file:Readlink@1557,file:Readlink@1726,file:Readlink@3254,file:Stat@2449 09b91d059735787d01ce3747a1ed8af5069ea0b19fa2f1dd4c7ad47e0dad6fae
+internal/host/systemapply.go systemApplier.activationGuard authority E2 PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. whole:Route@870 3dff6208f27c8a74f94df68e389b75cfe18d6e17a14a61a7f701597484bc29e2
+internal/host/systemapply.go systemApplier.restoreFiles authority E2 PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. file:Stat@226 418eaa7dd1fd35f6d5640c7afd5385273088883e301066c7ac9c444b70d62cac
+internal/instanceexec/claim_generation_linux.go ScopeClaimGeneration authority E2 PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. file:Syscall@2236 0300932372f83c15b3c5716394128757476554c0603724b9f5801cae1a1a5ab7
+internal/instanceexec/claimexec_linux.go checkClaimExec authority E2 PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. file:Fstat@303,file:Open@114 a9cfcdaf74e4bd245060e05b9fbbabc7c0d3ccf83477f1ad38f6a0c2597c3792
+internal/registry/timer_materialization.go Registry.DeleteTimerOwnedRegistration hint E2 PROPOSED E45: service registry child executes explicit timer row materialization/removal; service-writable SQL is deny-only to root and cannot confer ownership. Root lifecycle producer wiring is pending. file:Exec@333,registry:ClaimIncarnation@520,registry:ClaimOwner@554,registry:ClaimOwnership@205,registry:ClaimRowID@275,registry:ClaimRowID@568,registry:ClaimStamp@540,registry:Host@488,registry:Name@496,registry:Registration@233,registry:Registration@257,registry:Registration@504,registry:remote@69,registry:remote@99,registry:sql@333,whole:ClaimOwnership@207,whole:Timer@172,whole:Timer@175,whole:remoteClient@101,whole:remoteClient@71 3d24b1ba7130b25725d1fc6400d10678e9514e8448a5e1964b1a2b7067e5ed56
+internal/registry/timer_materialization.go Registry.MaterializeTimerRegistration hint E2 PROPOSED E45: service registry child executes explicit timer row materialization/removal; service-writable SQL is deny-only to root and cannot confer ownership. Root lifecycle producer wiring is pending. file:Exec@1622,file:Exec@1912,file:QueryRow@495,file:QueryRow@896,registry:Attempts@2085,registry:ClaimIncarnation@1459,registry:ClaimIncarnation@1483,registry:ClaimIncarnation@1800,registry:ClaimIncarnation@2135,registry:ClaimIncarnation@719,registry:ClaimIncarnation@745,registry:ClaimOwner@1539,registry:ClaimOwner@1557,registry:ClaimOwner@1834,registry:ClaimOwner@2169,registry:ClaimOwner@803,registry:ClaimOwner@823,registry:ClaimOwnership@265,registry:ClaimOwnership@687,registry:ClaimRowID@1429,registry:ClaimRowID@2183,registry:ClaimRowID@335,registry:ClaimStamp@1505,registry:ClaimStamp@1523,registry:ClaimStamp@1820,registry:ClaimStamp@2155,registry:ClaimStamp@767,registry:ClaimStamp@787,registry:CreatedAt@2097,registry:DueAt@2110,registry:Host@1774,registry:Host@2001,registry:Host@556,registry:Host@968,registry:Kind@1321,registry:Kind@1333,registry:Kind@2017,registry:LastAt@2051,registry:LastMs@2075,registry:LastStatus@2061,registry:Name@1782,registry:Name@2009,registry:Name@976,registry:NextAt@2041,registry:Path@2033,registry:Registration@1244,registry:Registration@1264,registry:Registration@1397,registry:Registration@2119,registry:Registration@293,registry:Registration@317,registry:Spec@2025,registry:Timer@242,registry:remote@128,registry:remote@96,registry:sql@397,whole:ClaimOwnership@267,whole:ClaimOwnership@693,whole:Timer@1140,whole:Timer@1147,whole:Timer@1226,whole:Timer@201,whole:Timer@204,whole:Timer@2299,whole:Timer@246,whole:remoteClient@130,whole:remoteClient@98 da3727d74006ee5f389d9f1e52f54a1602597c525eee9940d8408b49b9248733
+internal/host/rootgeneration_containment_linux.go generationContainedStatus authority E2 PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. 08ea66880ec8ddd2ac36c23b1a9ed4df026e0b24f627ad8e03e07452dea91f89
+internal/host/rootgeneration_containment_linux.go generationKernel.containedTask authority E2 PROPOSED E45: root uses retained reservation identities and live kernel containment checks; unrecognized processes still refuse. Current positive root acceptance requires rerun after proc-mount tightening. f8b8580ee0cd0912a8c1e758c4885146c4ce3ffd2501a53781ba5bfecc4f4b75
+internal/host/claimstop_run.go claimActivationReady authority E2 PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. 82d893dc252c02813cd492f44dab8adc75b0e6fe5eaf4b7314c17759fdd5c83e
+internal/host/rootreleaselock.go withClaimReleaseCensus authority E2 PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. 35bcc5e18e89f9755cf1f749bbaa4cbf493380b75500183687476abd71c946b6
+internal/rootparticipation/ledger.go IsAcquisitionRecovery authority E2 PROPOSED E45: root enforces census-gated release ordering, bounded acquisition and scoped recovery; registry timer primitives preserve supplied identity but producer integration remains pending. d71ec87152a9ba780aaf6e08fd54288bd6da40d61ad96495e7c3b29a0be200cd
+@coverage internal/instancectl ede13946f9019158fd631e09a4543d3c73ee2e353b70945a900e963a688b1c33
+@coverage internal/instanceexec 1a4b9c7a5c57c99ecb39b56c724f08f7e0226dc4809f31000e49e4b38cb4d059
diff --git a/internal/registry/plan14_timer_materialization_test.go b/internal/registry/plan14_timer_materialization_test.go
new file mode 100644
index 000000000..d67bcded2
--- /dev/null
+++ b/internal/registry/plan14_timer_materialization_test.go
@@ -0,0 +1,127 @@
+package registry
+
+import (
+ "errors"
+ "path/filepath"
+ "reflect"
+ "testing"
+)
+
+func TestPlan14TimerMaterializesIntendedIdentity(t *testing.T) {
+ for _, transport := range []string{"local", "child"} {
+ t.Run(transport, func(t *testing.T) {
+ var r *Registry
+ if transport == "child" {
+ r, _ = remoteTestRegistry(t)
+ } else {
+ var err error
+ r, err = Open(filepath.Join(t.TempDir(), "registry.db"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ }
+ host := "shop.example"
+ routeOwner := ClaimOwnership{"inc", 3, "owner", "route"}
+ if err := r.Upsert(Route{Host: host, ClaimOwnership: routeOwner}); err != nil {
+ t.Fatal(err)
+ }
+ for _, kind := range []string{"schedule", "once", "landing"} {
+ timerKind := kind
+ if kind == "landing" {
+ timerKind = "schedule"
+ }
+ old := Timer{ClaimOwnership: ClaimOwnership{"inc", 3, "owner", "first-" + kind}, Host: host, Name: kind, Kind: timerKind, Spec: "every 1h", Path: "/tick", NextAt: 123, DueAt: 120, CreatedAt: 90, LastAt: 80, LastMs: 7, LastStatus: "ok", Attempts: 2, Registration: "first-" + kind}
+ installed, err := r.MaterializeTimerRegistration(old, "")
+ if err != nil || installed == nil || *installed != old {
+ t.Fatalf("intended identity changed: %+v %v", installed, err)
+ }
+ if got, err := r.MaterializeTimerRegistration(old, ""); err != nil || got == nil || *got != old {
+ t.Fatalf("lost-reply replay: %+v %v", got, err)
+ }
+ changed := old
+ changed.Path = "/collision"
+ if _, err := r.MaterializeTimerRegistration(changed, old.Registration); !errors.Is(err, ErrTimerChanged) {
+ t.Fatal("identity reused for changed timer", err)
+ }
+ for _, column := range []string{"incarnation", "stamp", "owner"} {
+ wrong := old
+ switch column {
+ case "incarnation":
+ wrong.ClaimIncarnation = "other"
+ case "stamp":
+ wrong.ClaimStamp++
+ case "owner":
+ wrong.ClaimOwner = "other"
+ }
+ if err := r.DeleteTimerOwnedRegistration(wrong); !errors.Is(err, ErrTimerChanged) {
+ t.Fatal("copied registration removed another owner", err)
+ }
+ }
+ next := old
+ next.Registration = "next-" + kind
+ next.ClaimRowID = next.Registration
+ next.Path = "/new"
+ if _, err := r.MaterializeTimerRegistration(next, "wrong"); !errors.Is(err, ErrTimerChanged) {
+ t.Fatal("stale update accepted", err)
+ }
+ if _, err := r.MaterializeTimerRegistration(next, old.Registration); err != nil {
+ t.Fatal(err)
+ }
+ if err := r.DeleteTimerOwnedRegistration(old); !errors.Is(err, ErrTimerChanged) {
+ t.Fatal("successor removed", err)
+ }
+ got, err := r.GetTimer(host, kind)
+ if err != nil || !reflect.DeepEqual(got, &next) {
+ t.Fatalf("successor changed: %+v %v", got, err)
+ }
+ if err := r.DeleteTimerOwnedRegistration(next); err != nil {
+ t.Fatal(err)
+ }
+ }
+ })
+ }
+}
+
+func TestPlan14TimerMaterializationRefusesOwnershipChanges(t *testing.T) {
+ for _, attack := range []string{"missing owner", "other incarnation", "other stamp", "other owner", "row differs", "empty registration", "absent route", "replaced route"} {
+ t.Run(attack, func(t *testing.T) {
+ r, err := Open(filepath.Join(t.TempDir(), "registry.db"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ owner := ClaimOwnership{"inc", 3, "owner", "route"}
+ if attack != "absent route" {
+ if err := r.Upsert(Route{Host: "shop.example", ClaimOwnership: owner}); err != nil {
+ t.Fatal(err)
+ }
+ }
+ timer := Timer{Host: "shop.example", Name: "once", Kind: "once", Path: "/tick", Registration: "intended", ClaimOwnership: ClaimOwnership{"inc", 3, "owner", "intended"}}
+ switch attack {
+ case "missing owner":
+ timer.ClaimOwnership = ClaimOwnership{}
+ case "other incarnation":
+ timer.ClaimIncarnation = "other"
+ case "other stamp":
+ timer.ClaimStamp++
+ case "other owner":
+ timer.ClaimOwner = "other"
+ case "row differs":
+ timer.ClaimRowID = "other"
+ case "empty registration":
+ timer.Registration = ""
+ case "replaced route":
+ if _, err := r.sql.Exec(`UPDATE routes SET claim_stamp=claim_stamp+1 WHERE host=?`, timer.Host); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if _, err := r.MaterializeTimerRegistration(timer, ""); err == nil {
+ t.Fatal("unintended ownership materialized")
+ }
+ if rows, err := r.TimersFor(timer.Host); err != nil || len(rows) != 0 {
+ t.Fatal("refusal wrote rows", rows, err)
+ }
+ })
+ }
+}
diff --git a/internal/registry/remote_dispatch.go b/internal/registry/remote_dispatch.go
index 9b0e74559..23adccb5e 100644
--- a/internal/registry/remote_dispatch.go
+++ b/internal/registry/remote_dispatch.go
@@ -135,6 +135,10 @@ func (r *Registry) dispatchRemote(ctx context.Context, q remoteRequest, callback
})
case "AdoptClaimTimer":
err = r.AdoptClaimTimer(ctx, q.ObservedValue, q.Owner)
+ case "MaterializeTimerRegistration":
+ out.Timer, err = r.MaterializeTimerRegistration(q.T, q.Expected)
+ case "DeleteTimerOwnedRegistration":
+ err = r.DeleteTimerOwnedRegistration(q.T)
case "UpsertTimerRegistration":
out.Timer, err = r.UpsertTimerRegistration(q.T, q.Expected)
case "SetTimerStatusRegistration":
diff --git a/internal/registry/timer_materialization.go b/internal/registry/timer_materialization.go
new file mode 100644
index 000000000..dd1d94e4a
--- /dev/null
+++ b/internal/registry/timer_materialization.go
@@ -0,0 +1,83 @@
+package registry
+
+import (
+ "database/sql"
+ "errors"
+)
+
+// MaterializeTimerRegistration preserves the identity already recorded by root.
+// The caller must retain host exclusion and check its inventory revision before
+// this write. Replay accepts only the exact resulting row, including ownership.
+func (r *Registry) MaterializeTimerRegistration(t Timer, expected string) (*Timer, error) {
+ if r.remote != nil {
+ out, err := r.remote.call(nil, remoteRequest{Method: "MaterializeTimerRegistration", T: t, Expected: expected}, nil)
+ return out.Timer, err
+ }
+ if !t.ClaimOwnership.Valid() || t.Registration == "" || t.Registration != t.ClaimRowID {
+ return nil, ErrClaimOwnership
+ }
+ tx, err := r.sql.Begin()
+ if err != nil {
+ return nil, err
+ }
+ defer tx.Rollback()
+ route, err := scanRoute(tx.QueryRow(`SELECT `+routeCols+` FROM routes WHERE host=?`, t.Host).Scan)
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, ErrClaimOwnership
+ }
+ return nil, err
+ }
+ if !route.ClaimOwnership.Valid() || route.ClaimIncarnation != t.ClaimIncarnation || route.ClaimStamp != t.ClaimStamp || route.ClaimOwner != t.ClaimOwner {
+ return nil, ErrClaimOwnership
+ }
+ old, err := scanTimer(tx.QueryRow(`SELECT `+timerCols+` FROM timers WHERE host=? AND name=?`, t.Host, t.Name))
+ switch {
+ case errors.Is(err, sql.ErrNoRows):
+ if expected != "" {
+ return nil, ErrTimerChanged
+ }
+ case err != nil:
+ return nil, err
+ default:
+ if old == t {
+ if err := tx.Commit(); err != nil {
+ return nil, err
+ }
+ return &old, nil
+ }
+ if old.Registration == t.Registration {
+ return nil, ErrTimerChanged
+ }
+ if old.Kind != t.Kind {
+ return nil, ErrTimerKind
+ }
+ if expected == "" || old.Registration != expected || old.ClaimRowID != expected || old.ClaimIncarnation != t.ClaimIncarnation || old.ClaimStamp != t.ClaimStamp || old.ClaimOwner != t.ClaimOwner {
+ return nil, ErrTimerChanged
+ }
+ if _, err := tx.Exec(`DELETE FROM timers WHERE host=? AND name=? AND registration=? AND claim_incarnation=? AND claim_stamp=? AND claim_owner=? AND claim_row_id=?`, t.Host, t.Name, expected, t.ClaimIncarnation, t.ClaimStamp, t.ClaimOwner, expected); err != nil {
+ return nil, err
+ }
+ }
+ if _, err := tx.Exec(`INSERT INTO timers (`+timerCols+`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, t.Host, t.Name, t.Kind, t.Spec, t.Path, t.NextAt, t.LastAt, t.LastStatus, t.LastMs, t.Attempts, t.CreatedAt, t.DueAt, t.Registration, t.ClaimIncarnation, t.ClaimStamp, t.ClaimOwner, t.ClaimRowID); err != nil {
+ return nil, err
+ }
+ if err := tx.Commit(); err != nil {
+ return nil, err
+ }
+ return &t, nil
+}
+
+// DeleteTimerOwnedRegistration cannot remove a successor or a row with a
+// copied registration but another installation's ownership.
+func (r *Registry) DeleteTimerOwnedRegistration(t Timer) error {
+ if r.remote != nil {
+ _, err := r.remote.call(nil, remoteRequest{Method: "DeleteTimerOwnedRegistration", T: t}, nil)
+ return err
+ }
+ if !t.ClaimOwnership.Valid() || t.Registration == "" || t.Registration != t.ClaimRowID {
+ return ErrClaimOwnership
+ }
+ res, err := r.sql.Exec(`DELETE FROM timers WHERE host=? AND name=? AND registration=? AND claim_incarnation=? AND claim_stamp=? AND claim_owner=? AND claim_row_id=?`, t.Host, t.Name, t.Registration, t.ClaimIncarnation, t.ClaimStamp, t.ClaimOwner, t.ClaimRowID)
+ return timerRegistrationResult(res, err)
+}
diff --git a/internal/release/plan14_release_exclusion_test.go b/internal/release/plan14_release_exclusion_test.go
index 0f8f00cf6..a2b3cae3b 100644
--- a/internal/release/plan14_release_exclusion_test.go
+++ b/internal/release/plan14_release_exclusion_test.go
@@ -6,6 +6,7 @@ import (
"os"
"sync"
"testing"
+ "time"
"github.com/carlosframework/platform/internal/rootstate"
)
@@ -51,3 +52,34 @@ func TestPlan14ReleaseExclusionToken(t *testing.T) {
t.Fatal("stale context reacquired")
}
}
+
+type deadlineExclusionDisk struct {
+ rootstate.Store
+ wait bool
+}
+
+func (d deadlineExclusionDisk) Lock(ctx context.Context, name string) (func(), error) {
+ if _, ok := ctx.Deadline(); !ok {
+ return nil, os.ErrPermission
+ }
+ if d.wait {
+ <-ctx.Done()
+ return nil, ctx.Err()
+ }
+ return func() {}, nil
+}
+func (d deadlineExclusionDisk) Close() error { return nil }
+func TestPlan14ReleaseExclusionWaitBudget(t *testing.T) {
+ for _, wait := range []bool{false, true} {
+ x := &ReleaseExclusion{Wait: 10 * time.Millisecond, Open: func() (rootstate.Store, error) { return deadlineExclusionDisk{wait: wait}, nil }}
+ called := false
+ err := x.With(t.Context(), func(ctx context.Context) error { called = true; time.Sleep(20 * time.Millisecond); return x.Check(ctx) })
+ if wait {
+ if !errors.Is(err, context.DeadlineExceeded) || called {
+ t.Fatal("unbounded wait", err, called)
+ }
+ } else if err != nil || !called {
+ t.Fatal("wait budget leaked into effect", err, called)
+ }
+ }
+}
diff --git a/internal/release/plan14_root_generations_test.go b/internal/release/plan14_root_generations_test.go
index 955cf2374..ecd08f7c7 100644
--- a/internal/release/plan14_root_generations_test.go
+++ b/internal/release/plan14_root_generations_test.go
@@ -169,3 +169,26 @@ func TestPlan14GenerationRecordRefusals(t *testing.T) {
})
}
}
+
+func TestPlan14GenerationOldProcessStillParses(t *testing.T) {
+ g, s, b := generationFixture(t)
+ r, err := g.Reserve(b)
+ if err != nil {
+ t.Fatal(err)
+ }
+ p := GenerationProcess{PID: 100, TenantPID: 200, Start: "one", TenantStart: "two", Group: "unit", Invocation: "invocation", GroupIdentity: rootstate.FileIdentity{Device: 1, Inode: 2}}
+ if err := g.Started(r, p); err != nil {
+ t.Fatal(err)
+ }
+ raw, err := s.Read(generationKey(b))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if strings.Contains(string(raw), "LauncherIdentity") || strings.Contains(string(raw), "Containment") {
+ t.Fatal("old process wire shape changed")
+ }
+ got, err := g.Read(b)
+ if err != nil || got.Process != p {
+ t.Fatalf("old process: %+v %v", got, err)
+ }
+}
diff --git a/internal/release/releaseexclusion.go b/internal/release/releaseexclusion.go
index 40f3c527b..b3842a7e5 100644
--- a/internal/release/releaseexclusion.go
+++ b/internal/release/releaseexclusion.go
@@ -4,12 +4,14 @@ import (
"context"
"os"
"sync/atomic"
+ "time"
"github.com/carlosframework/platform/internal/rootstate"
)
type ReleaseExclusion struct {
Open func() (rootstate.Store, error)
+ Wait time.Duration
}
type releaseHeldKey struct{}
@@ -44,7 +46,14 @@ func (x *ReleaseExclusion) With(ctx context.Context, fn func(context.Context) er
return os.ErrPermission
}
defer store.Close()
- return rootstate.WithRelease(ctx, store, func() error {
+ wait := ctx
+ cancel := func() {}
+ if x.Wait > 0 {
+ wait, cancel = context.WithTimeout(ctx, x.Wait)
+ }
+ defer cancel()
+ return rootstate.WithRelease(wait, store, func() error {
+ cancel()
held := &releaseHeld{owner: x}
held.active.Store(true)
defer held.active.Store(false)
diff --git a/internal/release/rootgenerations.go b/internal/release/rootgenerations.go
index 3b1b07464..afdda3b26 100644
--- a/internal/release/rootgenerations.go
+++ b/internal/release/rootgenerations.go
@@ -35,6 +35,8 @@ type GenerationProcess struct {
PID, TenantPID int
Start, TenantStart, Group, Invocation string
GroupIdentity rootstate.FileIdentity
+ LauncherIdentity rootstate.FileIdentity `json:",omitzero"`
+ Containment uint8 `json:",omitempty"`
}
type GenerationReservation struct {
@@ -69,7 +71,7 @@ func (b GenerationBinding) valid() bool {
}
func (p GenerationProcess) valid() bool {
- return p.PID > 0 && p.TenantPID > 0 && p.PID != p.TenantPID && p.Start != "" && p.TenantStart != "" &&
+ return p.Containment <= 1 && (p.Containment == 0 || (p.LauncherIdentity.Device > 0 && p.LauncherIdentity.Inode > 0)) && p.PID > 0 && p.TenantPID > 0 && p.PID != p.TenantPID && p.Start != "" && p.TenantStart != "" &&
filepath.IsLocal(p.Group) && p.Group != "." && p.Invocation != "" && p.GroupIdentity.Device > 0 && p.GroupIdentity.Inode > 0
}
diff --git a/internal/rootparticipation/ledger.go b/internal/rootparticipation/ledger.go
index 6016ec4da..4bc03d0d7 100644
--- a/internal/rootparticipation/ledger.go
+++ b/internal/rootparticipation/ledger.go
@@ -253,9 +253,15 @@ func (l Ledger) RecoverAcquisition(ctx context.Context) error {
if c.Pending == "" {
return nil
}
- if err := l.idle(ctx, c); err != nil {
+ if err := l.idle(context.WithValue(ctx, acquisitionRecoveryKey{}, true), c); err != nil {
return err
}
c.Pending = ""
return l.writeCensus(c)
}
+
+type acquisitionRecoveryKey struct{}
+
+func IsAcquisitionRecovery(ctx context.Context) bool {
+ return ctx.Value(acquisitionRecoveryKey{}) == true
+}