carlos / platform Public

Known-no-start only when nothing of a wake started; census ruling 292fix

Census ruling 292fix (Plan 14 controller, 2026-10-15) and Astra's
re-review of 04101123.

The rule for both refusals: a refusal is known-no-start evidence only
when nothing of that wake has started.

- Pre-exec refusals. doWake's order already puts the main spawn's gate
  before every other stage: the seed has finished, and the replica and
  sidecar come after the spawn. The not-started record is still made
  conditional on the wake holding no process, replica or sidecar of its
  own (wakeStartedNothing). Otherwise the wake goes through
  cleanupFailedWake and its proofs.
- Pre-lease refusals (re-review P2). A wake refused before its lease
  started nothing, so it is recorded as known not started. Before this,
  its zero observation left the host's stop history incomplete. A
  verify request admitted on an edge's cached row, after the rollback
  closed the import gate and before it fenced, could then block the
  rollback for good.
- A known-no-start record is evidence only for a host with no stop
  history. On a host that has one, it leaves that record, its
  obligations and their owner exactly as they were.

Tests: a late refusal at the sidecar's gate, and one after the replica
opened, both still go through the checked stop and mark nothing held. A
cached-row wake refused before its lease lets the rollback restore the
original route in the same pass, with no stop history and after a clean
stop; it fails without the pre-lease record. A refusal leaves every
earlier record, its owner included, as it was.

Census: the block is now census ruling 292fix, an extension of census
ruling 292 for Astra's pre-land P2, in its own marked block of the
seeded overlay. doWake, cleanupFailedWake and reapUnprovenWake stay
accepted legacy. doWake is re-digested, and the activator envelope and
the seal are re-pinned. No new unit needed a row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev d078e435b9a93a6a18159e794f0e66435a39d19c parent 0410112
6 files changed, +218 −33
  • internal/activator/activator.go +20 −8
  • internal/activator/maintenance.go +20 −4
  • internal/activator/seed_rollback_stop_test.go +157 −1
  • internal/provision/plan14_legacy_identity_test.go +8 −7
  • internal/provision/plan14_root_reads_test.go +5 −5
  • internal/provision/testdata/plan14-root-reads-seeded-instances.tsv +8 −8

CI passed — run details

CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok  	amadan.net/carlos/platform.git/launchctx	0.011s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
go vet ./...
gofmt -l .: clean
go test ./... -count=1 -timeout 30m
ok  	github.com/carlosframework/platform	297.872s
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	2.834s
ok  	github.com/carlosframework/platform/cmd/moveprobe	93.232s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.704s
ok  	github.com/carlosframework/platform/internal/activator	249.608s
ok  	github.com/carlosframework/platform/internal/agentsock	0.016s
ok  	github.com/carlosframework/platform/internal/alerting	0.005s
ok  	github.com/carlosframework/platform/internal/analytics	0.019s
ok  	github.com/carlosframework/platform/internal/appconfig	2.493s
ok  	github.com/carlosframework/platform/internal/apperr	0.079s
ok  	github.com/carlosframework/platform/internal/applog	0.141s
ok  	github.com/carlosframework/platform/internal/awsinv	0.003s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.004s
ok  	github.com/carlosframework/platform/internal/boxid	0.861s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.008s
ok  	github.com/carlosframework/platform/internal/caps	0.012s
ok  	github.com/carlosframework/platform/internal/certwarm	0.002s
ok  	github.com/carlosframework/platform/internal/cgroup	0.693s
ok  	github.com/carlosframework/platform/internal/changelog	0.314s
ok  	github.com/carlosframework/platform/internal/clamd	0.054s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
ok  	github.com/carlosframework/platform/internal/console	392.101s
?   	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.074s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.009s
ok  	github.com/carlosframework/platform/internal/dbverify	0.004s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.003s
ok  	github.com/carlosframework/platform/internal/docsite	0.005s
ok  	github.com/carlosframework/platform/internal/domains	2.376s
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.680s
?   	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.131s
ok  	github.com/carlosframework/platform/internal/gate	1.657s
ok  	github.com/carlosframework/platform/internal/genselect	0.015s
ok  	github.com/carlosframework/platform/internal/geo	0.015s
ok  	github.com/carlosframework/platform/internal/geoip	0.030s
ok  	github.com/carlosframework/platform/internal/host	136.331s
ok  	github.com/carlosframework/platform/internal/hostactors	0.346s
ok  	github.com/carlosframework/platform/internal/hostbox	2.367s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.544s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.007s
ok  	github.com/carlosframework/platform/internal/hostexec	4.901s
ok  	github.com/carlosframework/platform/internal/hostfence	0.051s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.479s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.014s
ok  	github.com/carlosframework/platform/internal/hostguard	1.014s
ok  	github.com/carlosframework/platform/internal/hostops	1.602s
?   	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.462s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.139s
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.188s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.719s
ok  	github.com/carlosframework/platform/internal/instancefs	0.006s
ok  	github.com/carlosframework/platform/internal/instancekey	0.005s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.010s
ok  	github.com/carlosframework/platform/internal/layout	1.539s
ok  	github.com/carlosframework/platform/internal/lease	9.818s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.008s
ok  	github.com/carlosframework/platform/internal/ledger	0.025s
ok  	github.com/carlosframework/platform/internal/mail	0.165s
ok  	github.com/carlosframework/platform/internal/maintenance	0.082s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.528s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.089s
ok  	github.com/carlosframework/platform/internal/metrics	0.005s
ok  	github.com/carlosframework/platform/internal/moves	21.070s
ok  	github.com/carlosframework/platform/internal/moves/archive	0.774s
ok  	github.com/carlosframework/platform/internal/objstore	0.206s
ok  	github.com/carlosframework/platform/internal/ops	0.193s
ok  	github.com/carlosframework/platform/internal/park	0.225s
ok  	github.com/carlosframework/platform/internal/privatefile	0.004s
ok  	github.com/carlosframework/platform/internal/provision	15.777s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.009s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	4.938s
ok  	github.com/carlosframework/platform/internal/relay	7.234s
ok  	github.com/carlosframework/platform/internal/release	19.707s
ok  	github.com/carlosframework/platform/internal/rollout	3.694s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.004s
ok  	github.com/carlosframework/platform/internal/rootaws	0.007s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.007s
ok  	github.com/carlosframework/platform/internal/rootstate	0.026s
ok  	github.com/carlosframework/platform/internal/runs	0.572s
ok  	github.com/carlosframework/platform/internal/s3compat	0.005s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.493s
ok  	github.com/carlosframework/platform/internal/seeds	0.013s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	9.607s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	3.734s
ok  	github.com/carlosframework/platform/internal/status	0.038s
ok  	github.com/carlosframework/platform/internal/steering	0.008s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.206s
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.001s
ok  	github.com/carlosframework/platform/internal/vet	1.561s
=== 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: 46399650 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.024s
=== RUN   TestT4StorageProbeMinio
2026/10/15 08:48:06 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18DEA7B25C2359F4, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.020s
minio contract: PASS
diff --git a/internal/activator/activator.go b/internal/activator/activator.go
index 2a0b08d74..9dfa8060a 100644
--- a/internal/activator/activator.go
+++ b/internal/activator/activator.go
@@ -2082,7 +2082,11 @@ func (a *Service) doWake(ctx context.Context, e *actEntry) (*wokenInstance, erro
}
rt := e.route
host := rt.Host
+ // Until the lease (step 1) this wake starts nothing, so a refusal before
+ // it is known-no-start evidence, which leaves the host's stop history as
+ // it was (seeded pre-land re-review, P2; census ruling 292fix).
w := &wokenInstance{stopOutcome: &stopObservation{}}
+ w.stopOutcome.notStarted()
if ctx.Err() != nil || !a.wakeAllowed(host) {
return w, ErrFenced
}
@@ -2224,6 +2228,7 @@ func (a *Service) doWake(ctx context.Context, e *actEntry) (*wokenInstance, erro
// single writer. Contention is not retried here — on one box it means a
// previous incarnation hasn't released; tell the client to come back after
// the TTL instead of stalling its request.
+ *w.stopOutcome = stopObservation{}
lease, err := a.backend.Acquire(ctx, rt)
if err != nil {
if lease != nil {
@@ -2521,15 +2526,22 @@ func (a *Service) doWake(ctx context.Context, e *actEntry) (*wokenInstance, erro
// job systemd queued, which cancelling the client does not cancel)
// is stopped and proven gone before its lease goes (#292, P1).
w.legacyUncertain = legacyStartMayRun(err)
- if proc != nil || w.genUnproven || w.legacyUncertain {
+ switch {
+ case proc != nil || w.genUnproven || w.legacyUncertain:
a.cleanupFailedWake(ctx, rt, w, lease)
- } else {
- // A start its pre-exec gate refused ran nothing: this wake's
- // stop is known, not uncertain, and its other stages were
- // never reached (seeded pre-land review, P2).
- if launchNotStarted(err) {
- w.stopOutcome.notStarted()
- }
+ case launchNotStarted(err) && wakeStartedNothing(w):
+ // A start its pre-exec gate refused ran nothing, and this wake
+ // has started nothing else: its seed (step 3) has finished, and
+ // its replica (step 5) and sidecar (step 6) come after this
+ // spawn. Its stop is known, not uncertain (seeded pre-land
+ // review, P2; census ruling 292fix).
+ w.stopOutcome.notStarted()
+ w.stopOutcome.leaseReleased = a.giveBack(ctx, host, lease)
+ case launchNotStarted(err):
+ // Something of this wake is running after all: it goes through
+ // the failed wake's cleanup and its proofs, not the shortcut.
+ a.cleanupFailedWake(ctx, rt, w, lease)
+ default:
w.stopOutcome.leaseReleased = a.giveBack(ctx, host, lease)
}
return w, fmt.Errorf("start %s: %w", host, err)
diff --git a/internal/activator/maintenance.go b/internal/activator/maintenance.go
index 9c71a5ab3..14c710efd 100644
--- a/internal/activator/maintenance.go
+++ b/internal/activator/maintenance.go
@@ -43,12 +43,22 @@ type stopObservation struct {
// wake's proof discharges it (reapUnprovenWake); any other source of
// uncertainty clears it, and then the uncertainty stays.
owedBy *unprovenWake
+ // noStart: a wake refused before anything of it started (notStarted).
+ // It is evidence for a host with no stop history, and leaves any
+ // history the host has exactly as it was (rememberStopLocked).
+ noStart bool
}
func (o *stopObservation) complete() bool {
return o != nil && !o.uncertain && o.mainStop && o.mainExited && o.sidecarStop && o.sidecarExited && o.replicaClosed
}
+// wakeStartedNothing reports whether a failed wake holds no process,
+// replica or sidecar of its own: the condition for notStarted.
+func wakeStartedNothing(w *wokenInstance) bool {
+ return w != nil && w.proc == nil && w.rep == nil && w.sidecar == nil
+}
+
// notStarted records a wake whose launch is known not to have started: no
// process, no sidecar and no replica of it exist, so every stage holds and
// nothing is uncertain (seeded pre-land review, P2).
@@ -57,6 +67,7 @@ func (o *stopObservation) notStarted() {
o.sidecarStop, o.sidecarExited = true, true
o.replicaClosed = true
o.uncertain = false
+ o.noStart = true
}
// FenceAndStop installs a local fence before collecting stop evidence. Once this
@@ -304,14 +315,19 @@ func (a *Service) rememberStopLocked(host string, observation *stopObservation)
if a.stopHistory == nil {
a.stopHistory = make(map[string]*stopObservation)
}
- if prior := a.stopHistory[host]; prior != nil && prior.uncertain {
+ prior := a.stopHistory[host]
+ if observation.noStart && prior != nil {
+ // A wake that started nothing says nothing about what an earlier
+ // one left: that record, its obligations and their owner, stay.
+ return
+ }
+ if prior != nil && prior.uncertain {
retained := *observation
retained.uncertain = true
// The prior's uncertainty stays one retained wake's alone only
- // while nothing else adds to it: a wake refused before it did
- // anything (the zero observation), or that same wake again.
+ // while that same wake is the one recording.
retained.owedBy = nil
- if *observation == (stopObservation{}) || (observation.uncertain && observation.owedBy == prior.owedBy) {
+ if observation.uncertain && observation.owedBy == prior.owedBy {
retained.owedBy = prior.owedBy
}
observation = &retained
diff --git a/internal/activator/seed_rollback_stop_test.go b/internal/activator/seed_rollback_stop_test.go
index 53686535d..42244dc95 100644
--- a/internal/activator/seed_rollback_stop_test.go
+++ b/internal/activator/seed_rollback_stop_test.go
@@ -36,6 +36,9 @@ type importRollback struct {
mu sync.Mutex
removed int // RemoveLocal calls: the import's local database
replicas int // DeleteReplica calls: the host's replica
+ // beforeFence, when set, runs once as the rollback's fence hook is
+ // entered: after step 1 closed the gate, before the fence.
+ beforeFence func()
}
func newImportRollback(t *testing.T) *importRollback {
@@ -118,6 +121,10 @@ func newImportRollback(t *testing.T) *importRollback {
// rollbackFence is internal/host's fenceForRollback over this Service.
func (x *importRollback) rollbackFence(ctx context.Context, host string, gen uint64) (bool, error) {
+ if f := x.beforeFence; f != nil {
+ x.beforeFence = nil
+ f()
+ }
if gen == 0 {
return false, ErrFenceGeneration
}
@@ -418,7 +425,9 @@ func TestTheLegacyProofDischargesOnlyItsOwnUncertainty(t *testing.T) {
a.rememberStopLocked(host, &stopObservation{uncertain: true})
}
a.rememberStopLocked(host, &stopObservation{uncertain: true, owedBy: u})
- a.rememberStopLocked(host, &stopObservation{}) // a wake refused before it did anything
+ var refused stopObservation
+ refused.notStarted() // a wake refused before it started anything
+ a.rememberStopLocked(host, &refused)
a.unprovenWakes = map[string]*unprovenWake{host: u}
a.mu.Unlock()
a.reapUnprovenWake(host, u)
@@ -434,3 +443,150 @@ func TestTheLegacyProofDischargesOnlyItsOwnUncertainty(t *testing.T) {
reg.Close()
}
}
+
+// The not-started record is only for a wake that started nothing: one
+// that holds a process, a replica or a sidecar of its own does not get it.
+func TestOnlyAWakeThatStartedNothingIsRecordedNotStarted(t *testing.T) {
+ if !wakeStartedNothing(&wokenInstance{}) {
+ t.Fatal("an empty wake reads as having started something")
+ }
+ for name, w := range map[string]*wokenInstance{
+ "process": {proc: &maintenanceProc{}},
+ "replica": {rep: &maintenanceRep{}},
+ "sidecar": {sidecar: &maintenanceProc{}},
+ } {
+ if wakeStartedNothing(w) {
+ t.Fatalf("a wake holding a %s reads as having started nothing", name)
+ }
+ }
+}
+
+// A refusal at a gate later in the wake than the main spawn's, with the
+// main process and its replica already running: those stages still go
+// through the checked stop and are never marked held by the refusal.
+// The sidecar's start (step 6) is the wake's last gate; a replica that
+// opens and then fails is the stage before it.
+func TestALateRefusalMarksNothingHeld(t *testing.T) {
+ refused := launchRefused{ErrFenced}
+
+ t.Run("the sidecar's gate", func(t *testing.T) {
+ a, base, _, reg := testActivator(t, time.Hour)
+ defer reg.Close()
+ base.sidecarStaged = true
+ main, partial, rep := &maintenanceProc{}, &maintenanceProc{}, &maintenanceRep{}
+ a.backend = &maintenanceBackend{fakeBackend: base, main: main, side: partial, rep: rep, sideFailure: refused}
+ rt := testRoute("late-sidecar.example")
+ release, err := a.beginRequest(t.Context(), rt)
+ if err != nil {
+ t.Fatal(err)
+ }
+ release()
+ if !a.StopUncertain(rt.Host) {
+ t.Fatal("a running wake reads as stopped")
+ }
+ if main.calls.Load() != 0 || rep.calls.Load() != 0 {
+ t.Fatal("the refusal stopped the main process or closed the replica")
+ }
+ if proof, err := a.FenceAndStop(maintenanceContext(t, time.Second), rt.Host, 1); err != ErrStopUnproven || proof != nil {
+ t.Fatal("a refused sidecar's returned handle yielded trusted evidence", err)
+ }
+ if main.calls.Load() != 1 || rep.calls.Load() != 1 || partial.calls.Load() != 1 {
+ t.Fatalf("the stop skipped a stage: main %d, replica %d, sidecar %d", main.calls.Load(), rep.calls.Load(), partial.calls.Load())
+ }
+ })
+
+ t.Run("after the replica opened", func(t *testing.T) {
+ a, base, _, reg := testActivator(t, time.Hour)
+ defer reg.Close()
+ main, rep := &maintenanceProc{}, &maintenanceRep{}
+ a.backend = &maintenanceBackend{fakeBackend: base, main: main, rep: rep, replicateFailure: refused}
+ rt := testRoute("late-replica.example")
+ if _, err := a.beginRequest(t.Context(), rt); !errors.Is(err, ErrFenced) {
+ t.Fatalf("wake = %v, want the refusal", err)
+ }
+ if main.calls.Load() != 1 || !main.Exited() || rep.calls.Load() != 1 {
+ t.Fatalf("the failed wake's cleanup skipped a stage: main %d, replica %d", main.calls.Load(), rep.calls.Load())
+ }
+ if !a.StopUncertain(rt.Host) {
+ t.Fatal("a refusal after the spawn marked the wake's stages held")
+ }
+ if proof, err := a.FenceAndStop(maintenanceContext(t, time.Second), rt.Host, 1); err != ErrStopUnproven || proof != nil {
+ t.Fatal("a late refusal yielded trusted evidence", err)
+ }
+ })
+}
+
+// Astra's re-review, P2: the rollback has closed the import's gate but not
+// yet fenced the host, and a verify request the edge admitted on its
+// cached row reaches the activator. The wake re-reads the row, finds the
+// gate changed and refuses before its lease. That refusal started nothing:
+// it must leave the host's stop history as it was, and the rollback
+// restores the original route in the same pass, with no restart.
+func TestImportRollbackAfterACachedRowWakeRefusedBeforeItsLease(t *testing.T) {
+ for _, history := range []string{"none", "a clean stop"} {
+ t.Run(history, func(t *testing.T) {
+ x := newImportRollback(t)
+ cached := *x.row(t) // the edge's copy, gated by the verify hash
+ if history == "a clean stop" {
+ arrangeAwake(t, x.a, cached)
+ x.a.mu.Lock()
+ e := x.a.entries[cached.Host]
+ x.a.beginStopLocked(e, StopIdle)
+ stopped := e.stopped
+ x.a.mu.Unlock()
+ <-stopped
+ if x.a.StopUncertain(cached.Host) {
+ t.Fatal("the verify wake's clean stop reads as uncertain")
+ }
+ }
+ starts := x.b.startCount()
+ x.abort(t)
+ var refused error
+ x.beforeFence = func() {
+ release, err := x.a.beginRequest(context.Background(), cached)
+ if err == nil {
+ release()
+ }
+ refused = err
+ }
+ x.pass(t)
+ if refused == nil {
+ t.Fatal("a wake admitted on the cached row was served after the gate closed")
+ }
+ if x.b.startCount() != starts {
+ t.Fatal("the refused wake started the instance")
+ }
+ x.wantRestored(t)
+ })
+ }
+}
+
+// A refusal before anything of a wake started leaves every earlier record
+// exactly as it was: an uncertain one with its owner, and an incomplete
+// one.
+func TestAWakeThatStartedNothingLeavesTheHistoryAsItWas(t *testing.T) {
+ a, _, _, reg := testActivator(t, time.Hour)
+ defer reg.Close()
+ u := &unprovenWake{legacy: true}
+ priors := map[string]*stopObservation{
+ "owed.example.com": {uncertain: true, owedBy: u},
+ "incomplete.example.com": {mainStop: true, mainExited: true, sidecarStop: true},
+ }
+ var refused stopObservation
+ refused.notStarted()
+ a.mu.Lock()
+ for host, prior := range priors {
+ a.rememberStopLocked(host, prior)
+ a.rememberStopLocked(host, &refused)
+ }
+ a.rememberStopLocked("new.example.com", &refused)
+ for host, prior := range priors {
+ if a.stopHistory[host] != prior {
+ t.Fatalf("%s: the refusal replaced %+v with %+v", host, prior, a.stopHistory[host])
+ }
+ }
+ a.mu.Unlock()
+ if a.StopUncertain("new.example.com") {
+ t.Fatal("a host whose only wake started nothing reads as uncertain")
+ }
+}
diff --git a/internal/provision/plan14_legacy_identity_test.go b/internal/provision/plan14_legacy_identity_test.go
index cd62c94bc..bf0498ca0 100644
--- a/internal/provision/plan14_legacy_identity_test.go
+++ b/internal/provision/plan14_legacy_identity_test.go
@@ -481,9 +481,10 @@ func TestPlan14CarlosRunOverlayPreservesLegacySeal(t *testing.T) {
// overlay is loaded last, after carlos-run's. This pins what the ruling
// writes: 100 new frozen fingerprints, 115 rows, every basis naming the
// ruling, main's authority rows kept, the ruled classes, nothing removed.
-// Three rows are the pre-land fix's PROPOSED-seeded-instances-292fix
-// block, pending a ruling: Service.doWake (moved there from the ruled
-// rows) and #292's cleanupFailedWake and reapUnprovenWake.
+// Three rows are the census ruling 292fix block (Plan 14 controller,
+// 2026-10-15), an extension of census ruling 292 for the seeded pre-land
+// review's P2: Service.doWake (moved there from the seeded rows) and
+// #292's cleanupFailedWake and reapUnprovenWake.
func TestPlan14SeededInstancesOverlayPreservesLegacySeal(t *testing.T) {
root := censusRoot(t)
inv := rootReadInventory{Entries: map[string]classifiedRootRead{}, Coverage: map[string]string{}}
@@ -517,9 +518,9 @@ func TestPlan14SeededInstancesOverlayPreservesLegacySeal(t *testing.T) {
}
// Census ruling seeded-instances seal: 96 service-user and CLI rows plus
// activationGuard and activationRule, accepted legacy (J1) as on main,
- // less doWake, which moved to the proposed block; and that block's
+ // less doWake, which moved to the 292fix block; and that block's
// three rows.
- const seal = "680b47f44068ad0971b8de607ab2f5bda86104d6f7f537af9024d6cfe2721dd5"
+ const seal = "10e024eccd584f40b64e959a105c551302585e3e14e55104fc250dae5960898a"
if errs := checkRootReadLegacy(inv, seal); len(errs) != 0 {
t.Fatal(errs)
}
@@ -537,8 +538,8 @@ func TestPlan14SeededInstancesOverlayPreservesLegacySeal(t *testing.T) {
marker := "census-ruling-seeded-instances"
if proposed[key] {
// Main's rows, changed by the pre-land fix: their class is
- // kept, pending the controller's ruling.
- marker = "PROPOSED-seeded-instances-292fix"
+ // kept, under census ruling 292fix.
+ marker = "census-ruling-292fix"
if before[key].Class != "legacy" {
t.Fatalf("%s was %s on main; the fix's block keeps only legacy rows", key, before[key].Class)
}
diff --git a/internal/provision/plan14_root_reads_test.go b/internal/provision/plan14_root_reads_test.go
index 20a69dd95..b52458a3c 100644
--- a/internal/provision/plan14_root_reads_test.go
+++ b/internal/provision/plan14_root_reads_test.go
@@ -727,14 +727,14 @@ func TestPlan14RootReadCensus(t *testing.T) {
// seeds floor's root readers are authority F,J4 and deny-only;
// CollectCensusSeeds and ImportJournalHosts are authority G on the
// condition, recorded in the overlay's header, that they only ever feed
- // a refusal. Its last block, PROPOSED-seeded-instances-292fix, is NOT
- // ruled on: the pre-land fix's three changed rows of main's units
- // (doWake, cleanupFailedWake, reapUnprovenWake), pending the Plan 14
- // controller.
+ // a refusal. Its last block is census ruling 292fix (Plan 14 controller,
+ // 2026-10-15), an extension of census ruling 292 for the seeded pre-land
+ // review's P2: doWake, cleanupFailedWake and reapUnprovenWake, accepted
+ // legacy as on main.
if err = loadRootReadInventory(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-seeded-instances.tsv"), &inv, true); err != nil {
t.Fatal(err)
}
- seal = "680b47f44068ad0971b8de607ab2f5bda86104d6f7f537af9024d6cfe2721dd5"
+ seal = "10e024eccd584f40b64e959a105c551302585e3e14e55104fc250dae5960898a"
errs := append(compareRootReads(got, inv), checkRootReadLegacy(inv, seal)...)
errs = append(errs, checkRootReadDeclarations(got, inv)...)
if len(errs) > 0 {
diff --git a/internal/provision/testdata/plan14-root-reads-seeded-instances.tsv b/internal/provision/testdata/plan14-root-reads-seeded-instances.tsv
index a85c17cb2..8bdc72d4a 100644
--- a/internal/provision/testdata/plan14-root-reads-seeded-instances.tsv
+++ b/internal/provision/testdata/plan14-root-reads-seeded-instances.tsv
@@ -213,7 +213,7 @@ internal/schedule/runner.go Runner.Tick legacy accepted-legacy accepted-legacy;r
@fields A,Account,Accounts,AddedAt,Addr,Addrs,Alias,Aliases,Alive,App,Archives,Args,At,Attempts,B,Backing,Bucket,BuildState,Callback,CallbackError,CallbackFailed,Canary,Caps,CertExpiresAt,Changed,Channel,CheckedAt,ClaimIncarnation,ClaimOwner,ClaimOwnership,ClaimRowID,ClaimStamp,Class,Contents,CreatedAt,DBPath,DNSCheckedAt,DNSStatus,Deadline,Demand,Digest,DoneBefore,Due,DueAt,Env,EnvBefore,Environment,Error,ErrorKind,ExcludeHosts,ExpectGood,ExpectOp,Expected,F,Failed,FenceGen,Fleet,FleetBox,FleetBoxes,Fleets,From,FromDB,FromOp,FromSock,Gate,GoodDemand,GoodDigest,GoodVersion,Hash,Health,Held,Hibernate,HoldOp,Host,Hosts,ID,Id,IdValue,Idle,IdleSeconds,ImportGate,Journal,Keep,Kind,Label,LastAt,LastError,LastMs,LastStatus,Limit,MaxRun,MaxRunSeconds,Method,Ms,Name,Next,NextAt,NextDue,NextGood,NextValue,Now,Number,Number64,Observed,ObservedValue,Old,On,Op,Original,Owner,Ownership,P,Path,Paths,Platform,Pool,Prefix,Prev,PrevVersion,Provisioned,RecordKey,Region,Registration,Replica,Replicas,Replication,Retirement,Route,Routes,Row,Rows,Seed,SeedID,SeedSource,ServingVersion,Slug,Socket,Spec,Sqid,State,Status,StatusNamed,Steering,StringMap,T,TValue,Tags,Target,Targets,Text,Timer,Timers,Times,To,ToDB,ToOp,ToSock,TokenHash,Tunnel,Unit,UpdateSettings,Ver,Version,W,WakeIncarnation,Wildcards,closed,conn,connect,ctx,d,dec,done,enc,full,input,mu,output,path,remote,sql,wait
@coverage . 1e94e1644297311bfaeec977a479d454eb39c6ab51e7cdd57f2dbc452cd92033
@coverage cmd/console 0554957d7bc31eb4553b6a98c7481738d11bfd4fb809277bf7e0cc723422ad2e
-@coverage internal/activator a7faaf34a27db6a4603c007fd8ab7676fd582a106518144b467ac3c9cae88e35
+@coverage internal/activator d78d3443831bd65eddf6f498ba046db27b836208f3237426322ed71188183636
@coverage internal/console 2e39e3ffb055cf7c230c7d231e7d8adcf0f8543222eac015f610e920f91c217b
@coverage internal/dbdoor 0109cd1cd4f0ca14d57bc6562166424273f770d8261e60b738c61f9a6d080dd9
@coverage internal/domains 3c7159e24bbb66faec61bc32d56019501fb19c072f10eacca9fd920c3cb7c0d6
@@ -230,10 +230,10 @@ internal/schedule/runner.go Runner.Tick legacy accepted-legacy accepted-legacy;r
@coverage internal/release 55c36a3e2a1b32a8736c3f0404a8e6442d747cd5ecbde68a0e8b3364e6fe3324
@coverage internal/schedule 295d28a90ab322dd05781d30bde48bffcf8e8eb49884460c02a1b559bf72259b
@coverage internal/seeds 433f3cc6c1b885bf7b95c4530fc1af78e5d3b64b578a7dee919797afbdc09967
-# PROPOSED-seeded-instances-292fix: pending the Plan 14 controller's ruling. The pre-land fix for the seeded review's P2 (a failed import wake's stop kept uncertain after it is known) changes three units main rows under census ruling 292 and census ruling owed, which census-ruling-seeded-instances does not cover. Each keeps its current class, accepted legacy; nothing is reclassified. Service.doWake's row moves here from the seeded rows above, since its body now carries both.
-@legacy 3011833f100e4a0b582906168dca2f04b635adebf93e8857f2f1d3a9bce36a6e internal/activator/activator.go Service.doWake legacy accepted-legacy accepted-legacy;rev3=PROPOSED-seeded-instances-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): main's body (census ruling 292's legacy start mark and lease give-back; census ruling owed's pending record) with the seeded change ruled under census-ruling-seeded-instances (the refreshed row checked against the row the wake began from, twice; the seed's first upload recorded after the wake), and now one more line on a failed start: a start its pre-exec gate refused (launchNotStarted: the exec gate, the pending record or its recheck answered no before root was asked) is recorded as known not started (stopObservation.notStarted) instead of uncertain. Read sites unchanged; no new read and no root decision registry:DBPath@1032,registry:DBPath@1048,registry:DBPath@1175,registry:DBPath@1186,registry:DBPath@21121,registry:Host@1014,registry:Host@1166,registry:Host@1500,registry:Host@174,registry:Host@2360,registry:Host@4335,registry:Host@861,registry:Host@936,registry:Replication@21156,registry:Seed@11684,registry:Socket@1061,registry:Socket@1077,registry:Socket@1200,registry:Socket@1211,whole:Route@10535,whole:Route@11785,whole:Route@11856,whole:Route@1395,whole:Route@1400,whole:Route@14240,whole:Route@1454,whole:Route@1460,whole:Route@1526,whole:Route@1534,whole:Route@159,whole:Route@16098,whole:Route@16744,whole:Route@18051,whole:Route@18205,whole:Route@18561,whole:Route@18655,whole:Route@19327,whole:Route@20569,whole:Route@20717,whole:Route@21230,whole:Route@21589,whole:Route@2250,whole:Route@22525,whole:Route@22620,whole:Route@3704,whole:Route@3963,whole:Route@4225,whole:Route@4230,whole:Route@4287,whole:Route@4293,whole:Route@4362,whole:Route@4370,whole:Route@5158,whole:Route@6157,whole:Route@7190,whole:Route@8071,whole:Route@959,whole:Route@9653,whole:Route@9845 f6ac517702861739881d3561b21e759f21deb7038676edd7e411d440205d6471
-internal/activator/activator.go Service.doWake legacy accepted-legacy accepted-legacy;rev3=PROPOSED-seeded-instances-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): main's body (census ruling 292's legacy start mark and lease give-back; census ruling owed's pending record) with the seeded change ruled under census-ruling-seeded-instances (the refreshed row checked against the row the wake began from, twice; the seed's first upload recorded after the wake), and now one more line on a failed start: a start its pre-exec gate refused (launchNotStarted: the exec gate, the pending record or its recheck answered no before root was asked) is recorded as known not started (stopObservation.notStarted) instead of uncertain. Read sites unchanged; no new read and no root decision registry:DBPath@1032,registry:DBPath@1048,registry:DBPath@1175,registry:DBPath@1186,registry:DBPath@21121,registry:Host@1014,registry:Host@1166,registry:Host@1500,registry:Host@174,registry:Host@2360,registry:Host@4335,registry:Host@861,registry:Host@936,registry:Replication@21156,registry:Seed@11684,registry:Socket@1061,registry:Socket@1077,registry:Socket@1200,registry:Socket@1211,whole:Route@10535,whole:Route@11785,whole:Route@11856,whole:Route@1395,whole:Route@1400,whole:Route@14240,whole:Route@1454,whole:Route@1460,whole:Route@1526,whole:Route@1534,whole:Route@159,whole:Route@16098,whole:Route@16744,whole:Route@18051,whole:Route@18205,whole:Route@18561,whole:Route@18655,whole:Route@19327,whole:Route@20569,whole:Route@20717,whole:Route@21230,whole:Route@21589,whole:Route@2250,whole:Route@22525,whole:Route@22620,whole:Route@3704,whole:Route@3963,whole:Route@4225,whole:Route@4230,whole:Route@4287,whole:Route@4293,whole:Route@4362,whole:Route@4370,whole:Route@5158,whole:Route@6157,whole:Route@7190,whole:Route@8071,whole:Route@959,whole:Route@9653,whole:Route@9845 f6ac517702861739881d3561b21e759f21deb7038676edd7e411d440205d6471
-@legacy ce713e012707529182b59431a9e24c4a1c96b06c6ceeb408aa3ff800d32ff99e internal/activator/maintenance.go Service.cleanupFailedWake legacy accepted-legacy accepted-legacy;rev3=PROPOSED-seeded-instances-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): census ruling 292's body; when #292's whole proof holds for an aborted legacy start (legacyRunGone: the unit stopped and its cgroup empty) this wake's uncertainty is discharged and the stages it never reached (no sidecar, no replica) are recorded as held; when the proof fails the start is retained exactly as before, now named as the owner of this wake's uncertainty (owedBy). Read sites unchanged; the proof still only decides release-or-keep; no new read and no root decision registry:Host@1824,registry:Host@2337,registry:Host@3288,registry:Host@3628,registry:Host@4240,whole:Route@1691,whole:Route@1930,whole:Route@1934,whole:Route@2970,whole:Route@3653,whole:Route@3657 f59321652db8dc67a25ffabf3589b727b12b25d1136b2b18c48f97d6929d93b0
-internal/activator/maintenance.go Service.cleanupFailedWake legacy accepted-legacy accepted-legacy;rev3=PROPOSED-seeded-instances-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): census ruling 292's body; when #292's whole proof holds for an aborted legacy start (legacyRunGone: the unit stopped and its cgroup empty) this wake's uncertainty is discharged and the stages it never reached (no sidecar, no replica) are recorded as held; when the proof fails the start is retained exactly as before, now named as the owner of this wake's uncertainty (owedBy). Read sites unchanged; the proof still only decides release-or-keep; no new read and no root decision registry:Host@1824,registry:Host@2337,registry:Host@3288,registry:Host@3628,registry:Host@4240,whole:Route@1691,whole:Route@1930,whole:Route@1934,whole:Route@2970,whole:Route@3653,whole:Route@3657 f59321652db8dc67a25ffabf3589b727b12b25d1136b2b18c48f97d6929d93b0
-@legacy 1cfba7fa3532305b054f166162cde4ba939462488d3a995b07aa10ed47facee8 internal/activator/opset.go Service.reapUnprovenWake legacy accepted-legacy accepted-legacy;rev3=PROPOSED-seeded-instances-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): census ruling 292's body; when the retried proof holds for a retained legacy start and the host's stop history is uncertain only because of that start (owedBy), the uncertainty is discharged and the stages the failed wake never reached are recorded as held; any other uncertainty is kept through rememberStopLocked as before. Read sites unchanged; no new read and no root decision whole:Route@439,whole:Route@493 7484890de454d363b9e191d28b1c70f8ea6339492169931c2f47e0dfb02db852
-internal/activator/opset.go Service.reapUnprovenWake legacy accepted-legacy accepted-legacy;rev3=PROPOSED-seeded-instances-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): census ruling 292's body; when the retried proof holds for a retained legacy start and the host's stop history is uncertain only because of that start (owedBy), the uncertainty is discharged and the stages the failed wake never reached are recorded as held; any other uncertainty is kept through rememberStopLocked as before. Read sites unchanged; no new read and no root decision whole:Route@439,whole:Route@493 7484890de454d363b9e191d28b1c70f8ea6339492169931c2f47e0dfb02db852
+# Census ruling 292fix (Plan 14 controller, 2026-10-15): an extension of census ruling 292 (#292, fix-shutdown-lease-292) for the seeded pre-land review's P2 (Astra: a failed import wake's stop kept uncertain after it is known, which blocked the import rollback). The fix changes three units main rows under census ruling 292 and census ruling owed; each stays accepted legacy, as on main. Service.doWake's row moved here from the seeded rows above, since its body now carries both changes. The not-started record is conditional on the wake having started nothing (wakeStartedNothing: no process, replica or sidecar of its own). A refusal before the wake's lease (step 1) is known-no-start evidence too, and like a pre-exec refusal it leaves any stop history the host already has, its obligations and their owner, exactly as it was (Astra's re-review, P2).
+@legacy f916bf7129687212adcc3a169e2db3b8bea19ed882d689654aea6df34de45a91 internal/activator/activator.go Service.doWake legacy accepted-legacy accepted-legacy;rev3=census-ruling-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): main's body (census ruling 292's legacy start mark and lease give-back; census ruling owed's pending record) with the seeded change ruled under census-ruling-seeded-instances (the refreshed row checked against the row the wake began from, twice; the seed's first upload recorded after the wake), and now one more line on a failed start: a start its pre-exec gate refused (launchNotStarted: the exec gate, the pending record or its recheck answered no before root was asked) is recorded as known not started (stopObservation.notStarted) instead of uncertain, only when the wake has started nothing else (wakeStartedNothing); otherwise it goes through cleanupFailedWake and its proofs. A refusal before its lease (step 1), which has started nothing, is recorded the same way. Read sites unchanged; no new read and no root decision registry:DBPath@1276,registry:DBPath@1292,registry:DBPath@1419,registry:DBPath@1430,registry:DBPath@21825,registry:Host@1105,registry:Host@1180,registry:Host@1258,registry:Host@1410,registry:Host@174,registry:Host@1744,registry:Host@2604,registry:Host@4579,registry:Replication@21860,registry:Seed@11964,registry:Socket@1305,registry:Socket@1321,registry:Socket@1444,registry:Socket@1455,whole:Route@10125,whole:Route@10815,whole:Route@1203,whole:Route@12065,whole:Route@12136,whole:Route@14520,whole:Route@159,whole:Route@16378,whole:Route@1639,whole:Route@1644,whole:Route@1698,whole:Route@17024,whole:Route@1704,whole:Route@1770,whole:Route@1778,whole:Route@18331,whole:Route@18485,whole:Route@18841,whole:Route@18935,whole:Route@19619,whole:Route@20274,whole:Route@21273,whole:Route@21421,whole:Route@21934,whole:Route@22293,whole:Route@23229,whole:Route@23324,whole:Route@2494,whole:Route@3948,whole:Route@4207,whole:Route@4469,whole:Route@4474,whole:Route@4531,whole:Route@4537,whole:Route@4606,whole:Route@4614,whole:Route@5402,whole:Route@6437,whole:Route@7470,whole:Route@8351,whole:Route@9933 ebb22bf8c55e14ea92c0127ff116fca0f0e5df23938b82c6a77cac491451e156
+internal/activator/activator.go Service.doWake legacy accepted-legacy accepted-legacy;rev3=census-ruling-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): main's body (census ruling 292's legacy start mark and lease give-back; census ruling owed's pending record) with the seeded change ruled under census-ruling-seeded-instances (the refreshed row checked against the row the wake began from, twice; the seed's first upload recorded after the wake), and now one more line on a failed start: a start its pre-exec gate refused (launchNotStarted: the exec gate, the pending record or its recheck answered no before root was asked) is recorded as known not started (stopObservation.notStarted) instead of uncertain, only when the wake has started nothing else (wakeStartedNothing); otherwise it goes through cleanupFailedWake and its proofs. A refusal before its lease (step 1), which has started nothing, is recorded the same way. Read sites unchanged; no new read and no root decision registry:DBPath@1276,registry:DBPath@1292,registry:DBPath@1419,registry:DBPath@1430,registry:DBPath@21825,registry:Host@1105,registry:Host@1180,registry:Host@1258,registry:Host@1410,registry:Host@174,registry:Host@1744,registry:Host@2604,registry:Host@4579,registry:Replication@21860,registry:Seed@11964,registry:Socket@1305,registry:Socket@1321,registry:Socket@1444,registry:Socket@1455,whole:Route@10125,whole:Route@10815,whole:Route@1203,whole:Route@12065,whole:Route@12136,whole:Route@14520,whole:Route@159,whole:Route@16378,whole:Route@1639,whole:Route@1644,whole:Route@1698,whole:Route@17024,whole:Route@1704,whole:Route@1770,whole:Route@1778,whole:Route@18331,whole:Route@18485,whole:Route@18841,whole:Route@18935,whole:Route@19619,whole:Route@20274,whole:Route@21273,whole:Route@21421,whole:Route@21934,whole:Route@22293,whole:Route@23229,whole:Route@23324,whole:Route@2494,whole:Route@3948,whole:Route@4207,whole:Route@4469,whole:Route@4474,whole:Route@4531,whole:Route@4537,whole:Route@4606,whole:Route@4614,whole:Route@5402,whole:Route@6437,whole:Route@7470,whole:Route@8351,whole:Route@9933 ebb22bf8c55e14ea92c0127ff116fca0f0e5df23938b82c6a77cac491451e156
+@legacy 805b8d79fa8b7a4350536968730c899bbbcdb6788a0ce4ef76bcef2f13a163bb internal/activator/maintenance.go Service.cleanupFailedWake legacy accepted-legacy accepted-legacy;rev3=census-ruling-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): census ruling 292's body; when #292's whole proof holds for an aborted legacy start (legacyRunGone: the unit stopped and its cgroup empty) this wake's uncertainty is discharged and the stages it never reached (no sidecar, no replica) are recorded as held; when the proof fails the start is retained exactly as before, now named as the owner of this wake's uncertainty (owedBy). Read sites unchanged; the proof still only decides release-or-keep; no new read and no root decision registry:Host@1824,registry:Host@2337,registry:Host@3288,registry:Host@3628,registry:Host@4240,whole:Route@1691,whole:Route@1930,whole:Route@1934,whole:Route@2970,whole:Route@3653,whole:Route@3657 f59321652db8dc67a25ffabf3589b727b12b25d1136b2b18c48f97d6929d93b0
+internal/activator/maintenance.go Service.cleanupFailedWake legacy accepted-legacy accepted-legacy;rev3=census-ruling-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): census ruling 292's body; when #292's whole proof holds for an aborted legacy start (legacyRunGone: the unit stopped and its cgroup empty) this wake's uncertainty is discharged and the stages it never reached (no sidecar, no replica) are recorded as held; when the proof fails the start is retained exactly as before, now named as the owner of this wake's uncertainty (owedBy). Read sites unchanged; the proof still only decides release-or-keep; no new read and no root decision registry:Host@1824,registry:Host@2337,registry:Host@3288,registry:Host@3628,registry:Host@4240,whole:Route@1691,whole:Route@1930,whole:Route@1934,whole:Route@2970,whole:Route@3653,whole:Route@3657 f59321652db8dc67a25ffabf3589b727b12b25d1136b2b18c48f97d6929d93b0
+@legacy baa6de3e5faac90eac3fa5055386fddfc15e433cfb00f94089bdb69e0d27f263 internal/activator/opset.go Service.reapUnprovenWake legacy accepted-legacy accepted-legacy;rev3=census-ruling-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): census ruling 292's body; when the retried proof holds for a retained legacy start and the host's stop history is uncertain only because of that start (owedBy), the uncertainty is discharged and the stages the failed wake never reached are recorded as held; any other uncertainty is kept through rememberStopLocked as before. Read sites unchanged; no new read and no root decision whole:Route@439,whole:Route@493 7484890de454d363b9e191d28b1c70f8ea6339492169931c2f47e0dfb02db852
+internal/activator/opset.go Service.reapUnprovenWake legacy accepted-legacy accepted-legacy;rev3=census-ruling-292fix;basis=the agent (/opt/carlos/carlos agent, carlos-edge.service, user carlos): census ruling 292's body; when the retried proof holds for a retained legacy start and the host's stop history is uncertain only because of that start (owedBy), the uncertainty is discharged and the stages the failed wake never reached are recorded as held; any other uncertainty is kept through rememberStopLocked as before. Read sites unchanged; no new read and no root decision whole:Route@439,whole:Route@493 7484890de454d363b9e191d28b1c70f8ea6339492169931c2f47e0dfb02db852