report: Build 2 Review Zero — payment mechanism BLOCKED, no Stripe credentials

Nothing built. No Stripe object created or changed, no price logic touched.

STOPPED because there is no STRIPE_SECRET_KEY in this environment (.env holds
only ODDS/SUPABASE/INTERNAL keys). The order's standing floor requires
founder/standing/grandfather/race all verified server-side; none of that is
verifiable here, the standing price objects cannot be created, and the
concurrent-checkout race cannot be exercised. On a payment path the failure modes
are permanent and customer-facing — a race bug mis-prices a subscriber forever,
a grandfather bug overcharges one every month — so it must not ship unverified.

VERIFIED ANYWAY:
- Stripe IS live and FOUNDER price objects DO exist. /api/founders/count returns
  {available:true, claimed:0, total:100}, and routes/founders.js returns
  {available:false} whenever countFounderSeats() is null, which it is when
  !STRIPE_SECRET_KEY || founderPrices.length === 0. So available:true proves the
  secret key and at least one founder price ID are configured in prod, and
  claimed:0 is a real count rather than a fallback.
- THE COUNTER IS NOT A GATE. It is a cached (300s) READ, not a claim; founder
  pricing is gated by CODE + EXPIRY, not by the count, so anyone holding
  FOUNDER2026 gets the founder rate at any seat number and the cap is decorative.
  Two simultaneous checkouts at slot 99 would both read 99 and both get founder —
  there is no lock or unique constraint anywhere in the path.
- The gate reads users.tier via config/tiers.js reasoning_visible, so a
  successful subscription must set users.tier for Build 1's gate to open.

CANNOT DETERMINE: whether the STANDING price objects exist (env unreadable, and
getPriceId falls back SILENTLY to a PRICE_UNCONFIGURED sentinel, so a missing
standing object would not surface until the first post-cap checkout 400s in front
of a paying customer); whether the webhook writes users.tier on
checkout.session.completed.

DESIGN IS SETTLED for when it unblocks: a founder_slots table with a unique
constraint on (tier, slot_number) claimed before the Stripe call — the unique
index, not a count read, is what makes the race impossible; price selection from
the claim rather than a code, with the code+expiry bypass retired; grandfathering
by simply never calling Stripe price-migration on a founder sub;
founder-follows-upgrade by claiming on the target tier and releasing the slot on
cancellation; honest display that shows no number when the count is unavailable
(the existing route already sets that precedent).

