aaa41134d4
The landing hero reads the snapshot from Redis DIRECTLY via heroPropService, so it never passed through routes/snapshot.js and was still serving model_odds, ev_pct, value and takeable to anonymous visitors after the first fix. Same strip, same tier resolution, same private-cache rule for authenticated callers; the Next proxy now forwards the bearer token. PRODUCT CONSEQUENCE, FLAGGED RATHER THAN BURIED: the landing hero is served to anonymous visitors, so it now renders BOOK and FAIR with the model leg LOCKED instead of the full triplet it showed this morning. That follows the stated free-tier rule exactly, but it does trade a strong marketing moment (VALUE +21.1% VS FAIR on the shop window) for consistency of the gate. Reversing is one line — add 'model_price' to the free tier in src/config/tiers.js, or special-case the hero route — and is a product call, not a correctness one. Tests 3557 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
39 lines
1.6 KiB
JavaScript
39 lines
1.6 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* GET /api/hero-prop (Truth-Everywhere Part 2, item 5) — the daily hero prop:
|
|
* the live read where VYNDR disagrees most with the market (largest
|
|
* |model - consensus| gap, A/B only). Public, cache-only (reads the pre-graded
|
|
* snapshot) → never triggers grading, never spends API credits. Empty slate →
|
|
* most recent real graded read; nothing cached → { available:false } (hides).
|
|
*/
|
|
|
|
const express = require('express');
|
|
const { createRateLimit } = require('../middleware/rateLimit');
|
|
const heroPropService = require('../services/heroPropService');
|
|
// Session 67 — the hero reads the snapshot from Redis DIRECTLY, so it bypassed
|
|
// the gate on routes/snapshot.js and was still serving VYNDR's own price to
|
|
// anonymous visitors. Same strip, same rule: market legs free, model gated.
|
|
const { stripModelPrice } = require('../utils/snapshotGating');
|
|
const { resolveTierFromRequest } = require('../utils/requestTier');
|
|
|
|
const router = express.Router();
|
|
router.use(createRateLimit({ windowMs: 60_000, max: 60 }));
|
|
|
|
router.get('/', async (req, res) => {
|
|
try {
|
|
const hero = await heroPropService.pickHeroProp({});
|
|
const tier = await resolveTierFromRequest(req);
|
|
// stripModelPrice operates on rows; the hero IS one row.
|
|
const [gated] = stripModelPrice([hero], tier);
|
|
// Varies by entitlement → never shared-cached for an authenticated caller.
|
|
res.set('Cache-Control', req.headers.authorization ? 'private, max-age=300' : 'public, max-age=300');
|
|
return res.json(gated);
|
|
} catch (err) {
|
|
console.error('[hero-prop]', err.message);
|
|
return res.status(200).json({ available: false });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|