Read-only. Nothing changed.
FREE TIER, EXACTLY:
- Board /api/snapshot: NO count limit. The only gate is
stripModelPrice(grades, tier) at routes/snapshot.js:106-107 — no slice, no
volume branch. Live anonymous right now: MLB 5, WNBA 25 = the full board.
The "3 scans/day" cap rations the SCAN path only.
- Grade letter: fully visible on every tier (grade_visible: true). Anon also
receives confidence, edge_pct and VYNDR's own projection.
- Edge fields: correctly stripped. p_win/ev_pct/model_odds/value/takeable are
ALL absent from the anonymous payload, with model_price_locked stamped so the
card shows a lock teaser rather than an absent leg. This half works as designed.
THE HEADLINE — the two paths disagree on reasoning:
- Scan REDACTS it: tierGating.js lockReasoning + lockKillConditions +
tier_gated + upgrade hint, driven by free.reasoning_visible = false.
- Board SERVES IT IN FULL: snapshotGating MODEL_FIELDS is
[model_odds, p_win, ev_pct, value, takeable] — reasoning is not in the list.
Verified live anonymously: full reasoning.summary plus a kill condition WITH
its reason.
Intent: config/tiers.js declares free: { reasoning_visible: false } with the
comment "blurred — frontend renders tier-locked". One of the two paths does not
enforce the product's own declared line, so the evidence reads as oversight
rather than funnel — a funnel would be declared in config, not contradicted by
it. Flagged with the counterweight: board reasoning is good marketing and the
data layer is already free, so closing it is a monetization tightening (Kev's
call), not a fabrication fix.
FREE DATA IS A REAL AGGREGATOR, not just a limited graded view: schedule,
per-book lines, player stats, streaks, hot lists, team hubs, public record — all
public and uncapped (probed live).
PAID (config/tiers.js, checkout.js:4): analyst $14.99 / desk $44.99. Analyst is
unlimited reads; Desk differentiates on capability (alt ladder, Kelly, portfolio,
engine2). africa tier is defined but activation is blocked on a DB CHECK
constraint. api_access is false on every tier. book_odds/fair_odds deliberately
pass through on all tiers — the de-vigged fair number is the hook and is never
the paywall; only model_odds gates.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
6.4 KiB
VYNDR — TIER STRUCTURE PULL (report-only, 2026-07-31)
Real numbers from code + live anonymous prod probes. Nothing changed.
PHASE 1 — THE FREE TIER, EXACTLY
1. How many grades does free/anonymous see?
| path | limit | enforced where |
|---|---|---|
Board /api/snapshot/:sport |
NO LIMIT — the entire slate | routes/snapshot.js:106-107 — the only gate is stripModelPrice(grades, tier); there is no count cap, no slice, no tier branch on volume |
Scan /api/scan |
3 per day (scans_per_day: 3) |
config/tiers.js free block; anonymous callers fall back to the free bucket, IP-keyed |
Live, anonymous, right now: MLB 5 grades and WNBA 25 grades returned — the full board. So "5 free scans" rations the SCAN path only; the BOARD is unlimited and ungated on volume.
2. Does free see the GRADE LETTER? — YES, in full. grade_visible: true on every tier.
Anon payload carries grade: "B", confidence: 63, edge_pct: 10.8, and projection: 5.8
(VYNDR's own model number).
3. Does free get REASONING? — THE TWO PATHS DISAGREE. This is the headline.
| path | reasoning | evidence |
|---|---|---|
| Scan | REDACTED | utils/tierGating.js — if (!canAccess(tier,'reasoning_visible')) → lockReasoning() + lockKillConditions() + tier_gated: true + upgrade hint |
| Board | SERVED IN FULL | utils/snapshotGating.js MODEL_FIELDS = ['model_odds','p_win','ev_pct','value','takeable'] — reasoning is not in the list |
Live anonymous board payload: reasoning.summary = "Natasha Cloud is averaging 5.8 assists
over his last 5 games…" plus 1 kill condition WITH its reason. The scan path locks exactly
this content; the board hands it over.
4. Edge fields — CORRECTLY STRIPPED. p_win, ev_pct, model_odds, value, takeable
are ALL absent from the anonymous payload, and model_price_locked: true is stamped so the
card renders a lock teaser rather than an absent leg. This half of the gate works exactly as
designed.
PHASE 2 — THE FREE DATA EXPERIENCE
Every one of these is public, no auth, 200 live: schedule/:sport · streaks/:sport ·
hotlist/:sport · stats/player/:name · books/:sport (per-book lines) · gamelines/:sport ·
team/:abbr · accuracy · ticker.
So there IS a real free data-aggregator surface, not merely a limited view of the graded product: schedules, per-book line shopping, player season/last-10 stats, archetypes, streaks, hot lists, team hubs and the public model record are all free and uncapped. The graded product sits on top of that as the paid layer — except that, per Phase 1.3, the board currently gives away the explanation layer too.
PHASE 3 — THE PAID TIERS (as coded)
config/tiers.js — four tiers (africa is defined but activation is blocked on a DB CHECK
constraint, so it is not live):
| capability | free | africa (inactive) | analyst | desk |
|---|---|---|---|---|
| scans/day | 3 | 10 | ∞ | ∞ |
| grade_visible | ✅ | ✅ | ✅ | ✅ |
| reasoning_visible | ❌ | ✅ | ✅ | ✅ |
| kill_conditions_detail | ❌ (count only) | ✅ | ✅ | ✅ |
| alerts | ❌ | ❌ | ✅ | ✅ |
| clv_badge | ❌ | — | ✅ | ✅ |
| model_price (our own price) | ❌ | — | ✅ | ✅ |
| alt_line_ladder | ❌ | ❌ | ❌ | ✅ |
| kelly_sizing | ❌ | ❌ | ❌ | ✅ |
| portfolio / engine2 (LLM) | ❌ | ❌ | ❌ | ✅ |
| stat_dashboard | ✅ | ✅ | ✅ | ✅ |
| api_access | ❌ | ❌ | ❌ | ❌ (nobody) |
Prices as coded (web/src/lib/checkout.js:4): analyst $14.99 · desk $44.99.
Analyst is unlimited reads by design — Desk differentiates on capability (alt ladder, Kelly,
portfolio, engine2), not on rationing reads.
A deliberate, documented carve-out: book_odds / fair_odds pass through on every tier —
"the de-vigged fair number is the free-tier hook, and the fair leg is NEVER the paywall; only our
own price gates." Only model_odds is withheld.
PHASE 4 — THE HONEST PICTURE
8. Deliberate funnel, or oversight? — THE CONFIG SAYS OVERSIGHT.
config/tiers.js states the intent in one line: free: { reasoning_visible: false }, with the
comment "blurred — frontend renders tier-locked." The scan path implements exactly that. The
board path simply never got the same treatment — MODEL_FIELDS was written (Session 67) to
close the model-price leak and lists only the five price-derived fields.
The evidence points one way: the product has declared that free must not see reasoning, and one of two paths does not enforce it. I would not call that a funnel decision — a funnel would be declared in config, not contradicted by it. (It is also not a new leak: it has been serving this way, and the earlier honesty passes did not catch it because they hunted fabricated content, not under-gated real content.)
Worth weighing before "fixing" it: the board reasoning is genuinely good marketing, and the line-shopping/stats layer is already free. Closing it is a monetization tightening, which is a different decision from closing a fabrication. That is Kev's call, not a bug fix.
9. WHAT IS GIVEN AWAY vs SOLD TODAY
| FREE gets today | PAID adds | |
|---|---|---|
| Grades | Whole board, uncapped + letter + confidence + edge_pct + VYNDR's projection | — (nothing on volume) |
| Explanation | Full reasoning + kill-condition reasons ON THE BOARD; redacted on scan | Reasoning on the scan path; kill-condition detail |
| Prices | book + fair (de-vigged) — deliberate hook | model_odds (our price), CLV badge |
| Edge math | none (p_win/ev_pct/value/takeable all stripped) |
the edge fields |
| Scans | 3/day | ∞ |
| Desk-only | — | alt ladder, Kelly, portfolio, engine2 |
| Raw data | schedule, per-book lines, player stats, streaks, hot lists, team hubs, public record — all uncapped | — |
One-line summary: free already receives the entire graded board, the model's projection, and — on the board only — the model's full explanation; paid buys the price/edge math, unlimited scans, and Desk capabilities. The gate that is declared but not enforced is board reasoning.
TAGS
VERIFIED: every number above (config file/line + live anonymous probes). CANNOT DETERMINE:
/api/odds/:sport returned no status on probe (timeout/limiter) — not conclusive either way.
BLOCKED: none. Nothing changed.