spec: host lifecycle claim rev 6.18, PROPOSED §5.9 console operation contracts
Admission format and ownership, deployment inventory and executors, durable evidence for regional workers, the purge journal, placement, and create/purge effect recovery (Plan 5's P5-D1..D6), with the effect of #232 noted on each. Awaiting Paul (amadan #236).
Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+404
−2
docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md+404 −2
CI passed — run details
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 935deae16..0257b78bb 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,6 +1,6 @@ |
| # The host lifecycle claim |
| -Design, 2026-10-06, **rev 6.17**. Joint rounds 6 and 7 judged the claim ready for primitive planning. Rev |
| +Design, 2026-10-06, **rev 6.18**. 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 |
| @@ -12,7 +12,8 @@ rule set, and the readings sections are history. Rev 6.15 adds the |
| persisted park scope and the holderless sweep (§2, §6.2a), found while |
| planning the park engine. Rev 6.16 states what a box reference is |
| (amadan #212). Rev 6.17 adds the PROPOSED contracts §7.3 and §10.1 |
| -(Plan 8's D1 to D7), awaiting Paul's rulings P1 to P3 and joint review. Rev 6.9 answered |
| +(Plan 8's D1 to D7), awaiting Paul's rulings P1 to P3 and joint review. Rev 6.18 adds the |
| +PROPOSED §5.9 (Plan 5's P5-D1 to P5-D6). Rev 6.9 answered |
| joint round 9 |
| (R9-1 to R9-4) and takes **Paul's ruling R6 (2026-10-04): a box never |
| reuses its local database. Every writer start restores from the |
| @@ -593,6 +594,407 @@ is an operation, `placing`. Its CAS from `serving` comes **before** any |
| app, registry or record write, with the target in `intent`. The pass |
| replays it. Moves and placements therefore exclude each other. |
| +### 5.9 Console operation contracts (rev 6.18, PROPOSED) |
| + |
| +**Status: proposed, not yet ruled.** These close the gaps Plan 5's draft found (P5-D1 to P5-D6). Each says where it would land in §2, §5, §6, §8 and §9 once ruled, and how amadan #232 (host ownership; Paul decides its direction) would change it. Paul's decisions are listed in amadan #236. |
| + |
| +The text below depends on the proposed §7.3 and §10.1 contracts. It does not treat them as approved. The referenced #212 design is also unsettled: its current header recommends an alternative and records unresolved review findings. |
| + |
| +All additions are additive. IDs are random 128-bit values encoded as 32 lowercase hexadecimal characters. Timestamps use UTC RFC3339Nano. Structured values stored in `op.intent` or `op.journal` are JSON-encoded strings, preserving their existing `map[string]string` representation. |
| + |
| +#### P5-D1. Admission format and ownership |
| + |
| +*Insert in §5.1 step 0; reference from §5.3 and §5.4.* |
| + |
| +`apps/<acct>/<app>/admission.json` is a persistent, conditionally replaced object: |
| + |
| +```json |
| +{ |
| + "v": 1, |
| + "app_id": "<immutable app incarnation>", |
| + "revision": 1, |
| + "epoch": 1, |
| + "state": "open", |
| + "op": null, |
| + "registrations": {}, |
| + "effects": {}, |
| + "last_purge": null |
| +} |
| +``` |
| + |
| +`revision` increases on every write. `epoch` increases whenever admission reopens. Closing admission preserves its epoch so existing operations remain identifiable. |
| + |
| +The console persists `app_id` in its app row and this object. Initial enrollment and app creation reserve the row while establishing that binding; mutation admission remains unavailable until both agree. An absent or mismatched admission object is not permission to initialize a live app opportunistically. |
| + |
| +A create registration, keyed by its operation ID, contains: |
| + |
| +```json |
| +{ |
| + "kind": "create", |
| + "host": "<host>", |
| + "owner": { |
| + "console": "<D7 console ID>", |
| + "enrollment": "<enrollment ID>", |
| + "controller": "<unique request controller ID>" |
| + }, |
| + "app_id": "<app incarnation>", |
| + "epoch": 1, |
| + "claim": {"absent": false, "etag": "<saved ETag>", "gen": 41}, |
| + "expires": "<timestamp>", |
| + "created_at": "<server-assigned timestamp>", |
| + "incarnation": "<derived incarnation>" |
| +} |
| +``` |
| + |
| +For an absent claim, `absent` is true and `etag` and `gen` are omitted. The console registers first, then assigns and conditionally records `created_at` and `incarnation`, before attempting the claim CAS. |
| + |
| +A registration lasts **90 seconds**, renewed every **20 seconds**. The owner stops issuing work five seconds before its acknowledged expiry. Draining treats it as expired only five seconds after expiry. These rules require clock skew bounded by five seconds; inability to satisfy that bound refuses admission. |
| + |
| +Only the exact owner tuple may renew. Renewal cannot recreate a missing entry, revive an expired controller, change its incarnation, or cross an app ID or epoch. An ambiguous renewal does not extend the controller’s acknowledged deadline. Another console recovers the claim; it does not impersonate the registration owner. |
| + |
| +The ordering remains: |
| + |
| +1. Read and retain the claim observation. |
| +2. Register while admission is open and no purge reservation exists. |
| +3. Bind the server-assigned incarnation. |
| +4. Re-read admission as the final check. |
| +5. Attempt the claim CAS **once**, against the original observation. |
| + |
| +A conflict restarts the entire sequence with a new operation ID, registration and creation time. Accepted operations may finish while admission is closed. Their existing registrations may renew, but no new registration is admitted. |
| + |
| +Recovery removes a completed registration only after its claim and effect journal establish completion or cancellation. Expiry alone never removes a registration whose claim still names its operation. Before discarding an expired, unaccepted start, recovery invalidates its saved claim revision, or records evidence that it has already changed. Purge uses P5-D4’s fence procedure. |
| + |
| +Other host-operation starts use the same registration mechanism with their own `kind`, saved claim observation and existing incarnation. This lets placement and purge drain delayed move or delete starts as well as creates. |
| + |
| +Admission is **never deleted**. Recreation requires the old purge to be complete, its journal absent, and its effects drained. The new app reserves a new `app_id`, then conditionally reopens admission with a higher epoch. Old renewals and removals consequently cannot affect it. |
| + |
| +Rejected alternatives: |
| + |
| +- Five-minute registration TTL: the recovery-age threshold does not define a lease. |
| +- Taking over an expired registration: recovery must not revive its delayed claim attempt. |
| +- Deleting admission on purge: this reintroduces an absence/recreation race. |
| + |
| +**Effect of #232:** No change to this mechanism. Its app-name reservations must carry the same `app_id`; alias acquisition does not replace app admission. |
| + |
| +--- |
| + |
| +#### P5-D2. Inventory and executor selection |
| + |
| +*Insert in §2, replace “the record region’s box” in §5.1 step 4, and extend §9.* |
| + |
| +Plan 8 D7’s `control/host-actors.json` is the sole membership authority. Do not introduce another box roster. |
| + |
| +Extend that directory with the information needed to evaluate provisioning scope: |
| + |
| +- Box enrollment: served pools, placement namespace, inbound posture, and regional data-bucket access. |
| +- `regions`: a map from placement code to its data-bucket identity and retirement state. |
| + |
| +A region remains listed while it may contain retirement residue, even if no active box remains there. Losing its last worker leaves chores pending; it does not remove the region from the census. |
| + |
| +Use the provisioning scope predicates, including pool defaults, fleet placement, sidecar inbound restrictions and everywhere-class behavior. Status freshness determines readiness, not membership. Offline enrolled boxes remain in scope under D7. |
| + |
| +A legacy census consists of: |
| + |
| +1. Complete, paginated listings and validated reads of every account’s instance and sidecar records, including apps absent from console SQL. |
| +2. A complete registry and local-installation report from each required enrolled box. |
| +3. Fresh claim and domain-claim reads for the host being adopted. |
| + |
| +Reports are immutable objects at: |
| + |
| +```text |
| +control/host-census/<reservation-id>/<box-reference>.json |
| +``` |
| + |
| +They contain `box`, `enrollment`, `boot_id`, `reservation`, `directory_revision`, `captured_at`, `complete`, and the reported routes/installations with their host, owner, incarnation and installation stamp where available. The reporting box produces them under the relevant admission fence and guards. Heartbeats and edges files may identify discrepancies but cannot certify an empty or complete inventory. |
| + |
| +A missing report, unknown actor, duplicate record, ambiguous identity, unexplained route, partial listing or undecodable body refuses adoption or the affected operation. |
| + |
| +For a **new, single-executor host**, evaluate the complete eligible set. Choose the greatest hexadecimal SHA-256 of: |
| + |
| +```text |
| +host + "\x00" + box-reference |
| +``` |
| + |
| +Break a tie by the smallest box reference. Persist the selected box and enrollment in the create intent. Selection happens once; recovery never substitutes a newly available box. The selected participant must satisfy D7 and the first-phase acknowledgement barrier before completion. |
| + |
| +For **legacy adoption**, selection is not scheduling. Require evidence identifying exactly one current installation owner. A hibernating installation still has an owner. Zero or multiple provable owners refuse adoption pending repair. |
| + |
| +`op.exec` remains singular. Records whose intended behavior requires several simultaneous serving boxes, including applicable everywhere/static shapes and nonempty `Record.Replicas`, cannot be silently reduced to one executor. Independent replica records with distinct hosts may each have one executor. A route-only installation with no representable holder is unsupported; do not fabricate a provisioning record during adoption. |
| + |
| +Use Plan 8’s enrollment-qualified fleet references. A fleet label alone is insufficient deployment-wide identity. |
| + |
| +Rejected alternatives: |
| + |
| +- First live heartbeat or console-local box: neither establishes ownership nor completeness. |
| +- SQL-listed apps only: this misses direct-bucket records. |
| +- Selecting one box from a multi-serving record: this changes the workload’s meaning. |
| + |
| +**Effect of #232:** Yes. Its adoption direction replaces §9’s lazy entry points with the frozen, deployment-wide backfill. The census above remains necessary, but only the backfill may adopt. Doors and recovery refuse unclaimed legacy records, and orphan cleanup honors `control/hosts-quarantine.json`. |
| + |
| +**PAUL:** Accept #232’s backfill-only adoption and temporary create freeze, or retain lazy adoption with the complete census above. Recommended default: accept backfill-only adoption. Neither option permits choosing a winner among conflicting legacy records automatically. |
| + |
| +--- |
| + |
| +#### P5-D3. Evidence transport and regional workers |
| + |
| +*Insert after §6.1; extend §2’s obligation fields and §6.2a.* |
| + |
| +The claim’s obligations are the work queue. The console queues work and validates completion; it does not acquire another region’s data credentials. |
| + |
| +A box persists stop or empty-inventory evidence locally before publishing an immutable receipt: |
| + |
| +```text |
| +control/hosts/<host>/evidence/<operation-id>/<box-reference>/<receipt-id>.json |
| +``` |
| + |
| +The receipt contains: |
| + |
| +```text |
| +v, id |
| +host, incarnation, installation_stamp, writer_session |
| +operation_id, obligation_id |
| +box, enrollment, boot_id |
| +claim_gen, directory_revision |
| +kind: stopped | empty |
| +evidence_at |
| +fence_id, inventory_sha256, local_journal_sha256 |
| +``` |
| + |
| +`stopped` certifies every §6.1 condition. `empty` certifies a complete guarded scan of the relevant registry, installation records, configured artifact roots, retirement journals, processes and open file holders. Missing claim inventory or an absent cgroup is insufficient. |
| + |
| +The box retains the corresponding durable admission fence. It may not reopen the evidenced installation merely because the operation’s worker restarted. |
| + |
| +Each obligation gains `evidence_refs`, containing immutable receipt keys and digests, or the accepted Plan 8 loss-record reference. These references survive operation replacement. Discovery chores retain their discovery operation ID. |
| + |
| +A remote park scope is bound only after evidence covers **every box that could still write that location**. Its `evidence_at` is the latest original evidence time among those sources. A lost-box source uses the declaration’s persisted `fenced_at`. Retries never replace either with the current time. |
| + |
| +For legacy discovery, the non-serving box establishes stop or empty evidence under cleanup-only authority. The current executor’s existence elsewhere does not prove that box empty. An unavailable box requires the existing lost-box procedure or remains pending. |
| + |
| +The deployment’s signed actor directory also records: |
| + |
| +```text |
| +request_limit_ns: 120000000000 |
| +``` |
| + |
| +This is a **120-second total client deadline per storage request**, including retries. Every replication, staging and lifecycle storage client must enforce it before activation. A missing or unenforced bound refuses remote retirement. The bound is not a guarantee about when an already accepted server request completes; C3a and the at-rest sweep continue covering later completions. |
| + |
| +The regional adapter shall: |
| + |
| +1. Verify its own enrollment, region and access to the exact bucket. |
| +2. Acquire and renew all required replica and staging-work leases. |
| +3. Freshly validate the claim, obligation, phase, immutable scope and evidence. |
| +4. Obtain the moves-owned live-set view bound to that claim observation. |
| +5. Execute Plan 4’s exact-version park. |
| +6. Acknowledge with the chore’s own operation ID, updating `quiet_after` in the same CAS. |
| + |
| +Regional ownership remains `region:<r>`. Any eligible enrolled worker with access to that region may perform the chore under the aggregate leases. Local chores remain bound to the original machine enrollment. |
| + |
| +Receipt publication uses the existing trusted box-writer boundary. A console-generated receipt cannot substitute for another machine’s evidence. Missing transport authority leaves work unsupported; it does not justify broader IAM. |
| + |
| +Rejected alternatives: |
| + |
| +- Lease expiry as stop evidence: a paused process can resume. |
| +- Empty heartbeat or missing inventory entry as empty disk: neither is a complete scan. |
| +- Recomputing evidence time on retry: this loses the original retirement horizon. |
| + |
| +**Effect of #232:** No change to parking or evidence. Backfill becomes the source of adoption discovery chores. |
| + |
| +--- |
| + |
| +#### P5-D4. Purge journal, fences and completion |
| + |
| +*Replace §5.3’s journal and cleanup details; extend §8.* |
| + |
| +`apps/<acct>/<app>/purge.json` remains the create-only reservation and recovery journal. Its body contains: |
| + |
| +```text |
| +v, id, app_id, account, app |
| +phase: reserved | adopted | claimed | torn-down | retired | done |
| +requested_by, requested_at, reason |
| +tombstone: {key, etag, body} |
| +admission_epoch |
| +hosts: map[host]{ |
| + registrations, |
| + holder: {family, key, incarnation}, |
| + record: {etag, version_id}, |
| + fence: {state, claim_gen}, |
| + progress |
| +} |
| +effects: map[effect-id]effect journal |
| +cleanup: {sweep, tombstone, app_row, fences, admission} |
| +``` |
| + |
| +`reason` distinguishes authorized immediate purge from automatic expiry. Automatic purge retains the existing **30-day** cooloff. The console validates the tombstone, production and control-plane protections, app identity and backend availability before destructive work. |
| + |
| +Create the journal first. Then acquire the admission object’s exclusive purge operation and close admission. An existing app operation must finish or safely cancel before purge acquires that gate. Revalidate the captured tombstone and app identity after acquiring it. A reserved journal alone grants no teardown authority. |
| + |
| +**Draining registrations.** Persist each registration and its host in the purge journal **before** removing it from admission. |
| + |
| +- If the claim names that create, finish or cancel it through create recovery, then include its resulting holder or cancellation. |
| +- If the claim is free or absent, write the CC21 holderless purge fence, record its result, then remove the registration. |
| +- If another holder or operation occupies the claim, never overwrite it. Compare with the registration’s saved claim observation. A changed revision proves the delayed start cannot succeed. Persist that observation as the fence witness, then remove the expired registration. |
| +- If the saved revision remains usable, invalidate it conditionally before draining. Unreadable or contradictory evidence leaves the registration pending. |
| + |
| +A foreign-holder witness does not authorize retiring that holder. |
| + |
| +The fixed host set is the union of drained registrations, validated records and the complete ownership census. Persist it before advancing to `adopted`. Apply the selected §9 adoption policy. |
| + |
| +Reserve every matching holder under the purge ID before `claimed`. Unrelated operations remain conflicts. Already acquired reservations stay held. Before backend teardown, obtain the required participant acknowledgements and stop or lost-box evidence. |
| + |
| +Teardown uses P5-D6’s effect journal. Only completed teardown permits `torn-down`. Record retirement then follows the existing purge-owned `purging → parking → free` sequence. Per-host progress records the exact incarnation and operation. If that host later belongs to another app, replay considers the completed retirement complete and leaves the successor alone. |
| + |
| +**Completion order:** |
| + |
| +1. Reach `retired` only after all host retirement obligations and auxiliary effects finish. |
| +2. Sweep the remaining app prefix, excluding `admission.json`, `purge.json` and the captured trash marker. Provisioning records must already have passed through retirement. |
| +3. Use the existing versioned-storage machinery to delete exact recorded versions during the generic sweep. Persist each batch before deletion. This protects identical-content successor objects from delayed requests. Listing failure blocks completion. |
| +4. Verify the remaining prefix contains only the excluded control objects. |
| +5. Delete the captured trash marker conditionally. |
| +6. Remove console rows and related state conditionally on the old `app_id`. |
| +7. Persist `phase:"done"`. |
| +8. Release only this purge’s holderless fences by writing free claims. |
| +9. Leave admission closed, record `last_purge:{id,app_id}`, mark the old app incarnation retired, and clear only this purge’s admission operation. |
| +10. Delete the purge journal conditionally, last. |
| + |
| +App-name reuse remains blocked until step 10. The persistent admission object is an explicit exception to the old “any app-prefix object reserves the name” test. |
| + |
| +Each step is replayable from its checkpoint. A missing tombstone after its successful deletion does not turn recovery into `ErrNotTrashed`. A retry with a completed purge ID is completion, never authorization to purge a recreated app. Stale workers cannot restart a prefix sweep after `done`. |
| + |
| +Rejected alternatives: |
| + |
| +- Letting `PurgeApp` sweep its journal: this destroys recovery authority. |
| +- Releasing the app reservation when one host becomes free: other hosts and backend effects may remain. |
| +- Overwriting a foreign holder to fence a registration: the changed claim revision already supplies the necessary rejection. |
| + |
| +**Effect of #232:** Yes. Purge must also retire the app’s acquisition-bound `domains/` claims before releasing its app-name reservation. Materialization must use this completion protocol instead of independently deleting the SQL row. Its backfill/quarantine rules replace lazy adoption. |
| + |
| +--- |
| + |
| +#### P5-D5. Placement completion and app serialization |
| + |
| +*Replace §5.4; add the corresponding §8 recovery entry.* |
| + |
| +Placement uses the admission object’s exclusive `op` as its app-wide journal: |
| + |
| +```text |
| +id, kind: placing, app_id |
| +phase: draining | claimed | stopped | prepared | committed | releasing | done |
| +source_placement, target_placement |
| +hosts: exact holders, record observations, source/target enrollments |
| +routes: owning enrollment and captured route identity |
| +per_host_progress |
| +``` |
| + |
| +Acquiring this operation closes admission before any app placement, route or record write. Drain registered starts using P5-D1. An expired start whose saved claim revision remains usable must have that revision invalidated before removal. |
| + |
| +After draining, obtain a complete record and route census. Existing moves, creates, deletes or unresolved discovery chores prevent placement from advancing. The app gate prevents new ones from starting. |
| + |
| +**Initial supported scope:** |
| + |
| +- An empty app, proved to have no records, independent routes or local installation state. |
| +- A metadata change that preserves every host’s exact executor and installation. |
| +- A single-executor static installation whose entire target state can be regenerated from held release/configuration inputs, with no database, writer admission, timers or other state requiring transfer. |
| + |
| +Stateful executor changes, sidecar transfers, multi-serving records, nonempty `Record.Replicas`, and unexplained route-only installations are refused before placement effects. `Role:"replica"` does not grant a placement exception. |
| + |
| +For supported hosts: |
| + |
| +1. CAS every claim from `serving` to `placing`, recording the app operation ID, complete reconstruction inputs and exact source/target enrollments. Preserve the source executor and stamp. |
| +2. Cross the participant acknowledgement barrier. No spawn or writer admission is permitted while `placing`. |
| +3. For executor changes, obtain source stop evidence. A CAS then sets `exec:"none"` while preserving the source stamp. No data-copy shortcut is permitted. |
| +4. Prepare the target’s held release and configuration in operation-scoped staging. The target publishes a durable preparation receipt. It does not activate or spawn. |
| +5. After every host is prepared, update app placement conditionally on `app_id` and operation ID. Update owner-box routes, then records, preserving the existing routes-before-records ordering. Record every acknowledgement and resulting record ETag. |
| +6. For each changed installation, CAS to `serving` with the selected target executor and `holder.stamp` equal to that CAS’s resulting generation. The target activates that stamp under the guard. A metadata-only completion preserves both executor and stamp. |
| +7. Retire the stopped source installation through local retirement chores. Preserve unrelated obligations. |
| +8. Reopen app admission only after every host, projection and required cleanup is complete. |
| + |
| +Before any source is stopped or projection changed, cancellation may release acquired claims while preserving their original executors and stamps. After that boundary, recovery rolls forward. It does not guess a rollback installation or clear a partially completed app operation. |
| + |
| +Empty-app placement performs the same gate and census, changes only the app placement, then reopens admission. |
| + |
| +Rejected alternatives: |
| + |
| +- Writing app placement before acquiring claims: concurrent creates can inherit a partially applied target. |
| +- Changing only record regions: this transfers neither execution authority nor data. |
| +- Reusing the move engine for every placement: its current contract explicitly refuses fleets and several affected workload shapes. |
| + |
| +**Effect of #232:** Record-backed placement is unchanged because ownership does not transfer. Minted, route-only hosts remain app-name-owned under #232; that does not supply a lifecycle transfer protocol for them. |
| + |
| +**PAUL:** Accept this limited flagged placement scope, or require general fleet/stateful placement before activation. Recommended default: accept the limited scope and specify stateful fleet transfer separately. Unsupported placement must refuse the whole request, not partly update the app. |
| + |
| +--- |
| + |
| +#### P5-D6. Create effects, rollback and backend teardown |
| + |
| +*Extend §5.1, §5.2, §5.3 and §8.* |
| + |
| +A create’s initial claim intent includes: |
| + |
| +```text |
| +app_id, admission_epoch |
| +record_key, complete_record_body |
| +selected_executor, executor_enrollment |
| +private_store: environment, declaration, backend coordinates, grant authority |
| +first_route: domain key, acquisition ID, intended marker body |
| +effects: ordered reconstruction inputs |
| +``` |
| + |
| +The claim CAS precedes private-store provisioning, domain-marker writes and record creation. Private storage still completes **before** the record is written. No process or writer is admitted while `creating`. |
| + |
| +Each external effect first receives a durable entry in admission’s `effects` map: |
| + |
| +```text |
| +id, operation_id, app_id |
| +resource, action, inputs |
| +owner: console, enrollment, controller |
| +state: prepared | issued | complete | uncertain |
| +result_reference |
| +``` |
| + |
| +Effects on the same backend resource serialize through this map. Store grant and teardown reuse the existing grant/provisioned records and idempotent walks; the effect entry supplies operation ownership and recovery binding. |
| + |
| +Effect ownership does **not** expire with the create registration. A timed-out backend call remains uncertain. Recovery may not overlap it with teardown, release its resource name, or admit a replacement app merely because its controller stopped reporting. |
| + |
| +A controller completes an issued effect only after joining its request and accounting for its result. Another controller may take over only after the old issuer cannot issue further calls and outstanding calls have been reconciled. For an unavailable console, this requires verified process/compute fencing and backend reconciliation; heartbeat expiry is insufficient. If the backend cannot establish completion, the effect remains pending. |
| + |
| +Every new backend call requires a fresh durable effect admission. A stale handler cannot reuse a completed effect entry as authority for another call. |
| + |
| +**Create recovery and rollback.** |
| + |
| +- Five minutes makes a creating claim eligible for inspection, not unconditional cancellation. |
| +- A matching record permits completion only when required pre-serving effects and validations are durably complete. |
| +- An ambiguous record PUT is resolved by reading and validating the exact incarnation. A foreign incarnation is never rollback material. |
| +- Rollback first CASes the current create to `phase:"cancelling"`. It denies completion and records the exact cleanup inputs. |
| +- Cleanup conditionally removes only this attempt’s record and auxiliary objects. An absent record may still arrive late; the existing cancelled-create and orphan rules handle it. |
| +- Free is permitted only after required effect settlement and cleanup. Preserve retirement history and cancelled-create hints. |
| +- Once serving, rollback uses ordinary delete and park. It cannot call `CancelCreate`. |
| + |
| +A failed create leaves an already provisioned private store available under the existing store policy. It does not automatically destroy tenant data. |
| + |
| +**Auxiliary objects.** New sidecar wake objects carry `app_id`, `incarnation` and a unique request ID. Readers reject a wake for another incarnation. Retirement journals the observed wake ETag and uses conditional deletion. A changed object is revalidated, never blindly deleted. Legacy wake objects are removed only while the old holder is fenced. |
| + |
| +First-route markers carry an acquisition ID and record incarnation. Detach retains the marker until the matching record retirement and required cleanup complete. A marker-read error refuses the operation. Same account/app ownership alone is insufficient to identify rollback material. |
| + |
| +**Purge teardown.** Before teardown, the journal retains exact store, scanner, email principal, domain-claim and delivered-configuration identities. It includes incomplete grants and orphaned claims, not only completed provisioning records. Shared email-domain changes remain conditional on the shared domain’s membership and use resource-level serialization, not merely the purged app’s gate. |
| + |
| +Auto-purge boxes submit the same durable purge request with the observed tombstone identity. The console independently validates eligibility and drives backend teardown. Boxes do not receive new backend authority and never fall back to the raw prefix sweep. |
| + |
| +Restore, trash, protection changes, rename, account transfer and independent backend teardown participate in the same app gate. None may reopen or rename an app under purge. Rename or account transfer of a record-backed app remains refused until a protocol transfers its holders, admission identity and auxiliary ownership together. |
| + |
| +All eight retirement paths in Plan 5 use the configured coordinator: sibling-apex rollback, instance delete, sidecar delete, API detach, browser detach, attach rollback, owner purge and box auto-purge. The generic purge sweep cannot bypass it. |
| + |
| +**Observable outcome.** A durably accepted but unfinished operation returns HTTP **202**, its operation ID and pending phase. Existing app/instance/sidecar views continue exposing that operation after record removal while parking remains pending. A completed delete response is emitted only after required retirement and cleanup finish. An ambiguous request that has not established durable acceptance is an error, not a successful deletion. Implementation reuses accurate existing wording and reviews any new display strings. |
| + |
| +Rejected alternatives: |
| + |
| +- Best-effort rollback without a journal: an ambiguous write can outlive the request. |
| +- Unconditional wake or domain cleanup: this can delete a same-key successor. |
| +- Expiring backend-effect ownership: a paused issuer can resume after teardown or name reuse. |
| + |
| +**Effect of #232:** Yes. Reuse its `acq` identity, pending/committed marker states, incarnation binding and conditional detach. Its proposed first-route ordering must be amended so lifecycle intent exists before this create writes the marker. Add its cross-family verification before record creation. Reuse its app-name operation journal rather than creating a competing rename journal. |
| + |
| +**PAUL:** The record-backed rename/account-transfer refusal is the same migration-scope decision already raised by #232. Recommended default: retain the refusal until the transfer protocol exists. |
| + |
| ## 6. Mechanisms |
| ### 6.1 Stop evidence (J-D3) |