spec: host lifecycle claim rev 6.32 (Plan 12: J2(b) recovery handover evidence, J5 fenced-target release, RunHeld and the M6 lease set, retired-location census, cross-region materialisation per §6.6 P6-D3)
Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+167
−4
docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md+167 −4
CI passed — run details
CI log
=== gate ===
cd launchctx && go vet ./... && go test ./... -count=1
ok amadan.net/carlos/platform.git/launchctx 0.013s
? 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 159.255s
ok github.com/carlosframework/platform/cmd/console 2.703s
ok github.com/carlosframework/platform/internal/activator 55.848s
ok github.com/carlosframework/platform/internal/agentsock 0.030s
ok github.com/carlosframework/platform/internal/analytics 0.022s
ok github.com/carlosframework/platform/internal/appconfig 1.049s
ok github.com/carlosframework/platform/internal/apperr 0.180s
ok github.com/carlosframework/platform/internal/applog 0.201s
ok github.com/carlosframework/platform/internal/awsinv 0.004s
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.046s
ok github.com/carlosframework/platform/internal/clamd 0.057s
? github.com/carlosframework/platform/internal/cliflag [no test files]
ok github.com/carlosframework/platform/internal/console 53.917s
? 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 1.472s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.025s
ok github.com/carlosframework/platform/internal/dbverify 0.005s
ok github.com/carlosframework/platform/internal/dnsrec 0.014s
ok github.com/carlosframework/platform/internal/docsite 0.005s
ok github.com/carlosframework/platform/internal/domains 1.183s
ok github.com/carlosframework/platform/internal/doorkey 0.009s
ok github.com/carlosframework/platform/internal/economics 0.005s
ok github.com/carlosframework/platform/internal/edge 23.595s
ok github.com/carlosframework/platform/internal/edgesfile 0.012s
ok github.com/carlosframework/platform/internal/fleetapi 0.209s
ok github.com/carlosframework/platform/internal/gate 1.754s
ok github.com/carlosframework/platform/internal/host 6.903s
ok github.com/carlosframework/platform/internal/infraview 0.006s
ok github.com/carlosframework/platform/internal/instancectl 0.419s
ok github.com/carlosframework/platform/internal/instanceexec 0.023s
ok github.com/carlosframework/platform/internal/instancefs 0.009s
ok github.com/carlosframework/platform/internal/lease 5.255s
ok github.com/carlosframework/platform/internal/ledger 0.027s
ok github.com/carlosframework/platform/internal/mail 0.075s
ok github.com/carlosframework/platform/internal/maintenance 0.188s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.614s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.057s
ok github.com/carlosframework/platform/internal/metrics 0.011s
ok github.com/carlosframework/platform/internal/objstore 0.134s
ok github.com/carlosframework/platform/internal/ops 0.141s
ok github.com/carlosframework/platform/internal/privatefile 0.018s
ok github.com/carlosframework/platform/internal/provision 0.908s
ok github.com/carlosframework/platform/internal/publicrelease 0.007s
ok github.com/carlosframework/platform/internal/registry 1.343s
ok github.com/carlosframework/platform/internal/relay 7.247s
ok github.com/carlosframework/platform/internal/release 3.695s
ok github.com/carlosframework/platform/internal/s3compat 0.007s
ok github.com/carlosframework/platform/internal/schedule 1.519s
ok github.com/carlosframework/platform/internal/status 0.129s
ok github.com/carlosframework/platform/internal/steering 0.010s
ok github.com/carlosframework/platform/internal/svcrole 0.022s
ok github.com/carlosframework/platform/internal/telemetry 0.006s
ok github.com/carlosframework/platform/internal/tunnelclient 0.014s
ok github.com/carlosframework/platform/internal/unixdial 0.013s
ok github.com/carlosframework/platform/internal/usage 0.013s
ok github.com/carlosframework/platform/internal/version 0.002s
ok github.com/carlosframework/platform/internal/vet 1.711s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
minio: cached, digest verified
mc: cached, digest verified
minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
buckets and scoped user ready
=== RUN TestDeliveredContractMinio
=== RUN TestDeliveredContractMinio/put
=== RUN TestDeliveredContractMinio/get_round-trips_the_body
=== RUN TestDeliveredContractMinio/list_shows_the_key
=== RUN TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN TestDeliveredContractMinio/delete_removes_it
=== RUN TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
--- PASS: TestDeliveredContractMinio/put (0.00s)
--- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
--- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
--- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
--- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok github.com/carlosframework/platform/internal/objstore 0.029s
minio contract: PASS
diff --git a/docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md b/docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md| index 71bf7d4e6..a862b6f5b 100644 |
| --- a/docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md |
| +++ b/docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md |
| @@ -1,8 +1,10 @@ |
| # The host lifecycle claim |
| +**Rev 6.32 (2026-10-10):** completes the claim interfaces for moves M2, M3, M5, M6, M7, M9, M12 and M21. Claim Plan 12 supplies loss-bound Group D admission, recovery predecessor evidence, atomic fenced-target release and restore retirement, held-lease parks, local park authority and retained retirement-location evidence. |
| + |
| **Rev 6.31 (2026-10-09):** records the accepted region HA contracts in amadan #236: `AutomaticLoss` in claim-level `evidence_refs`, the stop-only `relocation-release` obligation, and immutable signed `enrolled_at`. Section 6.2a binds moves' push, landing-install and rollback-return parks by name and region; R6 refuses automatic empty failover after any writer session. Claim Plan 11 supplies the integration. |
| -Design, 2026-10-09, **rev 6.31**. Joint rounds 6 and 7 judged the claim ready for primitive planning. Rev |
| +Design, 2026-10-10, **rev 6.32**. Joint rounds 6 and 7 judged the claim ready for primitive planning. Rev |
| 6.5 added the R6-4 rule. Rev 6.6 added writer epochs (R7-1, amadan #225). |
| Rev 6.7 answered the moves session's notes on §6.4a. Rev 6.8 rewrote §6.4a |
| around writer sessions, for joint round 8. Rev 6.10 answers joint round 10 |
| @@ -1605,6 +1607,60 @@ IAM: the box needs `s3:ListBucketVersions`, `s3:GetObjectVersion` and |
| `s3:DeleteObjectVersion` on its own data bucket. Today it has only |
| `Get`, `Put`, `Delete` and `List` (region-host main.tf:166, :174). |
| +#### Held location leases and the lease set (moves M5, M6) |
| + |
| +`park.Engine.RunHeld(ctx, work, held)` runs the same authorizer, version |
| +census, archive, delete and completion path as `Run`. Its configured |
| +`AggregateLeases` borrows only a still-valid lease whose acquisition-bound |
| +bucket and location equal the persisted scope exactly. The location includes |
| +the host. A lease for another host, bucket, incarnation or staging lane is |
| +refused before data effects. The engine may acquire other required locks, |
| +but never releases or closes the borrowed lease. The landing keeps renewing |
| +that lease through `published`, as moves section 7.6 requires. |
| + |
| +For `moves/<host>/<op>` scopes, the lease set contains the scope's own |
| +lock. Staging signatures are bounded by the persisted `evidence_at`: the |
| +seal, abort, revert or return-seal bound. The same own-lock rule applies |
| +to an eligible whole-host moves sweep. For `instances/<host>[/<inc>]`, |
| +the set contains the location's replica lease and the scope lock. These |
| +are the same location name and are deduplicated. Every name is relative |
| +to the engine's data bucket. Deployment-bucket staging leases are never |
| +passed as bucket-qualified names. `ReplicaParkLeaseSet` states this contract. |
| + |
| +#### Archived-incarnation census (moves M7) |
| + |
| +A bare legacy object key does not identify its incarnation. The existing |
| +`retired_incarnations` list proves retirement, but does not say which |
| +incarnation occupied `instances/<host>`. Neither its ordering nor a move's |
| +`LastMove` is sufficient to infer that mapping. |
| + |
| +Retirement retains `retired_locations` in its existing CAS only while |
| +`CARLOS_HOST_CLAIM_RETIRED_LOCATIONS=1`. Leave this write gate off until |
| +every strict claim reader has the rev 6.32 reader. Entries follow the |
| +32-entry history cap. Old claims remain readable; missing, trimmed or |
| +ambiguous bare-location history produces a typed dependency refusal. |
| +The holder's own bare location is labelled with its incarnation in other |
| +regions when the history excludes another occupant. Region-specific live-set |
| +protection still applies in the authoritative region. |
| + |
| +Qualified `instances/<host>/<incarnation>` paths identify themselves. Their |
| +cleanup uses the ordinary retirement authority and live-set checks; it never |
| +requires membership of the bounded `retired_incarnations` list. |
| + |
| +The claim census labels instance residue. Moves proves its own staging |
| +namespace. `View.ArchivedIncarnations` separates their labels, and |
| +`View.RefusedLocations` excludes only the ambiguous location. A refused |
| +moves proof drops that lane through `VerifyMovesSweep`, leaving proven |
| +instance candidates eligible. A bare-location refusal cannot relabel it |
| +with the move's incarnation or block a separately proved lane. |
| + |
| +For a free claim, no move remains live. The claim supplies the lanes' proof |
| +using its newest retained incarnation and the maximum nonzero `QuietAfter` |
| +bound; the bound must precede the version listing. Without that evidence, |
| +lanes remain refused while independently proven instance residue can proceed. |
| +The census establishes a conservative initial observation-time bound only |
| +after proving retirement. Every retry retains the scope's original bound. |
| + |
| ### 6.3 Local retirement (J-I-7, 199-13, 199-14) |
| Retirement covers both layouts. |
| @@ -2295,6 +2351,113 @@ cold successor has a new generation. A warm successor exists only after |
| the old session certified that every one of its requests had completed. |
| Dead generations are parked by the at-rest sweep (§6.2a). |
| +### 6.4b Moves recovery admission completion (J2(b), J5) |
| + |
| +The first `restoring/probing` admission under `loss.<id>` uses |
| +`AdmitWriterWithLoss`, including a loss before target admission. The frozen |
| +sources may name the source-cut predecessor or contain no target source. |
| +`hostops.MoveLossRecoveryOwner` supplies the existing loss owner's signed-intent |
| +policy. `LossAdmissionSources` validates each frozen baseline; the writer |
| +fills both primary and additional remote maxima from that one result. |
| +`ParseMoveLossBaseline` is the shared decoder of replica and seal baselines. |
| +The writer first compares Select's restore maximum with its non-nil restore |
| +plan. Frozen loss maxima are independent: target M can exceed restored source N. |
| +Only a frozen ordinary predecessor replaces `RemoteMaxTXID`; an empty frozen |
| +set preserves Select's validated maximum. AdmissionEvidence receives that loss |
| +maximum alongside the unchanged restore plan. Never infer zero from no sources. |
| + |
| +The admission CAS records `loss_admitted.<id>` with the admitted session. |
| +This operation-local marker survives admission-history trimming. A restarted |
| +probe and later source admissions use ordinary admission after consumption, |
| +retaining the original loss evidence. Every admission under this operation |
| +still refuses sealed or clean-return coverage. Omitting `Admission.LossID` |
| +cannot bypass first consumption. Claim-level historical references alone do |
| +not turn a later operation into loss recovery. Multiple current declarations |
| +remain refused pending a combination protocol. |
| + |
| +Only a `restoring` source whose nonempty predecessor writer is the move's |
| +target uses cross-box recovery handover. Initial source returns, including session zero, use the exact source cut, |
| +source stamp and canonical complete source report through the normal stop |
| +wiring. A missing report, mismatched cut or changed claim refuses. Later source |
| +sessions retain their own local writer-stop receipts. Recovery accepts any of these evidence forms: |
| + |
| +- The target's own `TargetReleased` exec-to-none CAS records |
| + `target_session` and `target_generation` from the holder when the target |
| + admitted. Recovery compares those journal values with the predecessor and |
| + checks the complete unsigned `stopped-target.json` report's role, host, |
| + operation, incarnation and box. This is the same claim authority as the |
| + source cut's `cut_session`; `claim_gen` is never a session. Keys come from |
| + `moves.StoppedKey(moves.ReportPrefix(...), "target")`. No moves stop adapter |
| + or new signature scheme is required. |
| +- A root-signed `StopReceipt`, where available, bound through |
| + `target_stop_receipt` and `target_stop_sha256`; verify its exact digest, |
| + enrolled box signature, installation stamp, session, operation, phase, |
| + region, enrollment and completed stop evidence. |
| +- This operation's committed, signed `loss.<id>` naming the exact target |
| + predecessor in its frozen sources. |
| +- A consumed fence whose journal marker, claim-level automatic loss identity, |
| + enrollment, target and holder incarnation agree. Consumption removes target |
| + execution and participation. Completion and handover do not revalidate the |
| + dead target or witness against a later roster. An unconsumed fence still |
| + requires fresh verification against the target enrollment and region. |
| + |
| +Before admission the claim checks the source executor and predecessor; |
| +afterward it checks the cold session and fresh stamp. The source never |
| +manufactures a local receipt for the target. Restore retirement is claim-owned |
| +for both restoring and reverting: the guarded local hook retires the source's |
| +old database and marker at `JournalSourceStamp`, using `src_gen` and the |
| +source cut evidence. Later source sessions retain the root writer-stop receipt |
| +requirement. After activation and before prelaunch, the source reads its exact original |
| +StopInventoryDB installation. Under the descriptor guard it rebinds the exact |
| +registry route and timers transactionally, then transfers the inventoried paths |
| +and ownership to the admitted stamp. Physical identities and old inventory are |
| +retained. Missing inventory refuses; a replay verifies the existing successor. |
| +Root's inventory-write transport still validates registry ownership. The |
| +registry call uses the existing isolated transport; root opens no SQLite DB. |
| + |
| +`ReleaseFencedTarget` both declares and consumes a verified `ReturnFence` in |
| +one CAS. It rolls back an uncommitted landing if needed, removes the target |
| +from execution and participants, reaches `released`, covers target-region |
| +predecessors and queues the target location, staging lane and any partial |
| +return lane as obligations. It records a release marker that `ReturnToSource` |
| +accepts without giving the source console authority. The reference never |
| +acknowledges those parks. Replays preserve scopes and do not requeue completed |
| +work. A fenced recovery cannot claim clean-return coverage. The same CAS appends |
| +Lost, LossRefs and AutomaticLoss outside the operation journal, retaining the |
| +original fence observation time, last target admission, regional chores, |
| +unknown timer knowledge, no local-discharge assertion and pending reporting. |
| +No quarantine revision is invented. These values survive operation and holder |
| +replacement. The console recovery pass materializes a signed loss record with |
| +an optional `automatic` authority payload before marking the retained evidence |
| +reported. The signed record also retains `alerts_pending:true`; materialization |
| +does not count as notification, and the delivery owner must consume this work. |
| +Retries preserve and compare the exact consequences. The optional |
| +payload is part of the reader rollout before this writer is enabled. |
| + |
| +Moves consumes `MoveRecoveryGrant.ReleaseFencedTarget` from the console's |
| +`Recovery.Moves` hook. Its first pass records a fence rollback decision when |
| +needed, preserving the first rollback reason. `LossRef` identifies that fence |
| +form. The next pass revokes the claim; the final pass completes the record from |
| +the consumed claim evidence. Each grant performs at most one mutation and |
| +rechecks record ETag and sequence before its claim CAS. New parks use the |
| +verified fence's original observation time, never a retry or rollback clock. |
| +If a target wrote a clean released record and died before releasing the claim, |
| +the grant verifies its canonical complete stopped-target report and revokes the |
| +claim directly, preserving the record and its original evidence. |
| +`MoveRecoveryGrant.StampLostMoveTargetParked` replaces the console's separate |
| +raw-record closure. It requires revoked target authority and no owed target or |
| +target-region chore. A target fenced before install needs no invented local |
| +park. Existing local parks remain owed until their own authorized completion. |
| +A dirty target still requires J4 declared loss. The unused abort-complete action |
| +is removed; abort owners retain their record-first close. |
| +`ClaimProviders.VerifyMoveTargetFence` exposes the box-side roster-backed |
| +verifier for callers that need a typed fence, without granting a write. |
| + |
| +`MoveParks` also returns the enrollment-bound local authorizer used by the |
| +claim's own `park.Local`. Moves uses it for local retirement, with its |
| +operation-specific checks in addition. The regional engine keeps its |
| +stronger `VerifyBound` wrapper. Neither authorizer substitutes for the other. |
| + |
| ### 6.5 Door reads pinned to versions (J-N10) |
| The pull door's listing: |
| @@ -2595,7 +2758,7 @@ Before binding a scope or issuing storage mutations, the adapter must: |
| 1. Validate its enrollment, effective region, bucket and retirement authority. |
| 2. Resolve immutable evidence references covering every producer that could write the scope. |
| -3. Acquire all required replica-location and staging-work leases in a deterministic order. |
| +3. Acquire the data-bucket lease set defined in section 6.2a in a deterministic order, or borrow the exact caller-held location lease through RunHeld. Staging writers are bounded by the persisted evidence_at. |
| 4. Re-read the claim and validate the exact obligation, original operation, phase and immutable scope. |
| 5. Obtain P6-D3’s view under that observation. |
| @@ -2618,8 +2781,8 @@ Completion uses the chore’s original operation identity and updates `quiet_aft |
| Rejected alternatives: |
| -- Holding only the replica lock: staging can still write into the retirement scope. |
| -- Replacing evidence time with `time.Now()`: it invents evidence. |
| +- Holding a scope lock without the staging signature bound: staging can still write into the retirement scope. |
| +- Replacing a persisted evidence time with a retry clock, or using the clock without retirement proof. The archived-incarnation census in section 6.2a first proves retirement, then establishes the initial conservative bound; retries retain it. |
| - Treating publication as proof that every old writer stopped: publication establishes coverage, not termination. |
| #### P6-D6. Cross-process denial and returning-box cleanup |