carlos / platform Public

The handoff names 1395f7bf as the code tip with its gate and CI result; round twelve's reports are added

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 81ff7c9db8b4341ec722b1ce411df71358e1efbf parent 1395f7b
4 files changed, +248 −8
  • .claude/HANDOFF.md +7 −5
  • docs/superpowers/specs/CODEX-FINDINGS-carloku-paid-launch-overnight-round12.md +105 −0
  • docs/superpowers/specs/OPUS-REVIEW-carloku-paid-launch-overnight-round12.md +133 −0
  • implementation/carloku-paid-launch/OUTSTANDING.md +3 −3

CI passed — run details

diff --git a/.claude/HANDOFF.md b/.claude/HANDOFF.md
index 5939eeba5..ba65183cc 100644
--- a/.claude/HANDOFF.md
+++ b/.claude/HANDOFF.md
@@ -7,24 +7,26 @@
Get Carloku's paid plans live with Stripe Tax and the complete metered model; have the pricing page and signup flow reviewable in the morning.
**STATUS:**
-Three things, kept apart. (1) Draft copy to review, not approved: pricing http://docs-t1.tail5fd3ba.ts.net:38009 (fallback http://100.119.180.57:38009), console billing page http://docs-t1.tail5fd3ba.ts.net:45033 (fallback http://100.119.180.57:45033). (2) A demo to click through: http://100.119.180.57:41881, real console handlers with Keymail and Stripe simulated; not Stripe proof. (3) A tested branch that is not production-ready: `implementation/carloku-paid-launch/OUTSTANDING.md` says what is reviewable, tested and proven, lists every launch blocker, and lists each approval to ask for with its exact scope. All three review servers answered 200 on the IP form at 06:12 Dublin on 2026-10-01.
+Three things, kept apart. (1) Draft copy to review, not approved: pricing http://docs-t1.tail5fd3ba.ts.net:38009 (fallback http://100.119.180.57:38009), console billing page http://docs-t1.tail5fd3ba.ts.net:45033 (fallback http://100.119.180.57:45033). (2) A demo to click through: http://100.119.180.57:41881, real console handlers with Keymail and Stripe simulated; not Stripe proof. (3) A tested branch that is not production-ready: `implementation/carloku-paid-launch/OUTSTANDING.md` says what is reviewable, tested and proven, lists every launch blocker, and lists each approval to ask for with its exact scope. All three review servers answered 200 on the IP form at 07:33 Dublin on 2026-10-01.
Billing is OFF, the quantity worker is OFF (two switches, both unset), nothing attests, nothing was deployed, and no live Stripe or AWS write was made.
-Code tip `84311b9f` (it changes code: round ten's fixes, in `internal/objstore`). Full gate on that exact commit: go build, go vet, gofmt, `make test` with 61 packages ok (`logs/gate-84311b9f.txt` in the run directory); cross builds for darwin, linux arm64 and windows (`logs/cross-84311b9f.txt`). Commits after `84311b9f` on this branch are documents only unless this file says otherwise; the earlier gated tips were `94ca7482`, `3d4104cb`, `c3e87a94` and `43383b93`. Amadan CI has been queued on each pushed tip all night and has never run; an agent account cannot see or manage the runners.
+Code tip `1395f7bf` (it changes code: round eleven's fixes, in `internal/objstore`). Full gate on that exact commit: go build, go vet, gofmt, `make test` with 61 packages ok (`logs/gate-1395f7bf.txt` in the run directory); cross builds for darwin, linux arm64 and windows (`logs/cross-1395f7bf.txt`). The gate's first run on this commit failed one test in `internal/activator`, a package the commit does not touch (`TestExecBackendStartWorkerEnvWinsOverTenantFile`, which reads a file a child process is still writing); it passed 30 runs alone and the second full run passed. The failed log is kept as `logs/gate-1395f7bf-run1-failed.txt`. Commits after `1395f7bf` on this branch are documents only unless this file says otherwise; the earlier gated tips were `84311b9f`, `94ca7482`, `3d4104cb`, `c3e87a94` and `43383b93`. Amadan CI sat queued on every tip all night, first ran at 07:34 Dublin on `1395f7bf`, and passed on that commit. Later tips need their own run.
-Independent review: no round has passed. GPT-6 Astra rejected rounds 1 to 10; the AGENTS-required Opus production review accepted with fixes in rounds 2 to 10. Round ten (Astra three P1; Opus one P2, one P3) is fixed in `84311b9f` with reproductions and mutation checks, except what `OUTSTANDING.md` names as open. Those fixes have had no independent confirmation: round eleven of both runs against `84311b9f`, run directory `logs/astra-review11.md` and `logs/opus-review11.md`. Nothing lands until a round passes. Dispositions: `docs/superpowers/specs/CODEX-DISPOSITIONS-carloku-paid-launch-overnight.md`.
+Independent review: no round has passed. GPT-6 Astra rejected rounds 1 to 12; the AGENTS-required Opus production review accepted with fixes in rounds 2 to 12. Round twelve read `1395f7bf`: every round-eleven finding is closed for its reported shape; Astra found one P1 and two P2, Opus one P2 and three P3, all in the store walks and none fixed in `1395f7bf` (run directory `logs/astra-review12.md`, `logs/opus-review12.md`; copies under `docs/superpowers/specs`). Nothing lands until a round passes. Dispositions: `docs/superpowers/specs/CODEX-DISPOSITIONS-carloku-paid-launch-overnight.md`.
Two open defects, both with their paths disabled, neither offered as a risk to accept: a quantity request that runs after Stripe has forgotten its key, and a late invoice-line request that can leave a finalized invoice with a wrong total a customer could pay. The design that removes both is `docs/superpowers/plans/2026-10-01-late-request-fencing.md`, built so far on `feat/carloku-late-request-fencing-20261001` (`68f77faf`, pushed, not gated, not reviewed). That branch also voids a wrong-total invoice; that is remediation, and its limits are pinned by tests there.
Scoped replication attestation (path A) cannot prove an exclusive writer and is off this branch, parked on `feat/carloku-scoped-attestation-20261001` (pushed). Replication-write completeness is a launch blocker, not waived. Path B has a first build on `feat/carloku-metered-replicator-20261001` (`2140eb7b`, pushed, not gated, not reviewed): a metered replication store with no unmetered fallback, an owner for every database the box replicates, and a fleet store that refuses anything it cannot meter. Nothing calls it and nothing attests. Its code map is in the run directory, `notes/path-b-replicator-code-map.md`.
+The operator evidence door for a store's partial first hour has a first build on `feat/carloku-bucket-hour-evidence-20261001` (`0a578f22`, not gated, not reviewed). The hour still holds by default; the door releases only that hour, only for the owner the ledger names, on a named person's recorded evidence.
+
**NEEDED:**
-- Paul: approve or edit both copy reviews; confirm the HALF code word; decide the proration policy; approve or change the whole-paid-day rule for partial months; rule on a store's first clock hour and a box's first minutes; review member copy for a bucket retained by another account.
+- Paul: approve or edit both copy reviews; confirm the HALF code word; decide the proration policy; approve or change the whole-paid-day rule for partial months; review member copy for the store refusals. No ruling is asked for on a store's first clock hour or a box's first minutes: they hold by default, and what releases one is an operator evidence door (not built), not a waiver.
- Scoped approvals, prepared as plans only: the `rk_live` restricted key and Stripe catalog; the Cost and Usage Report export; Stripe test mode, to run `TestSandboxQuantityFence` and confirm the provider's treatment of a proration date.
**RECOMMENDED:**
-- Read round eleven, fix what it finds, re-review the exact tip. Get CI to run on that tip.
+- Fix round twelve's findings, gate and re-review the exact tip, and get CI on it.
- Finish the late-request design on its branch (the invoice engine's void on a failed verification, the idle measurement in the sandbox test), then review it.
- Then, each on its own branch: path B of the attestation plan, quantity reduction at renewal, the AWS principal inventory and the bucket ownership attestation door, Free-tier enforcement, dormancy and pool accounting (none started).
- Find what moved 1,395 GB out of Ireland to three other regions in September (collector plan).
diff --git a/docs/superpowers/specs/CODEX-FINDINGS-carloku-paid-launch-overnight-round12.md b/docs/superpowers/specs/CODEX-FINDINGS-carloku-paid-launch-overnight-round12.md
new file mode 100644
index 000000000..434986682
--- /dev/null
+++ b/docs/superpowers/specs/CODEX-FINDINGS-carloku-paid-launch-overnight-round12.md
@@ -0,0 +1,105 @@
+# Round-twelve review: `1395f7bf`
+
+Reviewed against base `aed42815`, concentrating on `84311b9f..1395f7bf`. Static review only: no modifications, tests, network commands, Stripe CLI or AWS CLI. Test counterfactuals below come from reading, not execution.
+
+All locations refer to the pinned commit. **O** = `internal/objstore`, **C** = `internal/console`, **D** = `implementation/carloku-paid-launch`, **S** = `docs/superpowers/specs`.
+
+## 1. Round-eleven dispositions
+
+| Finding or claim | Disposition | Fix and regression | Would the test pass without the fix? |
+|---|---|---|---|
+| Astra F1 / Opus N1: a legacy-X take-back chooses Y, leaving an untracked bucket or forgetting retained data | **Closed for the reported schedules.** Grant now preserves the provisioned bucket, including before any deletion note exists. | Bucket selection: `O/walk.go:186`; legacy provenance: `:271`. Tests: `TestGrantOverATeardownKeepsTheRecordsOwnBucket`, `O/ownership_test.go:2549`; `TestGrantOverARetainedBucketTakesThatBucketBack`, `:2621`. | **No.** Restoring grant-first selection makes the first take-back fail or choose Y; the retained case creates Y instead of taking X back. Restoring the old `Minted` expression fails the explicit assertion at `:2593`. Neither test reproduces the original final-write failure, but the bucket-selection invariant closes that schedule. |
+| Astra F2: a waiting rotation ignores an intervening grant’s count | **Closed for the reported ordering.** A rotation beginning after the failed grant remains unsafe; new F2 below. | Captured estate and comparison: `O/walk.go:732`, `:747`; count included at `:1203`. Test: `TestRotationStopsWhenAGrantRanWhileItWaited`, `O/ownership_test.go:2764`. | **No.** Comparing only bucket and key lets both verbs proceed. The test checks conflict and unchanged keys at `:2824`, `:2827`. |
+| Astra F3: IAM creation and policy writing precede the count | **Closed for provider mutations.** Read-only probes still precede it. | Count: `O/walk.go:424`; `EnsureUser`: `:461`. Test: `TestGrantThatCannotBeCountedTouchesNothing`, `O/ownership_test.go:2715`. | **No.** Moving the count after `EnsureUser` creates a user before the first injected failure and rewrites the edited policy before the healthy re-grant’s failure. |
+| Opus N2: permanent refusal masquerades as CAS conflict, retains the lease and recommends another grant | **Partly closed; member copy accepted and left open as 19g.** Sentinel and clean release are fixed. | `O/ownership.go:84`; refusal `O/walk.go:263`; clean release classification `:1222`. Test: `TestGrantIsRefusedOverATeardownNoteForAnotherBucket`, `O/ownership_test.go:2669`. Copy remains at `C/apistore.go:641`, `C/apistore_provision.go:62`. | **No.** The test requires the sentinel and immediate acquisition by another app (`:2689`, `:2703`). It does not test the recommended teardown route; new F1 below. |
+| Astra claim: final deletion can erase a record naming two live buckets | **Closed for the reported Grant/Provisioned mismatch.** Other inconsistent teardown records remain unsafe. | `oneBucket`: `O/walk.go:1418`; entry guards `:159`, `:796`. Test: `TestWalksRefuseARecordThatNamesTwoBuckets`, `O/ownership_test.go:2839`. | **No** with either entry guard removed: the corresponding sentinel assertion fails. The test covers Grant and Teardown only. |
+| Both reviews’ claim: every creating grant counts before provider calls | **Behavior closed; wording partly closed.** Mutations follow the count, but “before it calls the provider at all” remains false. | `D/AGENT_HANDOFF.md:35`; `O/walk.go:356`, `:397`, `:424`, `:461`. Regression is the count-failure test above. | That regression fails without mutation ordering, but does **not** assert absence of probe calls. |
+| Astra claim: rotations require the same estate | **Closed as a comparison between the two reads.** It does not establish that the recorded key is the delivered key. | `O/walk.go:732`, `:747`; `D/AGENT_HANDOFF.md:35`. Regression at `O/ownership_test.go:2764`. | **No** without the estate comparison. New F2 begins after the count changed and therefore passes that comparison. |
+| Opus claim: another bucket’s unfinished teardown is refused regardless of deletion timestamp | **Closed in Grant.** | `O/walk.go:246`, `:262`. Test at `O/ownership_test.go:2669`, both timestamp-present and timestamp-absent cases. | **No** without the refusal. Neither case invokes Teardown. |
+| Opus claim: grant-door promises about recovery and unrecorded resources | **Partly closed.** Preserving X closes the reported abandoned-Y schedule; the unusable retry advice remains explicitly open. | Bucket fix and tests above; unchanged door at `C/apistore.go:641`; `D/OUTSTANDING.md:67`. | Bucket regressions fail without the fix. No copy regression was added. |
+| Astra claim: every unconfirmed acknowledgement first tries the provider key | **Closed as documentation.** The handoff now states the human-resolution exception. | `D/AGENT_HANDOFF.md:38`; implementation `C/billingquantity.go:728`, `:745`; disposition `S/CODEX-DISPOSITIONS-carloku-paid-launch-overnight.md:386`. | No new test for this wording change. |
+
+## 2. New findings
+
+### F1. P1 — Teardown trusts a deletion note that Grant refuses, skips a live bucket and deletes its record
+
+**Where:** `O/walk.go:262`, `:796`, `:934`, `:980`, `:1070`, `:1183`.
+
+**Scenario:**
+
+1. An inconsistent legacy or edited record has `Grant.Bucket = X`, `Provisioned.Bucket = X`, and a teardown note with `DeletedBucket = Y` and a nonzero `BucketDeletedAt`. X still contains the app’s objects.
+2. Grant refuses this record with `ErrUnfinishedTeardown`: the note concerns Y, not X.
+3. Following the sentinel’s stated recovery—finish the teardown (`O/ownership.go:81`)—runs Teardown.
+4. `oneBucket` passes because Grant and Provisioned agree on X.
+5. `bucketDone` checks only the timestamp. Teardown skips emptying and deleting X, and records Y’s deletion timestamp on **X’s** ownership ledger.
+6. It deletes the user, closes ownership, removes configuration and deletes the store record successfully. X and its objects remain without a store record.
+
+This is more than item 19g’s missing repair command or misleading copy: the proposed recovery can discard the evidence of a live bucket and assert a destruction that did not happen. A console purge can then continue after this successful teardown (`C/ownership.go:451`, `:502`).
+
+I did not find a coherent current-build walk that initially creates this mismatched note. The finding concerns the inconsistent-record class the new refusal explicitly handles; its presence in production is unknown.
+
+**Smallest acceptable fix:** Validate the deletion note’s bucket against the teardown target before trusting its timestamp or changing anything. Refuse a mismatch for operator repair. Also preserve teardown-only records carrying unresolved bucket evidence: the declaration-only branch (`O/walk.go:807`) and purge parser (`internal/release/trash.go:298`) currently ignore that evidence.
+
+Extend the refusal regression with a live X, a note about Y, and a subsequent Teardown. Require refusal, unchanged ledgers and preservation of X’s record and objects.
+
+### F2. P2 — A rotation started after a failed grant still deletes the delivered key
+
+**Where:** `O/walk.go:495`, `:503`, `:517`, `:603`, `:636`, `:747`.
+
+**Scenario:**
+
+1. A coherent provisioned store records K1. Remove one delivered variable so Grant must repair it.
+2. Grant persists the increased walk count, reaps K1, mints K2 and successfully delivers K2.
+3. Its final record write fails before taking effect with a non-CAS error. The record still names K1; configuration contains K2.
+4. After the failed grant’s lease expires, start a **new** rotation.
+5. Both reads see the same increased count and the same recorded K1. The new estate comparison succeeds.
+6. `ReapKeys(..., []string{K1})` deletes K2. Fail the following mint or delivery: the app retains dead credentials. Even a successful rotation has removed the overlap promised to processes using K2.
+
+An ordinary Grant retry does not necessarily repair this: configuration has every required variable and the endpoint is unchanged, so `EnvDelivered` and the healthy short-circuit accept it (`O/walk.go:95`, `:472`).
+
+The round-eleven ordering is fixed, but its retry reaches this ordering: the first rotation is told to run again, and the next invocation reads the already-incremented count.
+
+**Smallest acceptable fix:** Persist enough grant-completion state to distinguish a completed grant from one that may have delivered an unrecorded key. Refuse rotation until that repair completes, and make Grant actually repair that state instead of taking the healthy short-circuit.
+
+Add a regression that starts rotation **after** the failed final grant write, rather than before its lease acquisition. Assert that the delivered key survives a subsequent mint or delivery failure.
+
+### F3. P2 — Rotation and scanning bypass the two-bucket refusal
+
+**Where:** `O/walk.go:725`, `:747`, `:636`, `:646`; `C/apiscan.go:344`, `:405`, `:478`; `O/sweep.go:132`, `:213`.
+
+**Scenario:**
+
+1. Start with the legacy split record explicitly described in item 19g: Provisioned names X/K1, Grant names Y, and a failed earlier grant delivered K2 for Y. There is no teardown block.
+2. Grant and Teardown now refuse this record.
+3. Rotate does not call `oneBucket`. Its two estate reads agree, so it proceeds.
+4. It reaps K2, then delivers a new key with **X’s** coordinates from Provisioned. Rotation never rewrites the user’s policy, which still names Y.
+5. Rotation can return success while replacing working Y credentials with a bucket/key combination that cannot access X.
+
+Scanning also crosses the refusal boundary. Scan grant enables the provisioned bucket without checking the conflicting Grant. Sweep selects X and can publish completed coverage for it while the app uses Y. Enforcement and revoke mutate the record before their eventual Grant call returns `ErrTwoBuckets`.
+
+These are additional effects beyond the documented hold for a person to resolve. Direct purge does correctly refuse a record carrying either Grant or Provisioned (`internal/release/trash.go:305`).
+
+**Smallest acceptable fix:** Apply the bucket-consistency check to both rotation verbs and the scanning paths before provider calls or record changes. Extend `TestWalksRefuseARecordThatNamesTwoBuckets` to cover those entry points and assert unchanged credentials, configuration and scan state.
+
+### Other requested checks
+
+- **Count ordering:** The calls before the count are `BucketExists` and `BucketGrantees`, whose contracts are read-only (`O/provisioner.go:35`, `:45`). I found no mutating provisioner call before the count.
+- **Churn:** Production Grant callers are explicit grant/provision requests and scanning-policy changes (`C/apistore.go:623`, `C/apistore_provision.go:54`, `C/apiscan.go:558`). I found no timer calling Grant. Healthy re-grants now write once, as documented. Ordinary same-prefix declarations do not write (`C/apistore.go:338`).
+- **Bucket selection and `Minted`:** I found no harmful caller-dependent bucket substitution beyond the documented preservation of an existing bucket. A legacy bucket recreated by a take-back is marked `Minted=false`; an explicitly adopted but absent bucket can also be created with that flag false. Both conservatively withhold completeness. `Minted=true` records name-selection provenance before creation, not successful creation; completeness still requires the absence and principal probes and no Provisioned block (`O/walk.go:351`).
+- **Reachability:** I found no coherent current-build schedule introducing either contradictory bucket names or a mismatched deletion note. F1 and F3 concern the legacy/edited states expressly covered by the new refusals.
+- **Permanent rotation refusal:** The estate comparison alone does not permanently refuse an otherwise coherent store: a new invocation reads the new estate. F2 shows why that retry is not sufficient protection.
+
+## 3. Claims the code contradicts
+
+| Location | Claim | What the code supports |
+|---|---|---|
+| `D/AGENT_HANDOFF.md:35`; round-eleven dispositions `:387` | Every grant is counted before it calls the provider “at all.” | Read-only provider probes occur at `O/walk.go:356`, `:397`, before the count. The mutation-ordering claim is supported. |
+| `D/OUTSTANDING.md:67` | The two inconsistent-record states are refused before anything is touched. | Only particular entry points refuse them. Teardown trusts a mismatched deletion note; rotation and scanning act on split records. F1 and F3. |
+| `O/ownership.go:81` | Finishing teardown is the way forward after `ErrUnfinishedTeardown`. | Teardown can skip a live bucket, falsify its destruction history and remove its record. F1. |
+| `O/walk.go:1182` | At final record deletion, everything the record named is gone. | X survives the mismatched-note teardown in F1. |
+
+The corrected human-review exception in handoff item 8 matches the code.
+
+Tests establish narrower properties than the broad claims: `TestRotationStopsWhenAGrantRanWhileItWaited` covers a grant **between** rotation reads; `TestWalksRefuseARecordThatNamesTwoBuckets` exercises only Grant and Teardown; and the mismatched-note test never attempts its advertised teardown recovery. The count-failure test checks absent provider mutations, not absence of all calls or ledger writes.
+
+**Verdict: reject.**
\ No newline at end of file
diff --git a/docs/superpowers/specs/OPUS-REVIEW-carloku-paid-launch-overnight-round12.md b/docs/superpowers/specs/OPUS-REVIEW-carloku-paid-launch-overnight-round12.md
new file mode 100644
index 000000000..c978b4a81
--- /dev/null
+++ b/docs/superpowers/specs/OPUS-REVIEW-carloku-paid-launch-overnight-round12.md
@@ -0,0 +1,133 @@
+# Opus production and authority review, round 12: Carloku paid launch, `aed42815..1395f7bf`
+
+This was a read-only pass using `git show` only. I changed nothing, ran no tests and made no network calls. Every "fails without the fix?" answer comes from reading the test against the code. Since round 11 (`84311b9f..1395f7bf`) the only code changes are in `internal/objstore` (`walk.go`, `ownership.go`, `objstore.go`, `ownership_test.go`). The rest of the diff is documents.
+
+Abbreviations: **O** = `internal/objstore`, **C** = `internal/console`, **D** = `implementation/carloku-paid-launch`.
+
+**Production authority**
+
+- **Nothing attests.** `SetAttestation` and `SetAllClientsInstrumented` (`usage/durabilityledger.go:1455`, `:1475`) still have no non-test caller.
+- **The quantity worker cannot reach Stripe with either switch unset.** None of these changed this round: `C/billingquantity.go`, `C/billingservice.go`, `internal/billingstripe`, `cmd/console`, `internal/usage`, `internal/sigfence`. `cmd/console/billing.go` still accepts only `stripe-sandbox-verified` for the fence switch. My round 9 to 11 reading of the send lease, the key fence and the second switch still holds.
+- **The sandbox test is unchanged.** It is still skipped without the key, still refuses anything that is not `rk_test_`, and has still not been run.
+- **The walk lease and the backend fence are unchanged.** `walklock.go`, `fence.go` and `aws.go` did not change. `walkLease = fenceWindow + fenceSkew` (`O/walklock.go:55`), and `bound` stops `walkMargin` before the window (`:184-190`). Round 11's reading stands.
+- **Wrong billing, leaks and destroyed data.**
+ - I found no path that bills wrongly or leaks a secret.
+ - The new refusals put bucket names only in errors that the doors log and do not return.
+ - I found no new path that destroys a customer's data.
+ - I found one new path that gives back cross-account access (N1).
+
+## 1. Disposition table (round 11)
+
+| Finding | Status | Fix | Test; fails without the fix? |
+|---|---|---|---|
+| Astra F1 / Opus N1: a take-back to the minted Y over a record provisioned on X | **Closed.** `bucketFor` now prefers `Provisioned.Bucket`, then `Grant.Bucket`, then the arguments. The take-back is always for X. The authority this reinstates is new: N1 below. | `O/walk.go:186-196`; `Minted` at `:271` | `TestGrantOverATeardownKeepsTheRecordsOwnBucket` (`O/ownership_test.go:2549`): **fails** under round 11's rule, which refuses with a CAS error. `TestGrantOverARetainedBucketTakesThatBucketBack` (`:2621`): **fails**, because Y is created. The `adopt` iteration at `:2585` is vacuous: the first iteration has already written `Grant{X}`, so the old rule would pick X too (N4). |
+| Astra claim: a record can name X and Y at its final delete | **Closed for this build's writes.** A record from an earlier build is refused by Grant and Teardown before lease, provider call or write. Rotation is not refused (N2). | `O/walk.go:159`, `:796`, `oneBucket` `:1418` | `TestWalksRefuseARecordThatNamesTwoBuckets` (`:2839`): **fails** without either check. Grant would bump `Walks` and the `DeepEqual` fails; Teardown would destroy X. |
+| Astra F2: a waiting rotation ignores the grant count | **Closed for the waiting rotation.** A rotation that starts after a failed grant still reaps that grant's key (see §2, "Asked and not found"). | `O/walk.go:732`, `:747` | `TestRotationStopsWhenAGrantRanWhileItWaited` (`:2764`), both verbs: **fails** without the fix. The keys change because K2 is reaped, or the pending key is deleted. |
+| Astra F3: `EnsureUser` ran before the count | **Closed.** The count now precedes every provider mutation. The `BucketExists` and `BucketGrantees` reads still precede it (§3). | `O/walk.go:424-438`, before `:461` | `TestGrantThatCannotBeCountedTouchesNothing` (`:2715`): **fails**. With the count after `EnsureUser`, a user and policy exist after the failed count. |
+| Opus N1 (refuse whenever the teardown's coordinates differ) | **Closed.** Now reachable only when the note names a bucket other than the record's. | `O/walk.go:245-268` | `TestGrantIsRefusedOverATeardownNoteForAnotherBucket` (`:2669`): **fails**. Round 11 returned `ErrCASConflict`, not the sentinel, and did not refuse at all in the case with no note. |
+| Opus N2 (CAS-typed refusal, lease held, wrong copy) | **Partly closed, as dispositioned.** It has its own sentinel, it is in `walkEndedClean`, and it makes no provider call. The copy is open as 19g. | `O/ownership.go:84`, `O/walk.go:1222` | Same test; the lease reacquire at `:166` **fails** without the `walkEndedClean` entry. |
+| Astra claim: handoff item 8 (key always tried) | **Closed.** Item 8 now states that a resolved review stands in for the fence. | `D/AGENT_HANDOFF.md:38` | — |
+| Both claims: "counted before it calls the provider"; "a walk requires the same estate" | **Estate claim closed. Provider claim partly closed** (§3). | `D/AGENT_HANDOFF.md:35`, `O/objstore.go:64-71` | — |
+
+## 2. New findings, most severe first
+
+### N1. P2: a take-back over a retained teardown gives back cross-account access, now also through the member's own door
+
+**Where:**
+- `O/walk.go:186-196`: `bucketFor` now keeps X for a legacy record with no grant block.
+- `:271`: `Minted` is false whenever the record is provisioned.
+- `:351`: the probes and `otherStoreHolders` run only for `Minted && Provisioned == nil`, so they never run on a take-back.
+- `:406` → `O/ownership.go:371`: exclusivity is checked only by the joiner (`exclusive = rec.Grant.Minted`). A non-exclusive joiner is never refused by another account's open interval.
+- `C/apistore_provision.go:54`: the account-approval door drives Grant.
+
+**Scenario:**
+1. Legacy records: account a, app A, has `Provisioned{X}` and no grant block. Account c, app C, also names X. Cross-account sharing exists only because an operator once arranged it.
+2. A's member destroys the store. C holds X, so A's teardown deletes A's user and returns `ErrBucketRetained`. C keeps writing private objects to X.
+3. A's member runs `carlos store create`. The declare door writes nothing, because the prefix is unchanged. The provision door calls Grant with `GrantSourceAccountApproval`.
+4. `bucketFor` is X and `tornBucket` is X, so nothing refuses. `openBucketOwner` is non-exclusive and joins C's open interval. `EnsureUser` writes a policy on X. A key is minted and delivered.
+5. A's app can now read and delete everything C wrote after A left. No operator acted.
+
+Under round 11 this record's take-back went to A's own minted Y (the old F1 problem), and on main the same. The reach is new on this branch for legacy records with no grant block. For records that already had `Grant{X}` on an adopted shared X, it is pre-existing. `TestGrantOverARetainedBucketTakesThatBucketBack` asserts this outcome as correct.
+
+**Scope:** only buckets shared across accounts, which only an operator could have set up. Whether any exist in production cannot be told from the repository.
+
+**Smallest fix:** on a take-back (`rec.Teardown != nil`), if the ledger shows an open interval, or `otherStoreHolders` returns a holder, from another account, refuse unless `source == GrantSourceOperator`. Return a typed refusal in `walkEndedClean`. Add the retained fixture through the account-approval source and assert no user, no policy and no key.
+
+### N2. P3: rotation still acts on a record that names two buckets
+
+**Where:** `leaseForRotation` (`O/walk.go:725-760`) never calls `oneBucket`. Rotate delivers `pr.Bucket` (`:646-648`).
+
+**Scenario:**
+1. The record is `Grant{Y}`, `Provisioned{X}`. Main's rule can write this: a re-grant of a legacy record with no grant block, such as `regrantForScanning`, picks the minted Y, and its `EnsureUser` points the policy at Y.
+2. A member runs `carlos store rotate`.
+3. The rotation reaps, mints and delivers X coordinates, while the policy reaches Y.
+
+No data is destroyed, but the walk acts on a state that the dispositions say "a person decides". The scan doors also still write the record. Their regrant then fails with `ErrTwoBuckets`, and the door says "re-run `carlos store grant`".
+
+**Smallest fix:** call `oneBucket(first, …)` at the top of `leaseForRotation`. Extend `TestWalksRefuseARecordThatNamesTwoBuckets` to both rotation verbs.
+
+### N3. P3: "finish the teardown" forgets the noted bucket for the shape `ErrUnfinishedTeardown` refuses
+
+**Where:** `O/walk.go:262-263`; Teardown's declaration-only path at `:806-807` and `:888-889`.
+
+**Scenario:** the test's fixture is a teardown block with `DeletedBucket = Z` and neither a grant nor a provisioned block. The refusal says to finish the teardown. That teardown takes the declaration-only path and deletes the record without a lease. Z's deletion is never recorded on Z's ledger, and any retention is forgotten.
+
+No walk of this build writes this shape, so the impact is limited to hand-edited or earlier-build records. The sentinel's doc ("the way forward is to finish the teardown") is wrong for it.
+
+**Smallest fix:** make Teardown also refuse when `Teardown.DeletedBucket` names a bucket other than its coordinates, with the same sentinel, so a person decides. Or reword the sentinel and 19g to say an operator repairs it by hand.
+
+### N4. P3: the adopt case in `TestGrantOverATeardownKeepsTheRecordsOwnBucket` tests nothing
+
+**Where:** `O/ownership_test.go:2585`.
+
+**Scenario:** the second iteration (`adopt = ledgerBucket`) runs after the first has written `Grant{X}` and cleared the teardown. Under either rule a re-run keeps X. "A caller that passes an adopted bucket over a legacy record with no grant block and gets X" is therefore untested.
+
+**Smallest fix:** build a fresh fixture for each iteration.
+
+### Asked and not found
+
+- **A provider mutation before the count.**
+ - None. The only provider calls before `:424` are `BucketExists` and `BucketGrantees`, which are reads under the fence.
+ - `markBucketDestroyed` and `openBucketOwner` are ledger writes.
+ - The count's conflict retry compares the pre-increment estate (`:428`). A lost-response count that did apply makes the estate differ, so that invocation fails and a re-run reads afresh.
+- **Churn from the healthy re-grant's write.**
+ - Grant has three callers, all per request with no timer:
+ - the operator grant door (`C/apistore.go:623`);
+ - the provision door (`C/apistore_provision.go:54`);
+ - `regrantForScanning` (`C/apiscan.go:558`).
+ - The declare door writes only when `Declared` is nil or the prefix changes on a record with no grant (`C/apistore.go:322-340`), so it does not conflict once a store is granted.
+ - A waiting teardown or rotation loses one invocation for each grant that ran. None is refused for good.
+- **`Minted` written wrongly.**
+ - It can be true only when the bucket is the minted name and nothing is provisioned. Even then the probes and the holder checks gate completeness.
+ - It is now false for a minted-name X on a legacy record. That is conservative: the ledger claims nothing.
+ - There is no case where it reads true for a bucket this grant did not mint.
+- **A two-bucket record written by this build.**
+ - None through an ordinary walk.
+ - The one theoretical writer is Grant's final-write retry (`:539-555`). Its guard `freshKeyID == startKeyID` holds for `""`, so a first grant G1 frozen past its lease could land `Provisioned{Y}` over a newer `Grant{Z}` that has no provisioned block yet.
+ - Only the signing fence stops that: `walkLease` is longer than the window. It holds on AWS. It is unproven on S3-compatible backends and the directory store (19c, 19d).
+ - It is pre-existing. The `oneBucket` comment "No walk in this build writes one" should be read as resting on the fence.
+- **A rotation that still reaps a key a grant delivered.**
+ 1. Grant G1's repair runs `ReapKeys(keep=PrevKeyID)`, which deletes K1, then delivers K2 and fails its last write.
+ 2. A re-run G2 then finds the record healthy (env present, endpoint unchanged) and short-circuits. The record goes on naming the deleted K1.
+ 3. A rotation started later compares equal and reaps K2. Hosts still on K2 fail until K3 reaches them.
+ - This is pre-existing, documented as Rotate's reap, and is availability only. It is not a waiting-rotation schedule.
+- **Purge, the destroy door and declaration-only teardown on a two-bucket record.**
+ - The purge and the destroy door call Teardown, which refuses (`C/ownership.go:451`, `C/appsettings.go:1037`). The purge aborts with the record intact.
+ - The app purge sweeps only bare declarations.
+ - Their copy ("try again") is wrong for the refusal. See §3, 19g.
+
+## 3. Claims the code contradicts
+
+| Location | Claim | What the code supports |
+|---|---|---|
+| `D/AGENT_HANDOFF.md:35` (item 5) | "Every grant is counted on the record before it calls the provider at all" | `BucketExists` and `BucketGrantees` (`O/walk.go:356`, `:397`) run before the count. The accurate sentence is the one in `O/objstore.go:64-71`: "before a grant ensures the user, creates the bucket or mints and delivers a key". |
+| `D/AGENT_HANDOFF.md:35`; dispositions round 11 Astra F1 row | "a grant never moves a store to another name", stated as closing the authority question | True. But keeping X now restores cross-account access through the member's door (N1), which neither document mentions. |
+| `D/OUTSTANDING.md` 19g; dispositions Opus N2 row | Only the grant door's copy is wrong for the two refusals | The provision door (`C/apistore_provision.go:62`), the destroy door (`C/appsettings.go:1044-1045`), the purge (`C/ownership.go:453`) and the scan-enforce door (`C/apiscan.go:518`) also say "try again" or "re-run" for `ErrTwoBuckets` and `ErrUnfinishedTeardown`. |
+| `O/ownership.go:81-84` (`ErrUnfinishedTeardown` doc) | "the way forward is to finish the teardown" | For a record with no grant or provisioned block, finishing deletes the record and forgets the noted bucket (N3). |
+| Dispositions round 11, `ErrTwoBuckets` row | A two-bucket record is refused "before they touch anything", read as covering every walk | That holds for Grant and Teardown only. Rotation and RotateFinish proceed (N2). |
+
+Nothing in `walklock.go`, `fence.go`, the evidence document, "The fence" in `billingquantity.go`, or `OUTSTANDING.md` items 6, 6a, 7, 9 or 19a to 19f is contradicted by the code. Nothing this round makes any of them worse.
+
+## 4. Verdict
+
+**Accept with fixes:** nothing attests, the worker cannot reach Stripe with either switch unset, and all round 11 findings are closed for their shapes; N1 (operator-only take-back over another account's open interval) and the item 5 and 19g wording should land before the storage authority claims are relied on.
diff --git a/implementation/carloku-paid-launch/OUTSTANDING.md b/implementation/carloku-paid-launch/OUTSTANDING.md
index 25a4de612..fa8d2e65a 100644
--- a/implementation/carloku-paid-launch/OUTSTANDING.md
+++ b/implementation/carloku-paid-launch/OUTSTANDING.md
@@ -34,7 +34,7 @@ Each is concrete. None is waived.
1. **Replication writes (`durability_writes_1k`) cannot be proven on any box.** The external replicator writes outside the ledger. Path A (scoped attestation) was built, reviewed in round six, and cannot prove an exclusive writer; it is off this branch and parked on `feat/carloku-scoped-attestation-20261001`. Path B (the platform's own replicator as the only writer) is **partial, unwired**, on `feat/carloku-metered-replicator-20261001` (`2140eb7b`, on the forge): a litestream store whose every replica client is metered with no unmetered fallback, an owner for each database the box replicates, and a fleet store that refuses anything it cannot meter, each with tests. **Absent**: the agent wiring that binds those meters to the box's ledger, a restore path that does not shell out to the external binary, the unit change, moving the replica credentials so nothing else on the box holds them (the unit runs as the same user as the agent today), and any attestation. Until all of that exists and is approved for a box, this meter is incomplete every month and nothing is invoiced.
2. **Store transfer (`store_egress_gb`) has no source.** No Cost and Usage Report export exists on the account. It needs the scoped infrastructure approval, a first real delivery, an IAM principal inventory in the AWS provisioner (not built), and the bucket ownership attestation for stores that already exist (designed, not built).
-3. **A store's partial first hour** with a charged line holds the month, and nothing releases it. It holds by default and nobody is asked to waive it. What can release it is evidence: an operator door that records, for one bucket-hour, who is shown to have owned it and on what evidence, which the fold then honours. **Absent**; to be built locally.
+3. **A store's partial first hour** with a charged line holds the month, and nothing releases it. It holds by default and nobody is asked to waive it. What can release it is evidence: an operator door that records, for one bucket-hour, who is shown to have owned it and on what evidence, which the fold then honours. **Partial, unwired into this branch**, on `feat/carloku-bucket-hour-evidence-20261001` (`0a578f22`): the record, the fold change and three operator API doors, with tests and mutation checks. It accepts only the hour a complete ledger began vouching in and only the owner that ledger names. Not gated, not reviewed, no CLI verb, and its operator-facing sentences are unreviewed.
4. **A box's first minutes** before its ledger opened hold that day for everyone. Same as item 3: it holds by default, and the release is an operator evidence door, not a waiver. **Absent**.
### Stripe
@@ -63,7 +63,7 @@ Each is concrete. None is waived.
### Process
-19. **No review round has passed.** Astra rejected rounds one to eleven; Opus accepted each with fixes. Round eleven found one P1 and two P2 in the store walks (a grant could move an older record to another bucket name; a waiting rotation ignored a grant that ran; the user and its policy were written before the grant was counted). Its fixes are on the launch branch and unconfirmed. Each round's fixes are on the launch branch, gated locally, and unconfirmed until the next round reads them.
+19. **No review round has passed.** Astra rejected rounds one to eleven; Opus accepted each with fixes. Round eleven found one P1 and two P2 in the store walks (a grant could move an older record to another bucket name; a waiting rotation ignored a grant that ran; the user and its policy were written before the grant was counted). Its fixes are on the launch branch (`1395f7bf`). Round twelve read them: every round-eleven finding is closed for its reported shape, Astra rejected again (one P1, two P2) and Opus accepted with fixes (one P2, three P3). The new findings are about records no walk of this build writes (a teardown note for another bucket, a record naming two buckets) being acted on by teardown, rotation and the scanning doors; a rotation started after a grant that failed at its last write; and a take-back that returns access to a bucket another account holds, through the member's own door. They are not fixed in `1395f7bf`. Each round's fixes are on the launch branch, gated locally, and unconfirmed until the next round reads them.
19g. **Two store refusals tell the operator to run the grant again, which cannot work.** A record that names two buckets (`ErrTwoBuckets`) and a teardown note for a bucket that is not the record's (`ErrUnfinishedTeardown`) are refused before anything is touched, and the grant door answers "re-run the grant to resume". Neither state is written by this build; whether an earlier build left such records in production has not been checked. There is no operator verb to repair one, and correct copy needs approval (A).
19f. **A store that leaves a bucket with an unsupported name does not hand on its scanner debt.** There is no ledger for such a name. The last holder would delete the bucket and leave a scanning plan behind. Today's scanner (ClamAV) has no plan to leave, so this is latent.
19e. **A purge can end with a store that was granted during it.** A declare and a grant admitted while the app was live can land between the purge's last check and the removal of its marker. The purge now reports that as an error and never deletes the record, but it does not prevent it: the doors check liveness when they admit a request, not when they write. And after that error the estate is named only by its record and a log line. The app's console row is still trashed, so the settings page and the store-destroy door answer 404 and a second purge answers "not trashed", until the console restarts and restores the row from the record. The flash the member sees says "Nothing was deleted", which is false in this case; correct copy needs approval (A).
@@ -71,7 +71,7 @@ Each is concrete. None is waived.
19c. **S3's conditional delete is unverified on a real backend.** The store walks and the app purge delete a record only as the generation they read. That has run against the directory store and an HTTP stub. No `If-Match` delete has been sent to AWS or to an S3-compatible store, and a backend that ignored the header would delete unconditionally. It needs one write to a scratch object, which is a production-adjacent write nobody has approved.
19d. **The signing-time fence has been observed on AWS only.** An S3-compatible deployment or store endpoint has not been probed for its window.
19a. **A retained teardown finishes only when someone runs it again.** Nothing runs it. A store that left a bucket to another account keeps its record until then.
-20. **Amadan CI has not run. External blocker.** The launch branch and the three side branches are on the forge. CI has been "queued" on every tip pushed tonight and has never started; the last check was on the tip named in `.claude/HANDOFF.md`, and an agent account can neither see nor manage the runners. The local gate (build, vet, gofmt, `make test`, cross builds) is not CI and is not offered in its place: a person has to get a runner to take the branch. The hub's `carlos/platform` repository also answered 500 on and off through the night; bundles of every tip are in the run directory.
+20. **Amadan CI has run once, on `1395f7bf`, and passed.** It sat queued on every tip all night and first ran at 07:34 Dublin on 2026-10-01. Any later tip needs its own run; the local gate (build, vet, gofmt, `make test`, cross builds) is not CI and is not offered in its place. An agent account can neither see nor manage the runners. The hub's `carlos/platform` repository also answered 500 on and off through the night; bundles of every tip are in the run directory.
21. **A scanner backend that calls a provider must be fenced like the walks.** Today's scanner (ClamAV) makes no provider call at teardown. A native one would, and its client would need the fenced signer.
## Approvals to ask for, each with its exact scope