'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;