carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

11. **P1 — §2: an expiry signature also validates as “production off.”** The production verifier checks the shared envelope’s signature, then decodes the payload without rejecting unknown fields ([production.go:160](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/release/production.go:160)). Its required fields—schema, account, app and `SetAt`—all exist in `AppExpiry`; missing `production` becomes `false` ([production.go:177](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/release/production.go:177)). Someone with bucket write access could copy an old expiry record onto `production.json` after keep→production, disabling that protection. **Fix:** cryptographically separate expiry signatures by purpose so even old production verifiers reject them. Adding a JSON purpose field alone is insufficient.
2
32. **P1 — §3: the five checks allow existing permanent data to become temporary.** An app with shipped releases but no promotion passes all five checks: `Ship` writes artifacts and a manifest without creating a channel pointer ([release.go:570](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/release/release.go:570)). Its existing releases would then become eligible for automatic destruction. The checks also omit other app objects and legacy state; the existing claim path explicitly checks qualified and, for ops, legacy prefixes ([server.go:3487](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/console/server.go:3487)). **Fix:** bind temporary status to a fresh app-creation reservation, refuse pre-existing app state, and coordinate that reservation with concurrent permanent creation and shipping.
4
53. **P1 — §§5–7: a successful keep or TTL extension can still be followed by expiry.** The housekeeper can read an expired record, then `keep` can observe no tombstone and delete the expiry, then the housekeeper can trash the now-permanent app. `TrashApp` conditionally creates only `deleted.json`; it does not check the expiry version ([trash.go:43](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/release/trash.go:43)). A stale read similarly defeats TTL extension or restore’s rewrite-first ordering. **Fix:** serialize these transitions through a shared per-app lifecycle mechanism with generation checks. Independent checks followed by writes do not close the race.
6
74. **P1 — §5: purge’s re-read does not revalidate the destruction deadline.** Reconcile checks the age of its previously read tombstone ([reconcile.go:228](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/domains/reconcile.go:228)). `PurgeReady` accepts any current valid tombstone; age is deliberately excluded ([trash.go:137](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/release/trash.go:137), [trash.go:176](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/release/trash.go:176)). Between those steps, restore→keep→manual-trash can replace an eligible temporary tombstone with a fresh permanent one, which is then purged immediately. **Fix:** give automatic purge a guarded operation that checks the current tombstone generation, current expiry classification and current deadline together. Preserve explicit manual purge as a separate operation.
8
95. **P1 — §§2, 7: deleting the expiry record does not revoke its signed authority.** After `keep`, replaying the formerly valid expiry at the same account/app path makes the permanent app temporary again, potentially with an already-past deadline. Path agreement and `SetAt` alone cannot distinguish that replay. The production implementation explicitly documents that signature verification needs separate replay protection ([production.go:11](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/release/production.go:11)). **Fix:** retain verifiable revocation or lifecycle state after keep, with a monotonic generation and protection against replay across app-name reuse. Deleting the only record of that transition is insufficient.
10
116. **P1 — §§3, 6: restore bypasses the live-app cap without any concurrency.** Create 20 temporary apps, trash them, create another 20, then restore the first batch: §6 allows 40 live temporary apps. The current restore path removes the marker and restores the row without a capacity check ([apicli.go:1333](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/console/apicli.go:1333)). This exceeds the explicitly accepted simultaneous-create overrun. **Fix:** apply the same live-temporary admission check before restoring a temporary app, across every restore entry point.
12
137. **P2 — §§4, 7–8: static-only restrictions are enforced only on the initial CLI invocation.** After temporary creation, a caller can use the ordinary ship API, which accepts binary releases ([apicli.go:1653](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/console/apicli.go:1653)), or instance creation, which accepts a requested kind ([apiinstances.go:890](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/console/apiinstances.go:890)); omitted kind means a process-backed instance ([provision.go:327](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/provision/provision.go:327)). The proposed expiry endpoint itself establishes no static shape. **Fix:** enforce temporary-app kind and host restrictions at the server’s release and provisioning mutation paths, including subsequent deployments, and require keep before widening them.
14
158. **P2 — §6: the browser and direct-store restore paths retain an expired expiry.** The browser calls `release.RestoreApp` directly ([ownership.go:251](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/console/ownership.go:251)); headless `apps restore` does likewise ([apps.go:373](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/apps.go:373)). That helper only deletes the tombstone ([trash.go:98](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/release/trash.go:98)). Updating only the console CLI handler leaves these restores immediately eligible for expiry again. **Fix:** specify and share the expiry-aware restore operation across all three paths. A caller lacking signing authority must obtain renewal through the console or refuse before removing the marker.
16
179. **P2 — §5: an interrupted purge can postpone cleanup from day 3 to day 30.** `PurgeApp` deletes every object except the tombstone during its main sweep ([trash.go:252](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/internal/release/trash.go:252)). If it deletes `expiry.json` and a later deletion fails, the next pass sees an absent expiry and applies 30 days. **Fix:** preserve the verified expiry or durable purge-start authorization until the payload sweep finishes. Test failure immediately after the point where expiry would otherwise be removed.
18
1910. **P2 — §§2–3: account migration silently disables expiry.** Migration skips `production.json` but copies every other relative key unchanged ([accounts.go:499](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/temporary-sites/accounts.go:499)). It would therefore copy `expiry.json` with the source account still signed into its body. Destination verification fails, so the migrated app no longer expires and is excluded from the valid-record cap. **Fix:** refuse migration of temporary apps until keep, or explicitly re-sign their lifecycle state at the destination while preserving the deadline and enforcing destination capacity.
20
21Verdict: revise.