carlos / platform Public

plan: Task 6/10 rulings from Task 5-6 reviews (grant-block guard, audit nil-safety)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 7886ba6114ef5aa52f8a180ba90bed516b5bea32 parent f375fce
1 file changed, +15 −2
  • docs/superpowers/plans/2026-08-10-object-storage-primitive.md +15 −2
diff --git a/docs/superpowers/plans/2026-08-10-object-storage-primitive.md b/docs/superpowers/plans/2026-08-10-object-storage-primitive.md
index ea810a912..fdc98cdf8 100644
--- a/docs/superpowers/plans/2026-08-10-object-storage-primitive.md
+++ b/docs/superpowers/plans/2026-08-10-object-storage-primitive.md
@@ -549,6 +549,7 @@ func Grant(ctx context.Context, s release.Store, p Provisioner, d Deployment,
**Files:**
- Modify: `internal/release/trash.go` (PurgeApp, after the production-flag check, before the List)
+- CONTROLLER RULING (2026-08-10, from Task 5 review): the guard refuses on a `provisioned` block OR a `grant` block — a grant that crashed before recording `Provisioned` has already created backend estate (bucket, user, delivered key), and `grant` is the earliest bucket-visible witness. All guard code and tests in this task check BOTH keys.
- Modify: `internal/release/trash_test.go`
**Interfaces:**
@@ -725,7 +726,10 @@ An UNREADABLE record refuses too — same direction as the production flag.
// is the only purge path with backend authority (objstore spec §9;
// box-side auto-purge refuses via release.ErrStoreProvisioned). Failure
// aborts the purge with the record intact: re-entering resumes.
- if rec, _, rerr := objstore.Get(r.Context(), s.cfg.Releases, acct.Sqid, app); rerr == nil && rec != nil && rec.Provisioned != nil {
+ // Guard on Provisioned OR Grant (controller ruling, Task 5 review): a
+ // grant block without a provisioned block is a crashed grant whose
+ // backend estate is real.
+ if rec, _, rerr := objstore.Get(r.Context(), s.cfg.Releases, acct.Sqid, app); rerr == nil && rec != nil && (rec.Provisioned != nil || rec.Grant != nil) {
if s.cfg.StoreProvisioner == nil {
fail("“" + app + "” has a provisioned object store, and this console has no storage backend configured (CARLOS_STORE_BACKEND) — it cannot be deleted here until the store is torn down.")
return
@@ -735,7 +739,16 @@ An UNREADABLE record refuses too — same direction as the production flag.
fail("Could not tear down “" + app + "”'s object store — nothing was deleted; try again.")
return
}
- s.audit(acct, app, addr, "store-destroy", "tore down the object store (bucket "+rec.Provisioned.Bucket+") during permanent deletion")
+ // rec.Provisioned may be nil here (crashed-grant shape) — name the
+ // bucket from Grant, which the guard condition proves non-nil when
+ // Provisioned is.
+ bucket := ""
+ if rec.Provisioned != nil {
+ bucket = rec.Provisioned.Bucket
+ } else if rec.Grant != nil {
+ bucket = rec.Grant.Bucket
+ }
+ s.audit(acct, app, addr, "store-destroy", "tore down the object store (bucket "+bucket+") during permanent deletion")
} else if rerr != nil {
log.Printf("console: purge store read %s/%s: %v", acct.Sqid, app, rerr)
fail("Could not read “" + app + "”'s object-store record — nothing was deleted; try again.")