diff --git a/outputs/VYNDR-COMPLETION-MATRIX.md b/outputs/VYNDR-COMPLETION-MATRIX.md index 32dffa9..3029d71 100644 --- a/outputs/VYNDR-COMPLETION-MATRIX.md +++ b/outputs/VYNDR-COMPLETION-MATRIX.md @@ -724,3 +724,21 @@ grades server-side, never infer resolution from time, fail closed to LIVE. 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. + +--- + +# BUILD 1 — THE SETTLED/LIVE GATE (shipped 2026-07-31) + +Unresolved = paid (Analyst+), resolved = free. Serving/gating only; `src/services/` untouched. + +Resolution is read **only from a written outcome** (never time or game status), `void`/`unrecoverable` +count as resolved, and `isResolved` **fails closed to LIVE** so a settle failure withholds rather than +exposes. Free gets **settled grades in full including reasoning** — the proof product — which also +turns the old unenforced board-reasoning leak into a deliberate rule. Live grades become a **shell**: +judgment stripped, real data kept (incl. `fair_odds`, which is never the paywall), `locked: true` +stamped. The tease is **aggregate only** (`live_locked {count, tiers}`) and no gated row carries a +grade, so nobody can tell which prop is the A. + +**Live anonymous fingerprint:** 25 grades → **5 settled free-full, 20 locked**, `live_locked +{"count":20,"tiers":{"B":13,"C":7}}`, **judgment-field leak: NONE**. 319 suites / 3,971 tests green, +web build exit 0. diff --git a/specs/STATE.md b/specs/STATE.md index 0c8165b..b3fabdd 100644 --- a/specs/STATE.md +++ b/specs/STATE.md @@ -1007,6 +1007,39 @@ > **CANNOT DETERMINE: whether the four Stripe price objects exist in the dashboard** (env not readable > here) — flagged as a prerequisite for build 3. +> ## 🔒 BUILD 1 SHIPPED 2026-07-31 — THE SETTLED/LIVE GATE (unresolved = paid, resolved = free) +> **Serving/gating change ONLY** — `src/services/` untouched (diff empty): no grade, model or +> settlement-logic change. Pricing = Build 2, migration = Build 3. +> **THE RULE:** a grade is **PAID while its outcome is unknown** and becomes **FREE the moment it +> resolves**. **Resolution is read ONLY from a written outcome** — never from time, game status or +> `gradedAt` (a game can be final long before the settle pass runs; treating "probably over" as +> settled is exactly how a live edge leaks — a test asserts an hours-old `gradedAt` with no outcome is +> still LIVE). `void`/`unrecoverable` **are** resolutions. **`isResolved` FAILS CLOSED** — null +> outcome, `{}` with no result, empty-string result all read LIVE — so a **settlement failure +> withholds rather than exposes**, the same direction `resolveTierFromRequest` fails. +> **FREE = SETTLED IN FULL** (reasoning + kill conditions included — settled is the proof product and +> costs nothing post-resolution). **This also converts the previously-UNENFORCED board-reasoning leak +> into a deliberate rule.** **LIVE = SHELL**: all model JUDGMENT dropped (`grade`, `confidence`, +> `confidence_basis`, `reasoning`, `kill_conditions_triggered`, `projection`, `edge_pct`, +> `matchup_grade`, `form`, `alt_lines`, `kelly`) + `locked: true`; the free-side DATA stays so the +> tease is REAL, not empty (player · market · line · `book_odds` · **`fair_odds` — the de-vigged fair +> number is the free hook and is NEVER the paywall** · season/last10 · archetype · gradedAt · history). +> **THE TEASE IS AGGREGATE ONLY** — `live_locked {count, tiers}` computed from the ungated rows and +> **never joined back to one**, and **no gated row carries a grade**, so a free viewer learns N reads +> exist and their shape **without being able to work out WHICH prop is the A**. +> **✅ FINGERPRINT — LIVE, ANONYMOUS, DECISIVE** (deploy boundary captured: attempts 1-2 no +> `live_locked`, attempt 3 yes): **25 grades → 5 SETTLED served free-full · 20 LIVE locked · +> `live_locked {"count":20,"tiers":{"B":13,"C":7}}`**. **LEAK CHECK: judgment fields on live rows = +> NONE ✓**, all 20 stamped `locked`. Live shell still carries real data (Natasha Cloud · assists · +> 6.5 · book −145 · **fair −123** · season 4.6). Settled row keeps full proof (grade B · reasoning ✓ · +> 2 kill conditions · edge −2.2 · `outcome {"result":"hit","actual":1}`). +> **FLOOR: 319 suites / 3,971 tests green (10 new), web build exit 0.** +> **⚠️ TEST NOTE:** the route-level supertest case was **deliberately removed** — it needs a live +> Redis and hangs on ioredis' reconnect timer in a single-suite local run (known, CLAUDE.md). The +> gate's contract is fully covered by pure tests; **the wire is verified against PROD anonymously** +> (above). **Chrome audit:** free board (settled full + locked shells + unlock prompt) and the +> entitled board (live full) both need a visual pass — the entitled half needs Kev's Desk session. + - **Redirect EXISTS + WIRED:** `closingCapture.buildCaptureRows`→`closing_captures` (append-only, provenance: captured_at/book/line_type/both-prices/missed_reason) via `intradayRefreshService:221` + internal endpoint; `ledgerService.attachClosingProb`→`closing_prob` (de-vigs both raw sides,