| 1 | | Earlier finding | Disposition at `d1d9e6cf` | |
| 2 | |---|---| |
| 3 | | R1.1 — Expiry signature accepted as production-off | **Fixed:** no signed expiry object. | |
| 4 | | R1.2 — Existing permanent state passes creation checks | **Not fixed:** route-only apps still pass; new counterexample below. | |
| 5 | | R1.3 — Keep/TTL races expiry | **Fixed:** settle, lock, and recheck positive expiry. | |
| 6 | | R1.4 — Purge misses current deadline | **Declined-with-reason acceptable:** §11 explicitly defers the existing box race; console purge binds to the marker timestamp. | |
| 7 | | R1.5 — Expiry replay after keep | **Fixed:** no bucket expiry authority. | |
| 8 | | R1.6 — Restore bypasses cap | **Fixed:** console restores reserve capacity; direct-mode exception accepted. | |
| 9 | | R1.7 — Restrictions enforced only in CLI | **Fixed for reported paths:** server enforcement specified. | |
| 10 | | R1.8 — Browser/headless restore misses renewal | **Fixed:** shared restore and catch-up renewal. | |
| 11 | | R1.9 — Interrupted purge loses short deadline | **Fixed:** deadline survives in the row. | |
| 12 | | R1.10 — Migration disables expiry | **Declined-with-reason acceptable:** direct migration’s outcome explicitly accepted. | |
| 13 | | R2.1 — Rename brings permanent data under expiry | **Fixed:** shared rename operation refuses temporary apps. | |
| 14 | | R2.2 — Early purge skips email teardown | **Fixed for reported paths:** provisioning refusals and purge backstop. | |
| 15 | | R2.3 — Failed restore cancels purge deadline | **Fixed:** deadline retained until row restore succeeds. | |
| 16 | | R2.4 — Missing marker mistaken for restore | **Fixed for reported case:** empty prefix distinguishes completed purge. | |
| 17 | | R2.5 — Materialization omits deadline | **Fixed:** marker-bound deadline reconstruction specified. | |
| 18 | | R2.6 — Instance provisioning bypasses store prohibition | **Fixed:** manifest and provisioning paths covered. | |
| 19 | | R2.7 — Old console silently creates permanent app | **Fixed:** distinct endpoint and response verification. | |
| 20 | | R2.8 — Expiry bypasses control-plane protection | **Fixed:** creation/setter restrictions and pre-trash check. | |
| 21 | | R3.1 — Expiry condition matches kept app | **Fixed:** positive-expiry check under mutex. | |
| 22 | | R3.2 — Account move brings permanent data under expiry | **Fixed:** shared move operation refuses temporary apps. | |
| 23 | | R3.3 — `expiring` survives restore | **Fixed:** flag removed; settle-first recovery specified. | |
| 24 | | R3.4 — Failed restore frees capacity | **Fixed:** durable `restoring` reservation. | |
| 25 | | R3.5 — Temporary app becomes control plane | **Fixed:** setter restriction and expiry backstop. | |
| 26 | | R3.6 — Listing command/API shape nonexistent | **Fixed:** new command and additive endpoint specified. | |
| 27 | | R4.1 — Recovery bypasses empty-bucket safeguard | **Not fixed completely:** expiry writes can defeat the new safeguard; counterexample below. | |
| 28 | | R4.2 — Trash UI/API promises 30 days | **Fixed:** shared effective deadline and display tests specified. | |
| 29 | |
| 30 | 1. **P1 — §3: a permanent app represented only by routes can be claimed as temporary.** |
| 31 | |
| 32 | `carlos add` supports a named, account-qualified service backed by an existing TCP upstream, without requiring shipped releases or a console claim ([cli.go:198](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/cli.go:198)). Such an app can have empty qualified and legacy prefixes. |
| 33 | |
| 34 | The proposed “whole brand-new test” permits this state. `claimApp` checks console ownership and bucket prefixes ([server.go:3472](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/console/server.go:3472), [server.go:3517](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/console/server.go:3517)). Its apex check explicitly permits an existing route belonging to the same account/app ([apex.go:92](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/console/apex.go:92)). |
| 35 | |
| 36 | Create temporary `report` over that permanent service. When it expires, domains reconciliation deletes every matching account/app route, including the original permanent route ([reconcile.go:206](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/domains/reconcile.go:206)). This violates the brand-new-only ruling without concurrent writes. |
| 37 | |
| 38 | **Fix:** include existing account/app routes in temporary-creation eligibility, including the app’s own apex. Refuse when the required route inventory cannot be established. Test a serving service with no console row and empty bucket prefixes. |
| 39 | |
| 40 | 2. **P1 — §§2, 5: expiry can manufacture the evidence that defeats the empty-bucket safeguard.** |
| 41 | |
| 42 | Point the console at an incorrect empty bucket with temporary app A awaiting purge and temporary app B still marked live but already expired. Startup materialization preserves A and leaves B live ([materialize.go:311](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/console/materialize.go:311), [materialize.go:373](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/console/materialize.go:373)). |
| 43 | |
| 44 | §5 then expires B. `TrashApp` creates its marker without requiring existing app objects ([trash.go:43](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/release/trash.go:43)). On the following pass, `apps/` is nonempty solely because of that new marker. The §2 tripwire now passes, and A’s absent-marker/absent-objects recovery deletes its claim through `PurgeAppRow` ([store.go:1491](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/console/store.go:1491)). A’s real objects remain in the correct bucket without their claim or expiry state. |
| 45 | |
| 46 | **Fix:** when the deployment-wide listing trips the empty-bucket safeguard, suspend automatic expiry writes as well as absence-based finalization. Alternatively, verify bucket identity independently of app objects. Extend the test to mixed live-expired and awaiting-purge rows across multiple passes. |
| 47 | |
| 48 | Verdict: **revise**. |