carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

  1. 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). Its required fields—schema, account, app and SetAt—all exist in AppExpiry; missing production becomes false (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.
  1. 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). 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). 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.
  1. 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). 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.
  1. P1 — §5: purge’s re-read does not revalidate the destruction deadline. Reconcile checks the age of its previously read tombstone (reconcile.go:228). PurgeReady accepts any current valid tombstone; age is deliberately excluded (trash.go:137, 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.
  1. 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). 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.
  1. 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). 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.
  1. 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), or instance creation, which accepts a requested kind (apiinstances.go:890); omitted kind means a process-backed instance (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.
  1. P2 — §6: the browser and direct-store restore paths retain an expired expiry. The browser calls release.RestoreApp directly (ownership.go:251); headless apps restore does likewise (apps.go:373). That helper only deletes the tombstone (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.
  1. 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). 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.
  1. P2 — §§2–3: account migration silently disables expiry. Migration skips production.json but copies every other relative key unchanged (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.

Verdict: revise.