docs: tier redesign spec recorded (gate discriminator exists; counter is display-only; base is 3 users)

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 07:07:13 -04:00
parent 4a4a3428d8
commit dbc1416485
2 changed files with 58 additions and 0 deletions
+18
View File
@@ -706,3 +706,21 @@ Backend untouched. Three pure modules, unit-locked:
318 suites / 3,961 tests green, web build exit 0. **Mounting the modules into the live row components 318 suites / 3,961 tests green, web build exit 0. **Mounting the modules into the live row components
is a follow-up**; the visuals go to the Chrome audit. is a follow-up**; the visuals go to the Chrome audit.
---
# TIER REDESIGN SPEC — 2026-07-31 → `specs/tier-redesign-spec.md`
Option 2 (settled-free / live-paid) designed, not built. **FREE** = full data aggregator + the
**complete settled record** (letter, reasoning, edge, outcome — browsable) as the proof hook.
**ANALYST $14.99→$24.99** = tonight's live grades + reasoning + edge. **DESK $44.99→$59.99** = +
alt ladder, Kelly, portfolio, engine2.
**The gate already has its discriminator:** `/api/snapshot` merges per-grade `outcome` (live WNBA:
5 of 25 settled), so `outcome != null ⇒ free`, `null ⇒ paid`, with no new pipeline. Filter whole
grades server-side, never infer resolution from time, fail closed to LIVE.
**Two findings that reshape the plan:** (a) founder pricing is gated by **code + expiry, not seat
count** — the real counter (`/api/founders/count`, live 0/100) only displays and is cached 300s, so
making it a transactional gate is a genuine build; (b) the user base is **3 free, 0 paid**, so the
migration is a courtesy note, not a mass event — effort belongs on the settled-record proof surface.
+40
View File
@@ -967,6 +967,46 @@
> unlimited scans, and Desk capabilities. **The one gate that is declared but not enforced is board > unlimited scans, and Desk capabilities. **The one gate that is declared but not enforced is board
> reasoning** — which is exactly the decision the D1-close rationale mount is waiting on. > reasoning** — which is exactly the decision the D1-close rationale mount is waiting on.
> ## 🧾 TIER REDESIGN SPEC 2026-07-31 (report-first) → **`specs/tier-redesign-spec.md`**
> Option 2 (settled-free / live-paid) designed + sequenced. **Nothing built; no tier, price, gate or
> Stripe object changed.**
> **0.2 — THE LADDER IS HALF-EXPRESSIBLE TODAY, AND THE GATE IS THE WRONG THING.** `PRICE_MAP`
> already separates founder from standing objects (`STRIPE_PRICE_ANALYST[_FOUNDER]`,
> `STRIPE_PRICE_DESK[_FOUNDER]`), so **lifetime grandfathering is NATIVE** — a sub created against a
> founder price stays on it. **🔴 But founder access is gated by CODE + EXPIRY, not seat count**
> (`FOUNDER2026/VYNDR/BETONBLK/EARLYBIRD`, expiry 2026-12-31) — **anyone with a code gets founder
> pricing at ANY seat number.** A real Stripe-derived counter exists (`/api/founders/count`, **live
> `{claimed:0, total:100}`**) but only DISPLAYS, and is **cached 300s** — a cached read cannot enforce
> "slot 100 and 101 differ permanently." **Making the counter the GATE, uncached and transactional at
> checkout-session creation (plus retiring the code bypass), is a real build.**
> **0.3 — THE PAID→FREE FLIP POINT ALREADY EXISTS ON THE SERVED PAYLOAD.** Settlement writes
> `ledger_entries.outcome` + `settled_at`, and `/api/snapshot` already merges per-grade results —
> **live WNBA: 25 grades, 5 carrying `outcome:{result:'hit',actual:1}`.** So the discriminator
> (`outcome != null`) is present on the exact object to be gated. **No new pipeline needed.**
> **🔴 0.4 — THE MIGRATION IS NOT WHAT THE ORDER ASSUMES: `users` holds 3 users, ALL free, created
> Jun 12-19, and ZERO paid.** There is no warm mass base. The "founder launch to existing users" is a
> **courtesy note to 3 people**, and **the launch's real audience is people who have not signed up
> yet** — so effort belongs on the settled-record proof surface, not on migration machinery.
> **THE TABLE:** FREE = full data aggregator **+ the COMPLETE settled record** (letter · reasoning ·
> edge · outcome, browsable/filterable) — a complete product, not a demo, and the proof hook that
> sells. ANALYST **$14.99→$24.99** = tonight's LIVE grades + reasoning + edge, unlimited. DESK
> **$44.99→$59.99** = + alt ladder · Kelly · portfolio · engine2.
> **REASONING + GRADE + EDGE ARE ONE PAID UNIT WHILE LIVE** (the reasoning IS the grade's argument;
> splitting them sells a claim without its defence) **and all three go free together at resolution** —
> which also converts today's unenforced board-reasoning leak into a **deliberate rule**.
> **THE GATE:** `outcome == null` ⇒ LIVE ⇒ Analyst+ · `outcome != null` ⇒ SETTLED ⇒ everyone.
> **Filter whole grades server-side (not field-strips)** so a live grade cannot leak partially ·
> **never infer resolution from time or game status, only from a written outcome** (a game can be
> final before the settle pass runs) · **fail closed to LIVE** so a settle failure withholds rather
> than exposes · `void`/`unrecoverable` are terminal ⇒ free.
> **BUILD ORDER:** (1) the settled/live gate — depends on nothing new · (2) the free settled-record
> surface — **flagged as arguably shipping WITH (1), since gating live grades without it leaves free
> users with no graded content at all** · (3) Stripe ladder + transactional counter + grandfather rule
> + retire the code gate · (4) the founder note to the 3 · (5) pricing visuals (**already designed** in
> the package — no new design needed).
> **CANNOT DETERMINE: whether the four Stripe price objects exist in the dashboard** (env not readable
> here) — flagged as a prerequisite for build 3.
- **Redirect EXISTS + WIRED:** `closingCapture.buildCaptureRows``closing_captures` (append-only, - **Redirect EXISTS + WIRED:** `closingCapture.buildCaptureRows``closing_captures` (append-only,
provenance: captured_at/book/line_type/both-prices/missed_reason) via `intradayRefreshService:221` provenance: captured_at/book/line_type/both-prices/missed_reason) via `intradayRefreshService:221`
+ internal endpoint; `ledgerService.attachClosingProb``closing_prob` (de-vigs both raw sides, + internal endpoint; `ledgerService.attachClosingProb``closing_prob` (de-vigs both raw sides,