carlos / platform Public

Review twenty-three's two reports in the tree

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev e152df30cefc5bf70742b4078e8f0e0d99ac4bb0 parent ef8d944
2 files changed, +273 −0
  • docs/superpowers/specs/CODEX-FINDINGS-multi-currency-review23.md +170 −0
  • docs/superpowers/specs/OPUS-REVIEW-multi-currency-review23.md +103 −0

CI passed — run details

diff --git a/docs/superpowers/specs/CODEX-FINDINGS-multi-currency-review23.md b/docs/superpowers/specs/CODEX-FINDINGS-multi-currency-review23.md
new file mode 100644
index 000000000..2092779b0
--- /dev/null
+++ b/docs/superpowers/specs/CODEX-FINDINGS-multi-currency-review23.md
@@ -0,0 +1,170 @@
+All platform references below are at **`2618403f`**; pricing-site references are at **`01b62f7`**. This was source-only review against `12e70d95`, including the delta from `05ccec07`. Nothing was modified or tested, and no network commands were run. Mutation counterfactuals below are judgments from source.
+
+**Twenty-second review, finding by finding**
+
+| Finding | Closed? | Would its test pass without its fix? |
+|---|---|---|
+| **Opus P2-1:** a dead attached subscription can leave an honest buyer held indefinitely | **Addressed honestly as a conservative stage-one limitation and enablement gate, not eliminated.** `billingretirement.go:97` and dispositions `:484`, `:500` explicitly say there is no stage-one disposition. | **Yes: behavior is unchanged.** There is no new behavioral regression proving a status-based release, nor should one be claimed. |
+| **Opus P3-1:** unready cleanup is another no-archive exception | **Closed as a documentation finding.** Both ordinary expiry and unready cleanup are named at `billingretirement.go:91` and retirement design `:126`. | Not applicable: the accepted response documents the exception. |
+| **Opus P3-2:** subscription attribution follows incorrect or missing account metadata | **Closed.** `accountOfIntentTx`, `billingretirement.go:765`, finds the console’s recorded owner before classification. | **No.** `billingnamed_test.go:888` requires attachment for both missing and incorrect account metadata. |
+| **Opus P3-3:** the pass reports a hold it caused as a failure | **Closed at both sites**, `billingpass.go:267` and `:297`. | **No with the whole fix removed.** The named test at `billingnamed_test.go:912` exercises the bound-session branch. Removing only the recovery filter at `billingpass.go:297` is not distinguished by that test. |
+| **Opus P3-4:** inconsistent dispute key for consumption without a session | **Closed.** `billingretirement.go:660` uses the intent ID, matching consumption. | **No.** `billingnamed_test.go:938` distinguishes it. This remains a defensive store case: production reconciliation requires a bound session. |
+| **Opus claim 1:** only ordinary expiry lacks an archive | **Corrected**, as above. | Not applicable. |
+| **Opus claim 2:** the stranded buyer “waits on a person” | **Corrected:** the buyer is held with no available stage-one disposition. | Not applicable. |
+| **Opus claim 3:** the ended-subscription regression establishes ordinary provider behavior | **Corrected.** Dispositions `:473` calls it defensive and identifies the fake’s period change. | The regression fails without the consumption fix, but does not establish that Stripe produces its setup. |
+| **Astra 1, P2:** retirement ignores open checkout refusals/disputes | **Closed for an already-open case at reservation. The complete retirement boundary remains incomplete:** Finding 1 below. `billingstore.go:1514` adds the transactional check. | **No.** `billingnamed_test.go:711` fails without it. It passes with the remaining post-reservation race. |
+| **Astra 2, P2:** a session-only refusal filed after consumption disappears | **Closed.** `billingstore.go:1256` preserves the refusal against the consumed purchase. | **No.** `billingnamed_test.go:758` requires the dispute and open review. It directly exercises delayed filing, rather than orchestrating two consoles. |
+| **Astra 3, P2:** a click discloses a link despite freshly observed review | **Closed for the reported cases.** Both post-read checks and the newly saved link check include `Open`: `billingservice.go:653`, `:715`, `:781`. | **No.** The three cases at `billingnamed_test.go:791` distinguish the checks independently. The first changes country and asserts zero expiries, avoiding the previous masking by the second check. |
+| **Astra 4, P3:** duplicate-page failure discards decoded observations | **Closed for duplicate pagination. A decoding-error variant remains:** Finding 2 below. | **No for the reported duplicate case.** `billingnamed_test.go:868` and `billingstripe/catalog_test.go:333` distinguish that fix; both pass with Finding 2. |
+
+For **Opus P2-1**, the disposition is sufficient for conservative **stage one**, provided it remains an enablement gate. Reading an attached subscription’s status is unnecessary merely to continue holding it. Automatically exempting `incomplete_expired` would introduce a settlement policy that stage one deliberately withholds. Astra’s test-mode item 8 means exactly that status alone cannot establish the disposition of payments, invoices, pending collection or refunds. If rehearsal demonstrates the problematic honest-buyer sequence, enabling requires the owner’s settlement decision and an implementation checking its evidence—not just a status exception.
+
+On severity, Astra’s P2 reading was right for losing outstanding obligations, retiring their account, or offering payment after observing an open refusal. Opus’s conclusion that the deletion/consumption checks closed the whole lifecycle family was too broad. The pass’s one-time diagnostic failure and the unreachable empty-session archive case are appropriately P3.
+
+**Findings, most severe first**
+
+1. **P2 — A dispute recorded after retirement reservation does not stop the authoritative account tombstone.**
+
+ **Where:** `internal/console/billingstore.go:1527`, `:1539`; `internal/console/ownership.go:151`, `:172`, `:194`; `internal/console/billingretirement.go:653`, `:737`.
+
+ **Concrete scenario:**
+
+ - Legitimate subscription C records, consuming intent I. C later ends.
+ - Account deletion passes `reserveBillingRetirement`; its transaction commits.
+ - Before the bucket write, another console reads subscription H naming archived I. Classification **commits** a checkout dispute.
+ - The deletion request still calls `tombstoneAccount`.
+ - Only afterwards does `DeleteAccount` rerun `checkBillingRetirement` and refuse.
+
+ This is not the acknowledged boundary between receiving provider data and recording it: the dispute has already committed before external retirement.
+
+ The external effect is real. `internal/domains/mint.go:248` clears the manifest’s owner and sets `DeletedAt`; `:254` publishes it. `internal/domains/reconcile.go:601` subsequently removes the registry account.
+
+ The local account and evidence remain because `DeleteAccount` refuses. However, the authoritative manifest says the account is retired. The reservation remains, retries now fail the review check, and new checkout is blocked by `billingstore.go:363`. Stage one cannot resolve the dispute that prevents completion.
+
+ **Smallest acceptable fix:** coordinate authoritative retirement authorization with evidence recording across consoles. A pending retirement must be cancellable by evidence committed before finalization, or the final check and publication must have a shared serialization boundary. Another standalone preflight check merely narrows the race. Preserve evidence and operator access when retirement has already progressed.
+
+ Add a regression that pauses **after successful reservation and before tombstoning**, records H through the production classification path, then resumes deletion. The current test at `billingnamed_test.go:711` checks only the opposite ordering and passes with this defect.
+
+2. **P3 — A malformed session discards valid sessions already decoded from the same listing page.**
+
+ **Where:** `internal/billingstripe/catalog.go:152`, `:161`; `internal/billingstripe/client.go:240`, `:351`; recording wrapper at `internal/console/billingretirement.go:858`.
+
+ **Concrete scenario:**
+
+ - A session S was retired on proven expiry.
+ - A listing response is valid JSON containing a fully valid S, now `complete`, followed by `{"id":"cs_bad"}` without `livemode`.
+ - JSON decoding successfully populates S before `Session.UnmarshalJSON` rejects the second object.
+ - `c.do` returns `CodeDecodeError`.
+ - `listCheckouts` returns only `out`, containing previous pages, and discards the successfully decoded S from this page.
+ - The recording wrapper never receives the contradictory completion, so no dispute is filed.
+
+ The new loop correctly preserves duplicates **after successful page decoding**. It does not handle this earlier return.
+
+ **Smallest acceptable fix:** decode the page envelope into raw session elements, validate elements independently, and return fully validated observations alongside the page’s error. Preserve valid elements before and after a malformed element; never classify a partially decoded object. Recovery must still fail.
+
+ Add client and wrapper cases containing a contradictory archived session and a malformed neighboring element. The new duplicate-page tests pass without this additional protection.
+
+**Claims the code contradicts, and the remaining requested checks**
+
+The broad account-retirement and listing-evidence claims still need qualification because of Findings 1 and 2. Other concrete prose discrepancies remain:
+
+- `billingstore.go:1217` says a late gone-intent refusal files nothing; the new branch at `:1256` deliberately files a dispute after consumption. The binder’s “only while that intent stands” comment at `billingservice.go:1284` needs the same qualification.
+- `billingnamed_test.go:763` describes a session read before consumption. The test calls `purchased` first and reads the session at `:766` afterwards. It establishes delayed-filing handling, not that complete read/record interleaving.
+- `billingservice.go:790` calls unready deletion preparation unconditional. The production cleanup respects reviews and recorded named subscriptions.
+- `billingretirement.go:965` says retries are at least an hour apart and reach every case within “a few passes however many.” The schedule at `:797` begins at one, five and fifteen minutes; the three-attempt budget cannot promise a fixed small number of passes for an arbitrary backlog.
+- Retirement design, “Stage one as built,” `:116` still broadly says a subscription naming an unbound intent files a review. An on-terms one retries at `billingservice.go:1009`, with its obligation retained.
+- That design’s create-answer exclusion at `:115` still omits the completed-refused-create exception documented at `billingretirement.go:82`. Its subscription-attribution description also needs to reflect the recorded intent owner taking precedence over account metadata.
+
+The following conclusions cover the requested pricing, ownership, migration and lifecycle checks.
+
+| Area | Source conclusion |
+|---|---|
+| **A: GBP purchase end to end** | India selects band D independently of GBP. Personal renders £7.70 annually. The handler compares the effective posted choice with the authenticated quote (`billinghandlers.go:71`); checkout pins GBP/book (`billingservice.go:603`) and sends fixed-currency parameters (`:221`). Account and assignment storage preserve them (`billingstore.go:681`, `:848`). Monthly allowance rejects mixed price terms (`plans/month.go:167`); calculation produces GBP version 2 (`billingcalculation.go:174`). Invoice creation, recovery and verification retain GBP (`billinginvoice.go:516`, `:569`, `:901`, `:964`, `:1004`, `:1116`), as does the quantity worker (`billingquantity.go:1017`, `:1105`). I found no conversion or silent EUR substitution in this trace. |
+| **B, M: one form and effective choice** | Present currency must be exactly one supported value. Empty, repeated, uppercase and unsupported fields fail (`billinghandlers.go:176`). Absent currency supplies no explicit choice; against a book console the country default must still match the signed quote. Changing country/currency, erasing the choice flag or fabricating quote fields cannot authorize an unseen quote. A stale tab can submit its own still-valid quote; another tab’s cookies do not change its posted purchase. |
+| **Quote door** | Session, form token and membership precede a redirect constructed to the account’s billing page. Posted values are URL-encoded (`billinghandlers.go:165`, `:197`). This door creates no checkout and cannot choose an external redirect destination. |
+| **Q, U, Z: signature** | The key derives from the server’s billing secret; the disclosed CSRF token is message material, not the key (`billingpage.go:442`). The signature covers token, account, book, country, band, currency, choice flag and member count. Another session/account/book/token invalidates it. The page quotes all offered plans; charged quantity derives from membership and plan minimum. Postal information affects location/tax rather than the quoted pre-tax book price. Membership is checked again before charging and transactionally when the intent is created. |
+| **C, O: cookies and held currency** | Present-empty clears and suppresses fallback; absent falls back; invalid changes no cookie (`billingcurrency.go:43`). Consequently, an invalid parameter can leave an old cookie available on a later parameter-absent visit. That is the documented rule, not an implicit checkout choice. Held currency is offered alone; legacy purchases hold EUR. Service and transactional intent creation enforce it even after cancellation. |
+| **E, N: assignment-span trigger** | `billingstore.go:208` uses positive overlap ending at the earliest of the next assignment, paid-through limit and month end, matching `AllowanceForMonth` (`plans/month.go:141`). It considers both assignment tables and same-second revision ordering. Same-second supersession contributes zero span; inactive/free rows contribute no paid book span. Renewal and quantity writers preserve provider `period_end` as the paid-through limit, distinct from the revision’s effective date (`billingstore.go:845`). I found neither requested valid-history counterexample. |
+| **H, T: migration and rollback** | The refined trigger exists before `retireEarlierBarrierTrigger` verifies it and drops the old name transactionally (`billingstore.go:1002`). Missing new trigger leaves the old one; repeated startup is idempotent. An older prohibited build can recreate the old trigger name. The rollout prohibition remains necessary: these barriers do not make an old binary understand book history or the complete retirement protocol. |
+| **F: inclusions** | The page divides `PriceFor`’s pooled allowance by charged quantity. Unavailable Personal uses a valid Personal quantity for its disabled row. Current terms yield exact displayed shares, including teams above minimum; the tests check all factors and band-A wording (`billingcurrency_test.go:2554`). Removing an exactness assertion over already-exact data need not fail; changing the data to violate it does. |
+| **G, J: book, catalogs and export** | Validation checks strict shape, complete dimensions, duplicate keys, rounding, overflow and legacy-EUR agreement (`pricebook.go:149`). Configuration accepts the required catalog plus whole optional catalogs, with unique provider IDs (`cmd/console/billing.go:106`). Provisioning and console agree on own-only expanded `currency_options`. The export contains public commercial/resource/country data, not credentials or provider catalog identities (`pricebook.go:727`). |
+| **K: pricing site** | At `01b62f7`, rates retain exact micro amounts; inclusions use exported factors and exactness checks (`copy-review/pricing/build.mjs:47`, `:113`, `:156`). Draft rendering/checking uses the first-line marker and exact `"1"` override. Country choices/defaults come from the book. Links always carry both parameters, with currency only when chosen (`src/site.js:40`, `:75`, `:89`). A locale-only suggestion may differ from the console’s later country default because the suggestion is intentionally not transmitted as a choice. |
+| **MM, QQ: site tests** | Slot values are checked across plans, bands and currencies; rates are parsed back to exported micro amounts. Whole-token checks cover approved numeric literals. A hardcoded value exactly equal to the expected current value still passes an output-equality test; the test explicitly acknowledges that limitation (`pricing-pricebook.test.mjs:202`). `hack/money.mjs` supplies the production formatter exercised by the fractional-minor test. |
+| **D, L, R, Y, DD–NN: session ownership** | `ownSession` checks mode, intent, both account identifiers, known customer and bound ID (`billingservice.go:273`). Purchase checks follow ownership. Expected-intent binding checks its recorded account before deriving an account from the response (`:1204`). Create/list recovery cannot authorize another account’s session. |
+| **Bound-ID expiry exception** | A previously bound session with contradictory identity is reviewed first, then its **recorded bound ID** may be expired (`billingservice.go:310`; `billingretirement.go:914`). That is distinct from accepting its contradictory response as proof. The response cannot retire the intent. This is the justified exception to refusing expiry of an unowned create/listing result; leaving a disclosed payable bound URL open solely because metadata changed would be the worse authority choice. |
+| **Reconcile versus binder** | A first purchase must satisfy matching session purchase identity and its own subscription association (`billingservice.go:997`, `:1038`, `:1054`). Complete-with-no-subscription and pending payment retry. Renewals do not undergo first-purchase intent consumption. Refused matching-intent subscriptions are retained before rejection. Unknown terms outside the bound-first-purchase case remain retryable; an open review/disputed record can also continue returning conflict/503 under the explicitly conservative policy. |
+| **X, AA, CC: quantity history** | `quantityOutcomesSQL` supplies the processing guard, startup detection and obligation check (`billingquantityaudit.go:20`, `:42`, `:61`). The healing sweep is gone; startup does not clear pre-history or clock-rollback gaps. |
+| **Z, BB, YY, ZZ, BBB, CCC: operator records** | References and resolution outcomes are retained. Historical omitted outcomes remain `retire`; exact replay compares actor, note and outcome. The rejected listing is operator-only, newest first and bounded to 200. It drains rows before account lookups (`billingoperator.go:290`). It is not a universal ledger of interactive errors or retrying events. The added migration and ordering tests establish their fixtures, not every concurrent execution. |
+
+The lifecycle invariant supported by the code is: **durably classified named subscriptions survive intent consumption as accepted-purchase identity or disputes; expiry and cleanup preserve an intent with recorded obligations or an open refusal; operator retirement checks the recorded proof and obligations transactionally.** It is not yet a complete invariant across authoritative account retirement or all decoded listing evidence.
+
+There are four production intent-delete statements and one binding helper:
+
+| Door | Effect on previously recorded evidence and later observations |
+|---|---|
+| **First-purchase record — `billingstore.go:890`** | Exact-intent deletion, account/assignment changes, event, entitlement queue, consumption archive and disputes for other subscriptions commit together. Failure rolls everything back. Later subscription observations compare with that archive. Same-subscription renewal/cancellation consumes no intent. |
+| **Ordinary expiry — `billingstore.go:553`** | Reached through pass, click and refused-session expiry, including unbound refused creation. Proof is checked first. Known naming subscriptions cause a refusal and preserve the intent; open review/dispute blocks deletion. |
+| **Unready cleanup — `billingstore.go:604`** | Reached through pass, replacement click and account-deletion preparation. `customer_ready=0` plus no session distinguishes never-created checkout. Recorded subscriptions or open review prevent cleanup. |
+| **Operator retire — `billingstore.go:1203`** | Resolves and archives exactly the reviewed intent, with proof, outcome and quantity-event disposition, in one transaction. Protocol-zero reviews, disputes and attached subscriptions remain held. |
+| **Session binding — `billingstore.go:427`** | Both binding callers use this helper. It refuses another purchase’s session and a session conflicting with an open review reference. Same-session rebinding and lost-answer recovery remain possible. |
+| **Account retirement** | No intent cascade exists; local soft deletion and reaping check billing holds. The reservation-to-tombstone interval remains defective as Finding 1 describes. |
+| **Webhook settlement** | Required classification precedes acknowledgement. Evidence and rejected/seen-event recording use separate transactions; a crash between them leaves the event retryable. Archived identity paths retain attribution. Neither acknowledged rejection nor event deduplication independently resolves a review. |
+
+For observations committed before expiry or cleanup, the transaction ordering retains them. If deletion wins first, the explicitly documented exception applies: **ordinary expiry or unready cleanup with no previously recorded naming subscription leaves no archive**, and a subsequently observed subscription can become only a rejected event. Consumption and operator retirement have archives and do not share that exception.
+
+`billing_consumed_intents` is written when an intent is actually consumed. Generated intent IDs make ordinary replacement of an existing archive unnecessary; deliberate historical ID reuse is not a supported lifecycle. The account’s recorded subscription and the archive’s accepted subscription exclude ordinary renewal, cancellation and redelivery from becoming disputes. The empty-session fallback is now consistent.
+
+For **stage-one retirement**:
+
+- Fate is written once; contradictory terminal/identity evidence creates a durable dispute.
+- Review protocol, attachments, disputes and fate are checked inside the resolution transaction. No caller’s stale generation authorizes release.
+- `keep` requires the bound, owned, matching, completed and acceptably paid session. It does not fully validate the subscription’s terms; subsequent reconciliation can reopen the refusal, as the governing “as built” section states.
+- A kept intent receiving another subscription is held again; consumption disputes additional recorded subscriptions. Late session refusals now survive ordinary consumption.
+- A dispute has no stage-one resolution. An old client omitting outcome cannot bypass that rule.
+- Expiry failure or process death after filing leaves the review authoritative. `advanceRefusal` retries the pinned bound session and obtains proof through reads; a winning completion remains held.
+- The refusal turn has its own 45-second deadline, three-attempt budget and longest-due ordering. Both turns inherit caller cancellation. This avoids the reported fixed-order starvation, without promising bounded completion for an arbitrary workload.
+- Trigger recreation is transactional. A failed startup retains the previous committed trigger. The trigger alone does not make an older binary obey attachment and account-retirement rules.
+
+For **MMM–SSS**, the inspected production checkout/subscription reads use `readCheckout`, `listCheckouts` and `readSubscription`: start/resume, recovery, binder, reconciliation, pass, keep gate, refusal advancement, quantity worker and invoice checks. Store failure returns no usable object. The meaningful pre-classification returns are provider errors, requested-ID/mode mismatches and the explicitly excluded unidentifiable/no-refusal states; Finding 2 shows provider decoding errors can conceal successfully decoded neighboring observations.
+
+The structural test at `billingevidence_test.go:544` catches selector uses through aliases, method values, method expressions, interfaces and same-named methods on another type. Reflection also requires classification of every exported provider-client method. Its recording-order check is lexical, not control-flow proof; an unreachable recording call could satisfy it. A differently named read on an unrelated client remains outside that inventory.
+
+For **I, P, LLL, RRR, ZZZ2 and JJJJ**, the source supports the targeted counterfactual tests, with these limits:
+
+- The four previously surviving round-20 mutations now have distinguishing cases: individual identity fields, an incomplete additional subscription reopening after `keep`, the refusal’s sole read recording fate, and contradiction only on the second resume read.
+- The refusal-attachment rollback test injects failure inside the production helper, rather than merely testing an unrelated SQLite transaction.
+- The fake’s distinct second session and retired-completion replay assertions distinguish recording a retired purchase.
+- The historical-retirement helper recreates material pre-protocol state; it is not permission for current code to produce it.
+- Review-three/four qualifications matter: provisioning assertions inspect shell text; successful migration does not demonstrate every crash ordering; account-only deletion can be redundant behind an exact-intent lookup in the same transaction; removing an exactness assertion over exact data is not a distinguishing mutation.
+- The new late-filing test and other store-sequenced tests establish specific boundaries, not two concurrently running consoles. I cannot certify the claimed executed mutation counts or exact transformations from their descriptions.
+- I found no new shared-cookie-jar, temporary-root or calendar-dependent assertion defect. Fixture timestamps and provider hooks do not establish real provider ordering.
+
+For **BBBB and LLLL**, I found no additional regression relative to `b260ac8e` in an honest-provider sequence established without the disputed early-subscription/replacement assumptions:
+
+| Sequence | Result |
+|---|---|
+| Own subscription arrives before the session names it | Retained; reconciliation retries, then records once association/payment checks pass. |
+| Duplicate delivery | Idempotent attachment/event handling; no second subscription obligation. |
+| Payment retries on the same session and subscription | Pending until acceptable payment and identity; then ordinary recording. |
+| Abandonment leaves a named subscription and the session expires | Held without a stage-one disposition, even if the subscription later reads `incomplete_expired`. |
+| Provider replaces one subscription with another for the intent | The extra recorded ID becomes a dispute when the accepted purchase records. |
+| Renewal/cancellation after recording | Does not consume a newer intent or dispute the purchase merely for being a later event. |
+
+Whether the problematic honest sequences occur, and what their financial consequences are, remains a provider rehearsal and settlement-policy question.
+
+**What must be verified against Stripe test mode before enablement**
+
+1. **All 36 catalog identities:** exact integer amounts, currencies, metadata, tax behavior and expanded own-only `currency_options`. Console and provisioning checks must accept the same legitimate shapes and reject mutations.
+2. **One-form purchases:** India plus explicit GBP, country-default choices, cleared suggestions and held legacy EUR. Confirm displayed base price, checkout currency, disabled Adaptive Pricing, subscription/item currency and stored identity.
+3. **Payment/event ordering:** subscription before completion, completion before subscription attachment, duplicate delivery, declined/retried cards, abandoned 3-D Secure and delayed payment methods. Establish how many subscription IDs Stripe creates and when.
+4. **Financial obligations:** determine what `incomplete_expired` and cancellation leave outstanding—payments, collectible invoices, pending collection and refunds. No status alone is settlement. Apply the owner’s ruling before enabling any release that depends on it.
+5. **Expiry versus payment:** retained links, expiry failure/timeouts, payment racing expiry and subsequent GET visibility. Proven expiry must prevent further payment; winning completion must remain accounted for.
+6. **Identity and idempotency:** GET/list/create/expiry fields, metadata correction, concurrent identical keys, lost responses, remembered errors and key-retention expiry. Confirm session IDs are not recycled.
+7. **Lost-create recovery:** original-key replay and customer-scoped pagination, including failures. Recovery must offer no link from a failed listing while retaining attributable evidence.
+8. **GBP overages and quantity:** creation, lost-response recovery, item/final-line verification, finalization/payment and quantity/proration updates must preserve GBP and correct allowance dating.
+9. **Legacy/customer reuse:** unchanged legacy EUR request shapes and hashes, renewal/cancellation, subsequent purchases in the held currency, and unchanged historical invoices.
+10. **Ended subscriptions:** establish whether fields other than metadata can change after cancellation, separating the defensive regression fixture from actual provider behavior.
+
+**MMMM:** acceptance still requires the two code fixes above. What remains is therefore **not only** the listed decisions, rehearsal and owner approvals.
+
+**Verdict: reject.**
\ No newline at end of file
diff --git a/docs/superpowers/specs/OPUS-REVIEW-multi-currency-review23.md b/docs/superpowers/specs/OPUS-REVIEW-multi-currency-review23.md
new file mode 100644
index 000000000..51fc0e456
--- /dev/null
+++ b/docs/superpowers/specs/OPUS-REVIEW-multi-currency-review23.md
@@ -0,0 +1,103 @@
+**Verdict: accept with fixes.** Every round-twenty-two finding is closed or honestly stated, and each new test fails without its fix. I found no P1 or P2. What remains is three P3s and three claims to correct; none needs a code change before the round is accepted. On that basis I would count review eighteen's P2 as closed at `2618403f`.
+
+This was source-only, read with `git show 2618403f:`. I modified nothing and ran nothing. The delta (`05ccec07..2618403f`) touches no pricing, form, quote, cookie, allowance-trigger, export or site code, so last round's conclusions on A–K, L–ZZZ2 and the site at `01b62f7` stand.
+
+## Round twenty-two, finding by finding
+
+| Finding | Closed? | Would its test pass without the fix? |
+|---|---|---|
+| **Astra 1 (P2)**: an account could be retired under an open checkout dispute or refusal | **Closed.** `checkBillingRetirement` (`billingstore.go:1504-1521`) now refuses on an open `checkout_refused` or `checkout_fate_disputed` review. It runs in both the reservation (`:1533`) and `DeleteAccount` (`ownership.go:731`). | **No.** In `TestAnAccountIsNotRetiredUnderAnOpenCheckoutDisputeOrRefusal` (`billingnamed_test.go:711`), the old query would reserve in both held cases. The test also has a control. |
+| **Astra 2 (P2)**: a session refusal filed after another console consumed the intent was lost | **Closed.** The gone-intent branch now files a dispute against `billing_consumed_intents` (`billingstore.go:1256-1270`). | **No.** Without the fix the filing classifies an empty subscription, `purchaseOfSessionTx` never searches consumed purchases, and the count is 0. The test drives `fileRefusal` directly, not two consoles, as the dispositions say. |
+| **Astra 3 (P2)**: a click could return a pay link after a review opened mid-read | **Closed.** `st.Open \|\| st.Disputed` is checked after the first read (`billingservice.go:653`), after the second (`:717`), and before a new link is disclosed (`:780-785`). An undisclosed new session is still expired by the pass: `pinned()` prefers `intent.SessionID` (`billingretirement.go:255`). | **No.** First read: without the check the click goes on to expire the session, and the test asserts no expiry was requested and gets the wrong error. Second read and a new session: the URL is returned. |
+| **Astra 4 (P3)**: a broken listing page dropped the repeat and what followed it | **Closed** (`catalog.go:164-182`). Entries with no usable id are skipped, since nothing can be recorded without an id. On error the wrapper still hands the caller nothing. | **No**, for both the client test and `TestABrokenListingStillRecordsBothAnswers`. |
+| **Opus P2-1**: the dead subscription | Answered as a gate and a decision; see the next section. | No test, correctly: nothing changed in code. |
+| **Opus P3-1**: the unstated cleanup exception | **Closed.** It is now named in the header (`billingretirement.go:91-95`) and in the design. | n/a |
+| **Opus P3-2**: a subscription classified under its metadata's account | **Closed.** `accountOfIntentTx` (`billingretirement.go:762-782`) finds the account by intent id: standing, then consumed, then retired, then a resolution. The resolution's `subscription` column holds the intent id for refusals, as at `:639`. `readSubscription` records before reconcile's `AccountBySqid` can return. | **No.** Under both "no account" and "zzzz" the old code returned nil, and nothing was attached. |
+| **Opus P3-3**: the pass reported a hold it caused as a failure | **Closed at both sites** (`billingpass.go:267`, `:297-301`). | **No** for the `refuseSession` site. The recovery site has no test (P3-3 below). |
+| **Opus P3-4**: two keys for a dispute about a consumed intent with no session | **Closed in the classifier** (`billingretirement.go:660-664`), but the new filing uses a third key (P3-1 below). | **No.** |
+| **Opus claims 1–3** | All corrected: the exception is named twice, "held with no disposition" is at `DISPOSITIONS:484`, and the found defect is described as defensive. | n/a |
+
+**Where the reviewers disagreed on severity, Astra's reading was right on all three P2s:**
+- **Astra 1** released a case with an active hand-made subscription at the provider into an account no door reaches.
+- **Astra 3** offered a payable session that could not record, which takes money into a hold.
+- **Astra 2** is a P2 under the owner's "nothing observed is dropped" rule, though not on money: the purchase recorded on terms the other console verified.
+
+**My P2-1:** Astra listed it as residual, and it is a conditional P2. It needs provider behaviour that is not yet verified.
+
+## Opus P2-1 as a gate rather than code
+
+This is honest and sufficient for stage one:
+- Billing is off.
+- `ENABLEMENT.md` §6 step 2 (on the launch branch) makes "does Checkout ever create a Subscription before its session completes" the first check of the rehearsal, before anything else.
+- It says that if the answer is yes, decision 15 is needed and code must be built.
+- Reading an attached subscription's status in code now would be wrong.
+
+**Why reading status now would be wrong.** That is exactly what Astra's item 8 forbids: `incomplete_expired` or `canceled` is not settlement from status alone. The release would have to rest on a fresh read of the subscription's invoices and payments.
+
+**A correction to decision 15's proposal.** It proposes "a fresh read of the subscription and of its invoices showing none paid". That is too weak against item 8, because an invoice that is `open`, or whose payment is still `processing` (SEPA or Bacs), can still collect. The predicate should be: every invoice `void` (or `draft`), no succeeded or processing charge or PaymentIntent, and the subscription `incomplete_expired`. Anything else stays held.
+
+## Findings
+
+### P3-1. The late-refusal dispute uses a third key for an intent consumed with no session
+- **Where:** `billingstore.go:1263-1265` sets `usedSession = sessionID`. The record and the classifier (`billingretirement.go:660-664`) use the intent id.
+- **Scenario:** an intent is consumed with no session; no path does this today. A late session refusal then opens a dispute keyed by the session, and a later subscription opens a second one keyed by the intent. That is the same split P3-4 closed.
+- **Fix:** use `intent.ID`.
+
+### P3-2. Evidence first seen after an account is deleted lands where no operator looks
+- **Where:** `accountOfIntentTx` and `classifySubscriptionTx` do not check `accounts.deleted_at`.
+- **Scenario:**
+ 1. An account's purchase ended and the account was retired; the gate now passes only with no open checkout review.
+ 2. A hand-made H naming the consumed intent is read afterwards.
+ 3. A dispute is filed on the deleted account. Astra noted at `05ccec07` that the review listing excludes deleted accounts, and the resolution door answers 404.
+ 4. Reconcile then answers a bare identity refusal.
+- **Effect:** the row is durable but invisible. This is the only remaining door by which seen evidence ends as nothing but a rejected event, beyond the two stated exceptions.
+- **Fix:** list reviews of deleted accounts in the operator listing, or state the limit beside the two exceptions.
+
+### P3-3. The recovery site's filter of the hold it caused has no test
+- **Where:** `billingpass.go:297-301`.
+- **Effect:** removing that filter survives `TestThePassDoesNotFailOnAHoldItCaused`, which reaches only `:267`. The dispositions' "all nine die" covers one site.
+- **Fix:** add a case where recovery's refusal leaves a hold.
+
+## Claims the code contradicts
+
+1. **`billingservice.go:790-793`** says a stillborn intent "is discarded unconditionally". `discardUnreadyIntent` refuses under an open review or a named subscription. Astra raised this at `05ccec07` and it was not answered.
+2. **`billingretirement.go:965-966`** says a retried refusal is due again "an hour later at the earliest". The schedule (`:799`) is 60 s, 300 s, 900 s, then 3600 s. Also Astra's, also unanswered.
+3. **The dispositions' "Where" for Opus P2-1** cites `DECISIONS-FOR-PAUL.md`, which is not in `2618403f`. It is on the launch branch, as decision 15.
+4. **`ENABLEMENT.md` §6.2** says "if no, the rule costs an honest buyer nothing". That holds only if the provider also never names one intent from two subscriptions by any other means (test-mode items 5 and 6 below). Qualify it.
+
+## KKKK, LLLL and MMMM
+
+**KKKK, every door out of the protocol's sight:**
+
+| Door | Can seen evidence still end as nothing but a rejected event or a retired account? |
+|---|---|
+| Account retirement (reservation and `DeleteAccount`) | No longer while anything seen is open. After deletion, only through P3-2. |
+| The deletion's preparation | No: `discardUnreadyIntent` refuses under a review or an attachment. |
+| The operator's retire | No: it is held with anything attached; a later subscription is a dispute against the retirement archive. |
+| The record | No: the others seen become disputes; a late refusal is now one too. |
+| The click | No: it re-checks after each read and before disclosing a link. |
+| Recovery | No: partial and broken pages are recorded. |
+| The webhook's settled answers | No: each comes after `readSubscription` has recorded by intent. |
+
+The stated exceptions remain: an intent that expired in the ordinary way, and an unready intent cleaned up, each with nothing seen naming it.
+
+**LLLL, honest buyers.** Nothing in this delta is worse than at `b260ac8e` in any sequence the provider is known to produce:
+- The new retirement gate, the late-refusal dispute and the click's checks fire only on a review or a genuine disagreement.
+- A genuine disagreement needs a hand-made subscription, session metadata edited between reads, or two builds disagreeing during a roll. The roll rule covers the last.
+
+**MMMM.** Nothing in code is required before this round is accepted. The P3s and claims above are fixes to make, not gates. What remains is:
+- decision 15, with the corrected predicate;
+- the test-mode rehearsal and its gate;
+- the owner's approvals the dispositions list.
+
+## To verify against Stripe test mode before enabling
+
+Every earlier list stands. In order:
+1. **The gate:** in subscription mode, does Checkout create a Subscription before the session completes, and with what metadata? Run it with 3-D Secure abandoned, a declined card then another on the same session, and a delayed debit.
+2. **An abandoned `incomplete_expired` subscription:** what state are its invoices in, and does any charge or PaymentIntent exist? This is the input to decision 15.
+3. **SEPA or Bacs:** the session's status and payment status, and the subscription's, through a failed debit.
+4. **Event order:** `customer.subscription.created` against `checkout.session.completed`, and whether the session can read `open` once its subscription exists.
+5. **Metadata:** can a Checkout Session's or a Subscription's metadata be edited after creation, and by whom?
+6. **A canceled subscription:** can anything but its metadata change?
+
+**Verdict: accept with fixes** (P3-1 to P3-3 and the four claims; the gate and decision 15 before enabling).