da8bfdf1db
CARD BADGE ONLY. Ticker-CLV explicitly DEFERRED (named, not lost).
PHASE 1 — SERVER-SIDE GATE AT THE DATA LAYER. A Free request never
RECEIVES dclv data: the CLV columns are appended to the SELECT only behind
canAccess(tier,'clv_badge') (new capability, analyst+desk), and responses
are ALSO stripped as defence in depth so a future SELECT change cannot
quietly leak. No CSS/client gate — data that reaches the browser has left
the building. dclv_fair_lock/fair_close are de-vig internals and are never
sent at all.
SURFACE AUDIT, all six channels, each test-locked to contain no CLV:
public profile (share link), snapshot/card feed, ticker feed, share
card/OG, embeddable widget, newsletter. A test also asserts no
ledger_entries read uses select('*') — a star would auto-leak every new
column, which is exactly how a gate becomes theatre.
PHASE 2 — IMMUTABLE ONCE COMPUTED. A settle can re-run (stat correction,
protested game) and a badge that flips positive->negative AFTER a user saw
or screenshotted it is a credibility failure. First computation wins: dclv
is only computed when dclv_computed_at is null, so a re-settle can never
rewrite a shown badge. Same discipline as the locked grade.
PHASE 3 — RENDER, test-first, ABSENCE IS HONEST. unknown / flat / null /
missing-receipt all render NOTHING — no element, no placeholder, no
"pending". Proven on an ALL-NULL board (today: 0 badges) and a MIXED board
(tomorrow: 1 of 4 badged, badge-less cards clean). Binary states only:
positive -> MOVED TOWARD US "graded -110 · closed -145"
negative -> MOVED AWAY "graded -110 · closed +120"
The RECEIPT is the persuasive part, so a badge with no numbers is
suppressed rather than shown as a bare claim. Negative is neutral context
and NEVER touches the locked grade — no back-door re-grading.
NO aggregate, count or rollup exists by construction: the module exports
exactly {clvBadge, fmtPrice} and a badge payload carries exactly
{tone,label,receipt} — asserted by test, because an on-screen tally would
be the held aggregate claim through the side door.
Build gotcha hit and fixed: clvBadge is CommonJS (allowJs) with no TS
types, so the .tsx needed an explicit cast at the call site — the build
worker exits 1 on type errors even though compilation "succeeds".
Suite 288/3473 green, build exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
84 lines
3.3 KiB
JavaScript
84 lines
3.3 KiB
JavaScript
/**
|
|
* 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', () => {
|
|
expect(svc).toMatch(/dclv_computed_at, player_key/);
|
|
});
|
|
});
|