Forward model_price_locked to the hero — a paywall was wearing poison's copy
Live induction on the deployed landing page caught this; markup review and the unit suite both passed it. With the model leg stripped for anonymous visitors, LiveHeroProp forwarded book/fair/model/ev/quarantine to PriceTriplet but NOT model_price_locked, so deriveValueState fell through to the missing-model-price branch and the card rendered "MODEL READ WITHHELD" — the quarantine state, whose copy says we suppressed our own price because a leg is poisoned. Nothing was poisoned. The real reason was the paywall, and the two must never share a face: one says our data is untrustworthy, the other says you don't have this tier. Now forwarded, with a test asserting both the separation in deriveValueState and the forwarding at the call site. Tests 3559 passed / 291 suites, web build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
This commit is contained in:
@@ -261,6 +261,29 @@ describe('PriceTriplet component', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ── 6b. CONSUMERS MUST FORWARD THE LOCK FLAG ─────────────────────────────
|
||||
describe('a gated model leg never wears quarantine copy', () => {
|
||||
it('deriveValueState separates GATED from WITHHELD', () => {
|
||||
const gated = { book_odds: -153, fair_odds: -129, model_odds: null, ev_pct: null, model_price_locked: true };
|
||||
const withheld = { book_odds: -153, fair_odds: -129, model_odds: null, ev_pct: null };
|
||||
expect(vs.deriveValueState(gated)).toBe(vs.STATES.NO_VERDICT_LOCKED);
|
||||
expect(vs.deriveValueState(withheld)).toBe(vs.STATES.QUARANTINE);
|
||||
});
|
||||
|
||||
it('LiveHeroProp forwards model_price_locked (live bug: it did not)', () => {
|
||||
// Without this the landing hero rendered "MODEL READ WITHHELD" — poison's
|
||||
// copy — to every anonymous visitor, when the real reason was the paywall.
|
||||
const hero = read('web/src/components/LiveHeroProp.tsx');
|
||||
expect(hero).toMatch(/model_price_locked: data\.model_price_locked/);
|
||||
});
|
||||
|
||||
it('the grade adapter forwards it too', () => {
|
||||
const { buildPriceTriplet } = require('../../web/src/lib/gradeAdapter');
|
||||
const t = buildPriceTriplet({ book_odds: -153, fair_odds: -129, model_price_locked: true }).priceTriplet;
|
||||
expect(t.model_price_locked).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ── 7. THE SERVER GATE — fair is never the paywall ───────────────────────
|
||||
describe('free-tier gate strips the model price at the wire', () => {
|
||||
const { applyTierGating } = require('../../src/utils/tierGating');
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -39,6 +39,8 @@ type Hero = {
|
||||
value?: boolean | null;
|
||||
takeable?: boolean | null;
|
||||
quarantine_reason?: string | null;
|
||||
/** Free/anonymous: model leg stripped server-side (utils/snapshotGating). */
|
||||
model_price_locked?: boolean;
|
||||
};
|
||||
|
||||
const SPORT_LABEL: Record<string, string> = { nba: 'NBA', wnba: 'WNBA', mlb: 'MLB', soccer: 'Soccer' };
|
||||
@@ -162,6 +164,11 @@ export default function LiveHeroProp() {
|
||||
model_odds: data.model_odds,
|
||||
ev_pct: data.ev_pct,
|
||||
quarantine_reason: data.quarantine_reason,
|
||||
// Session 67 — MUST be forwarded. Without it a GATED model leg
|
||||
// falls through to the withheld/quarantine branch and the card
|
||||
// says "we suppressed our price" when the truth is "you don't
|
||||
// have this tier". A paywall must never wear poison's copy.
|
||||
model_price_locked: data.model_price_locked,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user