PREREQUISITES, all needing Kev and none of them code: confirm/create the two
standing price objects and set STRIPE_PRICE_ANALYST / STRIPE_PRICE_DESK; confirm
the webhook sets users.tier; provide a Stripe test-mode key so the race,
grandfather and end-to-end unlock can be exercised rather than asserted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
This commit is contained in:
Kev
2026-07-31 08:30:32 -04:00
parent e8b15c705a
commit 14f3ce95b9
+81
View File
@@ -0,0 +1,81 @@
# BUILD 2 — REVIEW ZERO (report; mechanism NOT built)
2026-07-31. Nothing built, no Stripe object created or changed, no price logic touched.
## WHY THIS STOPPED
**I have no Stripe credentials in this environment** (`.env` holds ODDS/SUPABASE/INTERNAL keys —
**no `STRIPE_SECRET_KEY`**). The order's standing floor requires *"founder/standing/grandfather/race
all verified server-side"*. **I cannot verify any of them**, cannot create the standing price
objects the rollover needs, and cannot run the concurrent-checkout race test.
This is a payment path where the failure modes are **permanent and customer-facing**: a race bug
mis-prices a subscriber *forever* (slot 101 on a founder rate, or slot 99 on standing), and a
grandfather bug overcharges a founder *every month*. Shipping that unverified is the one place
"probably right" is not good enough. **So: findings, then stop.**
## WHAT I DID ESTABLISH (VERIFIED)
**0.1 — Stripe IS live and FOUNDER price objects DO exist.** Proven indirectly but soundly:
`GET /api/founders/count` returns **`{available:true, claimed:0, total:100}`**, and
`routes/founders.js` returns **`{available:false}`** whenever `countFounderSeats()` is null — which
it is when `!STRIPE_SECRET_KEY || founderPrices.length === 0`. **`available:true` therefore proves
both the secret key and at least one founder price ID are configured in prod**, and that `claimed:0`
is a REAL count, not a fallback. (That route is honest by construction — *"any failure → hide, never
fabricate"* — which is why it can be used as a probe at all.)
**STANDING objects: CANNOT DETERMINE.** Env is not readable from here, and `getPriceId` **falls back
silently**: a missing standing price yields the `PRICE_UNCONFIGURED` sentinel, which does not fail
until Stripe rejects the session. **So a missing standing object would not surface until the first
post-cap checkout 400s in front of a paying customer.** Verifying this is a 30-second check in the
Stripe dashboard and is **prerequisite #1**.
**0.3 — THE COUNTER TODAY IS NOT A GATE, AND CANNOT BECOME ONE WITHOUT NEW WORK.**
- It is a **read**, not a claim: `countFounderSeats` lists Stripe subscriptions and returns a number.
- It is **cached 300s** (`CACHE_KEY 'founders:count'`), so it is stale by construction.
- **Founder pricing is gated by a CODE + EXPIRY, not by the count** (`VALID_FOUNDER_CODES`,
`FOUNDER_EXPIRY 2026-12-31`) — so today **anyone holding `FOUNDER2026` gets the founder rate at
any seat number**, and the cap is decorative.
- **Two simultaneous checkouts at slot 99 would both read 99 and both get founder.** There is no
claim, no lock, no unique constraint anywhere in the path.
**0.2 — the entitlement the gate reads** is `config/tiers.js` capability `reasoning_visible`
(free false / analyst+ true), consumed by `snapshotGating.entitledToItemizedGrades` via
`resolveTierFromRequest`, which reads `users.tier`. **So a successful subscription must set
`users.tier`** for Build-1's gate to open. Whether the Stripe webhook currently writes that on
`checkout.session.completed` is **CANNOT DETERMINE without the webhook secret** — and it is
**prerequisite #2**, because a paid sub that does not flip `users.tier` sells access that never opens.
## WHAT I WOULD BUILD, ONCE UNBLOCKED (design is settled, so this is fast)
1. **An atomic slot claim, DB-backed** — a `founder_slots` table with a **unique constraint on
`(tier, slot_number)`**, claimed inside the checkout-session request *before* the Stripe call.
The unique index — not a count read — is what makes the race impossible: two concurrent claims
for slot 100 mean one INSERT wins and the other is rejected to standing. **The cached count must
be removed from the decision path entirely** and kept only for display.
2. **Price selection from the claim**, not from a code: claim succeeded → founder object; claim
rejected/cap full → standing object. **Retire the code+expiry bypass**, or it silently defeats
the cap.
3. **Grandfathering** is already native (a sub created against a founder price stays on it) — the
build rule is simply: **never call Stripe's price-update/migration on a founder subscription.**
4. **Founder-follows-upgrade**: on tier change, attempt an atomic claim on the *target* tier's
founder slots; success → founder object, failure → standing. Tie to continuous subscription by
**releasing the slot on cancellation** (that is what makes "break it → standing" true rather than
aspirational).
5. **Honest display**: real uncached count at render; **if the count cannot be served, show the
offer with no number** — the route already does exactly this, so follow its precedent.
6. **Beta framing** with no proven-edge claim, pointing at `/record` (shipped) as the honest
building record.
## PREREQUISITES (all need Kev — none are code)
1. **Confirm/create the two STANDING price objects** in Stripe ($24.99 analyst, $59.99 desk) and
set `STRIPE_PRICE_ANALYST` / `STRIPE_PRICE_DESK`.
2. **Confirm the webhook sets `users.tier`** on `checkout.session.completed` (else the gate never opens).
3. **A Stripe test-mode key** available to the build/verification environment, so the race,
grandfather and end-to-end unlock can actually be exercised rather than asserted.
## TAGS
VERIFIED: Stripe live + founder price objects exist (via the honest counter probe); the counter is a
cached read with no claim; founder pricing is code-gated not count-gated; the gate reads `users.tier`
via `reasoning_visible`. **CANNOT DETERMINE: whether standing price objects exist; whether the
webhook writes `users.tier`.** **BLOCKED: the entire mechanism — no Stripe credentials, so nothing on
the payment path can be verified, and it must not ship unverified.**