brief: scope the codex build to stages 4-6 of the self-serve spec
The brief names the two review blockers by name — the mail-shaped authority gate and the grant-written-but-not-provisioned walk — because both are shapes an implementer reading only the code would reinvent wrong, and both cost a review round already. Committed beside the spec so a quota-interrupted run restarts from the same instructions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CfUNFHm89j2mj5q7vBHsfH
1 file changed,
+92
−0
docs/superpowers/specs/CODEX-BUILD-BRIEF-store-self-serve.md+92 −0
diff --git a/docs/superpowers/specs/CODEX-BUILD-BRIEF-store-self-serve.md b/docs/superpowers/specs/CODEX-BUILD-BRIEF-store-self-serve.md| new file mode 100644 |
| index 000000000..c4cacb3dd |
| --- /dev/null |
| +++ b/docs/superpowers/specs/CODEX-BUILD-BRIEF-store-self-serve.md |
| @@ -0,0 +1,92 @@ |
| +# Build brief: self-serve object storage, stages 4–6 |
| + |
| +**For:** an independent implementer (GPT-6 via codex) building from a fixed |
| +commit. **Reviewer:** a second model reads the result afterwards; write for that |
| +reading. |
| + |
| +**Repo:** carlos/platform. **Branch:** `worktree-store-self-serve`. |
| +**Base commit:** `af8d996`. **Spec:** |
| +`docs/superpowers/specs/2026-09-02-object-storage-self-serve-design.md` — |
| +read it whole before writing anything. It is the contract; this brief only |
| +scopes the slice and names the traps. |
| + |
| +## The one-paragraph shape |
| + |
| +An account applies for object storage. A deployment operator approves the |
| +account once. From then on any app in that account gets a store from |
| +`carlos store create` alone, and never learns the word "grant". The per-app |
| +operator `grant` verb survives unchanged for unapproved accounts, `--bucket` |
| +adoption, and repair. |
| + |
| +## Already done — do not rebuild |
| + |
| +Stages 1–3 of §14 are committed and green: |
| + |
| +- `09d18f5` — `Grant`'s final Put retries a lost CAS, as `Rotate`'s does. |
| +- `8e3482f` — the console serialises store walks per app (the mutex of §9). |
| +- `0294fa3` — the `Entitlement` record, its accessors, `GrantBlock.Source` |
| + and `Grant`'s new `source` parameter across all callers. |
| + |
| +So `objstore.Entitlement`, `GetEntitlement`/`PutEntitlement`, |
| +`GrantSourceOperator` and `GrantSourceAccountApproval` all exist. Read them |
| +before adding anything adjacent. |
| + |
| +## Build these three stages, one commit each |
| + |
| +**Stage 4 — console doors: apply, approvals, approve, decline.** No behaviour |
| +change anywhere else; the doors are dormant until stage 5 reads them. |
| + |
| +**Stage 5 — the provision door and the CLI's two-door `create`.** Behaviour |
| +changes here. Everything before it is inert. |
| + |
| +**Stage 6 — CLI verbs, the status line, and the two changed printer sentences** |
| +(`store.go:261` and `:348`). |
| + |
| +## Traps the spec already paid for — honour them exactly |
| + |
| +- **§5's authority shape is `apimail.go`'s pause door, NOT `apiAccountApp`'s.** |
| + `cliUser` → fleet-operator bit → `CARLOS_STORE_OPERATORS` → bare |
| + byte-identical `http.NotFound`; then resolve the account |
| + viewer-independently via `AccountBySqid`, as `mailPauseTarget` does. Routing |
| + approve/decline through a member gate deadlocks approval — an operator has no |
| + reason to be a member of the account they are approving. This was blocker 2 |
| + of the 2026-09-03 review; do not reintroduce it. |
| +- **§4's implementation gate comes first.** Before the record is used, prove by |
| + test that `store/entitlements/` is unreachable through the edge store door. |
| + Read `canonicalKey`/`aclRead` in `internal/edge/storedoor.go` rather than |
| + trusting the prefix's shape. The review flagged this suspected, not |
| + confirmed. |
| +- **A grant already written but not yet provisioned must let the walk RUN to |
| + completion**, not refuse it. That was blocker 1; §13 names the regression |
| + test. |
| +- **State is derived from the blocks, never stored beside them.** No object = |
| + never applied; `applied` alone = pending; `decision` present = decided. |
| +- **A declined owner may re-apply** — `apply` overwrites `applied` and clears |
| + `decision` under CAS. The anti-spam control is the existing `write` budget |
| + keyed on the address, not a new rule. |
| + |
| +## Out of scope for this run — do not touch |
| + |
| +- Stage 7 (console UI, the notification of §15, `StoreProvisioned` and the |
| + bucket count) and stage 8 (`docs/site/cli.md`). A later run. |
| +- Anything under `infra/`. The host role's provisioning IAM and its permissions |
| + boundary are real and blocking, but they ride with #368 — see §1's third |
| + blocker and §12. |
| +- New root env vars. The spec adds none, deliberately. |
| +- `main`, and any other branch or worktree. |
| + |
| +## How to work |
| + |
| +- Test-first, following §13's split: walks against `MemProvisioner`, doors |
| + against the console fixture, printers against literal replies with no stub |
| + server. §13 lists the cases; treat that list as the minimum, not the target. |
| +- `AGENTS.md` governs — read it. In particular: `go build ./...`, |
| + `go vet ./...`, `go test ./... -count=1` and `gofmt -l .` all clean before |
| + any commit. Sandbox note in its Build & test section: |
| + `GOCACHE=/tmp/claude-1001/gocache GOFLAGS=-mod=mod GOPROXY=off`. |
| +- One commit per stage, in order, each one green on its own. Commit messages in |
| + this repo's voice: what changed and the reasoning that made it that shape — |
| + read `git log` for the register. |
| +- If the spec is wrong or underdetermined, say so in the commit message and in |
| + your final summary rather than inventing a rule silently. A named gap is |
| + worth more than a guess. |