carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

| Earlier finding | Disposition at 9a11decc | |---|---| | R1.1 — Signature accepted as production-off | Fixed: no signed expiry object remains. | | R1.2 — Existing data passes creation checks | Fixed at creation: fresh row and empty-prefix checks. A separate account-move bypass remains below. | | R1.3 — Keep/TTL races expiry | Not fixed: the specified conditional update still matches after keep; finding 1. | | R1.4 — Purge misses the current deadline | Declined-with-reason acceptable: §11 explicitly defers the pre-existing box race. The unchanged reconcile.go:228 remains vulnerable. | | R1.5 — Expiry replay after keep | Fixed: no bucket expiry authority remains. | | R1.6 — Restore bypasses cap | Not fixed for failed console restores: finding 4. The direct-mode exception is acceptable. | | R1.7 — Restrictions enforced only in CLI | Fixed for the reported paths: §4 requires server enforcement. | | R1.8 — Browser/headless restore misses renewal | Fixed in the specified shared restore and catch-up behavior. | | R1.9 — Interrupted purge loses short deadline | Fixed: the deadline survives in the row. | | R1.10 — Migration disables expiry | Declined-with-reason acceptable: §4 accurately describes direct migration and accepts its outcome. | | R2.1 — Rename brings permanent data under expiry | Fixed: temporary-app rename is refused. | | R2.2 — Early purge skips email teardown | Fixed for the reported paths: provisioning is refused and early purge checks for email resources. | | R2.3 — Failed restore cancels purge deadline | Fixed: the deadline remains until the row restore succeeds. | | R2.4 — Missing marker mistaken for restore | Fixed for the reported completed-purge case: an empty prefix finalizes deletion. | | R2.5 — Materialization omits deadline | Fixed: §5 explicitly reconstructs the marker-bound deadline. | | R2.6 — Instance provisioning bypasses store prohibition | Fixed: both manifest enablement and implicit provisioning are covered. | | R2.7 — Old console silently creates permanent app | Fixed in §3: distinct endpoint and response verification. §7 still contains the obsolete /new instruction and should agree with §3. | | R2.8 — Expiry bypasses control-plane protection | Not fixed: post-creation flag setting remains available, and the backstop covers purge only; finding 5. |

  1. P1 — §5: the expiry update accepts a kept, permanent app. The pass selects an expired candidate, then keep acquires the mutex and sets expires_at = 0. When the pass subsequently acquires it, the specified UPDATE … WHERE expires_at <= now AND deleted_at = 0 AND expiring = 0 still matches: zero satisfies <= now. Step 2 then calls TrashApp, which writes the marker without checking lifecycle state (trash.go:43), and step 3 assigns the three-day deadline. The mutex prevents simultaneous transitions but does not invalidate an earlier candidate listing. An in-memory SQLite check confirms the specified update affects one row after keep. Fix: require expires_at > 0 in the conditional update and revalidate eligibility under the mutex. Test keep between candidate selection and lock acquisition.
  1. P1 — §§3–4: moving the console record can put a permanent site under expiry. Create temporary A/site without deploying it. Suppose account B already has permanent site releases and a non-platform route, but no console app row. That is an eligible account-repair target: the review groups accessible same-named routes whose account has no app row (accountreview.go:65). The browser move calls MoveApp (appsettings.go:1350), whose update changes only account_id, preserving the proposed expiry fields (store.go:1705). B’s existing permanent releases now fall under automatic expiry and three-day purge. This is separate from the accepted bucket-copy migration outcome. Fix: require keep before moving a temporary row, enforce it in the shared move operation, and serialize the check with lifecycle transitions.
  1. P2 — §§5–6: recovered expiry leaves expiring = 1 across restore. Crash after writing the expiry tombstone but before completing step 3. Startup materialization can mark the row trashed (materialize.go:367); the proposed extension sets the purge fields but never clears expiring. Restore likewise clears only deleted_at, purge_after and purge_for. The existing row helper provides no additional transition handling (store.go:1469). The restored app therefore retains an outstanding instruction to retry expiry from step 2, allowing it to be trashed again despite its renewed deadline; keep and TTL also remain refused. Fix: settle expiring when materialization completes the trash transition, clear it atomically during restore, and ensure retry eligibility cannot survive a completed restore.
  1. P2 — §§3, 5–6: failed console restore frees a capacity slot before recovery. With 19 live temporary apps, restore a twentieth. Tombstone deletion succeeds but the final row update fails—a failure explicitly handled by the existing API (apicli.go:1338). The restored app serves again, but its row remains excluded from the cap. Create another temporary app, bringing the counted total to 20. Later, §5’s missing-marker catch-up restores the first row without a cap check, producing 21 live apps. No direct-mode operation or simultaneous create is required. Fix: reserve capacity durably before removing the marker, count incomplete restores against the cap, and retain that reservation through recovery. Merely adding a late refusal cannot undo the already-serving restore.
  1. P2 — §§3–5: an existing temporary app can still become the control plane. Removing control_plane from the create request does not close the existing owner-facing setter: handleAppControlPlaneSet calls SetAppControlPlane(..., true) on an existing app (controlplane.go:288). §4 does not restrict that path. §5 checks the flag only before early purge, so expiry still trashes an app that ordinary deletion explicitly protects (apicli.go:1209). Fix: require keep before setting the control-plane flag and recheck the flag before automatic trash as well as purge, under the lifecycle mutex.
  1. P3 — §7: the proposed listing assumes an API shape and command that do not exist. carlos apps currently has no list subcommand (apps.go:129). The existing app enumeration returns account entries containing app-name strings (apicli.go:548), and clients decode them as []string (account_choose.go:27). Its store query excludes trashed apps (store.go:1362), so it cannot supply the advertised purge countdown. Fix: specify the new list command and an additive metadata endpoint or response field, including trash visibility and service-principal filtering; preserve the existing string-array contract.

Verdict: revise.