carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1# Build brief: self-serve object storage, stages 4–6
2
3**For:** an independent implementer (GPT-6 via codex) building from a fixed
4commit. **Reviewer:** a second model reads the result afterwards; write for that
5reading.
6
7**Repo:** carlos/platform. **Branch:** `worktree-store-self-serve`.
8**Base commit:** `4e6ea53`. **Spec:**
9`docs/superpowers/specs/2026-09-02-object-storage-self-serve-design.md` —
10read it whole before writing anything. It is the contract; this brief only
11scopes the slice and names the traps.
12
13## The one-paragraph shape
14
15An account applies for object storage. A deployment operator approves the
16account once. From then on any app in that account gets a store from
17`carlos store create` alone, and never learns the word "grant". The per-app
18operator `grant` verb survives unchanged for unapproved accounts, `--bucket`
19adoption, and repair.
20
21## Already done — do not rebuild
22
23Stages 1–3 of §14 are committed and green:
24
25- `09d18f5` — `Grant`'s final Put retries a lost CAS, as `Rotate`'s does.
26- `8e3482f` — the console serialises store walks per app (the mutex of §9).
27- `0294fa3` — the `Entitlement` record, its accessors, `GrantBlock.Source`
28 and `Grant`'s new `source` parameter across all callers.
29
30So `objstore.Entitlement`, `GetEntitlement`/`PutEntitlement`,
31`GrantSourceOperator` and `GrantSourceAccountApproval` all exist. Read them
32before adding anything adjacent.
33
34## Build these three stages, one commit each
35
36**Stage 4 — console doors: apply, approvals, approve, decline.** No behaviour
37change anywhere else; the doors are dormant until stage 5 reads them.
38
39**Stage 5 — the provision door and the CLI's two-door `create`.** Behaviour
40changes here. Everything before it is inert.
41
42**Stage 6 — CLI verbs, the status line, and the two changed printer sentences**
43(`store.go:261` and `:348`).
44
45## Traps the spec already paid for — honour them exactly
46
47- **§5's authority shape is `apimail.go`'s pause door, NOT `apiAccountApp`'s.**
48 `cliUser` → fleet-operator bit → `CARLOS_STORE_OPERATORS` → bare
49 byte-identical `http.NotFound`; then resolve the account
50 viewer-independently via `AccountBySqid`, as `mailPauseTarget` does. Routing
51 approve/decline through a member gate deadlocks approval — an operator has no
52 reason to be a member of the account they are approving. This was blocker 2
53 of the 2026-09-03 review; do not reintroduce it.
54- **§4's implementation gate comes first.** Before the record is used, prove by
55 test that `store/entitlements/` is unreachable through the edge store door.
56 Read `canonicalKey`/`aclRead` in `internal/edge/storedoor.go` rather than
57 trusting the prefix's shape. The review flagged this suspected, not
58 confirmed.
59- **A grant already written but not yet provisioned must let the walk RUN to
60 completion**, not refuse it. That was blocker 1; §13 names the regression
61 test.
62- **State is derived from the blocks, never stored beside them.** No object =
63 never applied; `applied` alone = pending; `decision` present = decided.
64- **A declined owner may re-apply** — `apply` overwrites `applied` and clears
65 `decision` under CAS. The anti-spam control is the existing `write` budget
66 keyed on the address, not a new rule.
67
68## Out of scope for this run — do not touch
69
70- Stage 7 (console UI, the notification of §15, `StoreProvisioned` and the
71 bucket count) and stage 8 (`docs/site/cli.md`). A later run.
72- Anything under `infra/`. The host role's provisioning IAM and its permissions
73 boundary are real and blocking, but they ride with #368 — see §1's third
74 blocker and §12.
75- New root env vars. The spec adds none, deliberately.
76- `main`, and any other branch or worktree.
77
78## How to work
79
80- Test-first, following §13's split: walks against `MemProvisioner`, doors
81 against the console fixture, printers against literal replies with no stub
82 server. §13 lists the cases; treat that list as the minimum, not the target.
83- `AGENTS.md` governs — read it. In particular: `go build ./...`,
84 `go vet ./...`, `go test ./... -count=1` and `gofmt -l .` all clean before
85 you call a stage done. Use
86 `GOCACHE=/home/paulca/.claude/tmp/gocache-codex GOFLAGS=-mod=mod GOPROXY=off`.
87- **Do not run `git commit`, `git add`, or any other writing git command.**
88 Your sandbox holds `.git` read-only on purpose and that is the right
89 division of labour: you write the code, the orchestrator makes the commits.
90 Leave the tree dirty and finish with a **commit plan** — for each stage, the
91 exact file list and a commit message in this repo's voice (read `git log` for
92 the register: what changed, and the reasoning that made it that shape).
93- **User-facing copy.** This repo's owner gates new user-facing wording behind
94 a review he runs himself. The wording the spec already fixes — §6's two
95 changed sentences at `store.go:261` and `:348`, and any string the spec
96 quotes — is settled; write it as specified. For any string you must invent
97 (a new CLI message, a new error), write the clearest thing you can and
98 **list every one of them in your final summary** under a heading
99 `NEW USER-FACING COPY`, with its file and line. They go to review as a batch
100 after the build. Do not block on this, and do not start a review server.
101- If the spec is wrong or underdetermined, say so in the commit plan and in
102 your final summary rather than inventing a rule silently. A named gap is
103 worth more than a guess.
104
105## Resuming from the first run
106
107A first run (2026-09-08) was stopped by sandbox blockers before it could
108commit. Its test drafts are **already applied to the working tree**, uncommitted:
109
110- `internal/edge/storedoor_test.go` — the §4 implementation-gate test. It found
111 that stage 3 *claimed* this test and it did not exist; the test now exists
112 and passes, and `aclRead` already excludes `store/entitlements/`, so no
113 policy change is needed.
114- `internal/console/apistore_entitlement_test.go` — stage 4 door tests.
115- `internal/console/apistore_provision_test.go` — stage 5 provision tests.
116- `internal/console/svcroutes_test.go` — four closed routes, count 17 → 21.
117
118**These drafts never ran** — the console fixture could not open a listener in
119that run. Treat them as a starting point to be run, corrected and extended,
120not as verified work. The first run's own notes flag two gaps in them: stage 5
121still needs a concurrent *two successful provisions* regression that mints one
122key (the existing queued test revokes before the second request), and stage 6
123has no tests yet.
124
125That run's full findings — twelve of them, including that the spec's route
126paths (`/a/{sqid}/apps/{app}/store/provision`) do not match the actual CLI
127route family (`/api/cli/...`) — are in `/tmp/store-self-serve-resume.md`.
128**Read that file.** Where it proposes a resolution, the proposal is sound
129unless the spec contradicts it; say in your commit plan which ones you took.
130