/** * Session 64 — the CLV gate is SERVER-SIDE, at the data layer. * * A Free request must never RECEIVE dclv data. Client/CSS hiding is rejected: * data that reaches the browser has left the building. These lock the gate and * the surface audit, so a future column addition can't quietly leak. */ const fs = require('fs'); const path = require('path'); const { canAccess } = require('../../src/config/tiers'); const read = (f) => fs.readFileSync(path.join(__dirname, '..', '..', f), 'utf8'); describe('TIER capability', () => { test('analyst and desk may see the badge; free may not', () => { expect(canAccess('analyst', 'clv_badge')).toBe(true); expect(canAccess('desk', 'clv_badge')).toBe(true); expect(canAccess('free', 'clv_badge')).toBeFalsy(); expect(canAccess(undefined, 'clv_badge')).toBeFalsy(); }); }); describe('SERVER GATE — the data never leaves for an unentitled tier', () => { const ledger = read('src/routes/ledger.js'); test('CLV columns are appended only via a capability check', () => { expect(ledger).toMatch(/canAccess\(tier, 'clv_badge'\)/); expect(ledger).toMatch(/CLV_COLUMNS/); }); test('the base column list does NOT contain dclv', () => { // Assert on the literal itself — a nearby comment mentioning dclv is fine. const m = ledger.match(/const ROW_COLUMNS = '([^']+)'/); expect(m).toBeTruthy(); expect(m[1]).not.toMatch(/dclv/); }); test('responses are ALSO stripped — defence in depth, not just the SELECT', () => { expect(ledger).toMatch(/function stripClv/); expect(ledger).toMatch(/stripClv\(data, req\)/); }); test('de-vig internals (fair_lock/fair_close) are never SELECTED for clients', () => { // They may (and should) appear in the strip list — that is the guard. const sel = ledger.match(/const CLV_COLUMNS = '([^']+)'/); expect(sel[1]).not.toMatch(/fair_lock|fair_close/); expect(ledger).toMatch(/dclv_fair_lock, dclv_fair_close, \.\.\.rest/); }); }); describe('SURFACE AUDIT — every channel CLV could leak through', () => { const surfaces = { 'public profile (share link)': 'src/routes/profiles.js', 'snapshot / card feed': 'src/routes/snapshot.js', 'ticker feed': 'src/routes/ticker.js', 'share card / OG': 'src/routes/shareCard.js', 'widget (embeddable)': 'src/routes/widget.js', 'newsletter': 'src/services/newsletterService.js', }; for (const [name, file] of Object.entries(surfaces)) { test(`${name} carries NO CLV data`, () => { expect(read(file)).not.toMatch(/dclv/); }); } test('no ledger read uses select("*") — a star would auto-leak new columns', () => { for (const f of ['src/routes/ledger.js', 'src/routes/profiles.js', 'src/services/ledgerService.js']) { const src = read(f); const stars = src.match(/from\('ledger_entries'\)[\s\S]{0,60}?select\('\*'\)/g) || []; expect(stars).toHaveLength(0); } }); }); describe('IMMUTABILITY — a shown badge never silently flips', () => { const svc = read('src/services/ledgerService.js'); test('dclv is computed only when it has never been computed', () => { expect(svc).toMatch(/row\.dclv_computed_at\s*\n?\s*\?\s*null/); }); test('the settle read fetches dclv_computed_at so the guard can see it', () => { // Asserted on the SETTLE SELECT itself rather than on an adjacent column // pair: the settle read is now a single query (the id-refetch that used to // follow it overflowed the URL and silently returned null), so column order // changed while the requirement did not. What matters is that the column the // immutability guard reads is actually fetched. const settleSelect = svc.match(/\.select\('id, player_key[^']*'\)/g) || []; expect(settleSelect.length).toBeGreaterThan(0); expect(settleSelect.some((sel) => sel.includes('dclv_computed_at'))).toBe(true); }); });