0e7871ac0e
4a VOICE v1.1 committed (board start); lint is EXECUTABLE — banned list + no-exclamation law enforced in the engine (throws in test, drops in prod) and locked by tests. Curly-apostrophe variants covered. 4b mediaEngine: deterministic templates (MORNING WIRE, SIGNAL, STREAK WATCH, THE SETTLE, RECEIPTS, ARCHETYPE WATCH, LINE DISPATCH) filled ONLY from snapshot/ledger/streaks JSON. Record percentages never render under n>=20 (counts + 'Record building' below). Stark layer = curated committed library (content/stark-lines.json), day-rotated selection — selected, never generated. 4c /desk (founder-only: requireAuth + DESK_OWNERS email allowlist, deny-by-default): all formats as text + <=280-char pre-segmented tweets with per-tweet copy buttons + char counts, wire/numbers-only variants, DATA BRIEF block (structured day numbers) with copy-for-claude.ai. ntfy ping after the day's first snapshot: 'Desk pack ready'. 4d ghostPublisher: DRAFTS ONLY (status:'draft' test-locked), env-gated no-op, HS256 JWT via node crypto (zero new deps). POST /api/internal/ghost/drafts saves slate preview + settle drafts. Nothing anywhere auto-posts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
180 lines
8.2 KiB
JavaScript
180 lines
8.2 KiB
JavaScript
// Session 63 (A1-S4) — the VOICE v1.1 template engine. Numbers only from
|
||
// pipeline JSON; lint enforces the banned list + no-exclamation law; record
|
||
// claims obey n≥20; Stark lines are SELECTED from the committed library.
|
||
|
||
const media = require('../../src/services/mediaEngine');
|
||
const { lintVoice, segmentThread, starkLine, morningWire, signal, theSettle, receipt, streakWatch, archetypeWatch, lineDispatch } = media;
|
||
|
||
describe('lintVoice — the banned list is law', () => {
|
||
test.each([
|
||
['This one is a lock!', 2], // "lock" + exclamation
|
||
['🔥 free money, who’s tailing', 3],
|
||
['We’re so back 🚀', 2],
|
||
])('%s → %i violations', (text, n) => {
|
||
expect(lintVoice(text).length).toBeGreaterThanOrEqual(n);
|
||
});
|
||
test('clean wire copy passes', () => {
|
||
expect(lintVoice('THE SETTLE — Jul 11\nA-tier: 6-2\nvyndr.app/ledger')).toEqual([]);
|
||
});
|
||
});
|
||
|
||
describe('every template output passes its own lint', () => {
|
||
const NOW = '2026-07-12T14:05:00.000Z';
|
||
const GRADE = {
|
||
player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5, direction: 'over',
|
||
grade: 'A', confidence: 78, archetype: 'BOMBER',
|
||
gradedAt: { line: 1.5, odds: -120, timestamp: NOW },
|
||
};
|
||
|
||
test('morning wire: real counts, stark from the library, no bans', () => {
|
||
const text = morningWire({ dateIso: NOW, counts: { mlb: 15, wnba: 3, nba: 0 }, loudest: GRADE });
|
||
expect(text).toContain('15 MLB.');
|
||
expect(text).toContain('3 WNBA.');
|
||
expect(text).not.toContain('0 NBA'); // zero sports never listed
|
||
expect(text).toContain('Aaron Judge');
|
||
expect(text).toContain('vyndr.app');
|
||
expect(lintVoice(text)).toEqual([]);
|
||
});
|
||
|
||
test('signal: player + line + grade + ET timestamp with real odds', () => {
|
||
const text = signal(GRADE);
|
||
expect(text).toContain('Aaron Judge — TB o1.5 · A');
|
||
expect(text).toContain('BOMBER read.');
|
||
expect(text).toContain('at -120');
|
||
expect(lintVoice(text)).toEqual([]);
|
||
});
|
||
|
||
test('the settle: counts always, percentages NEVER under n≥20', () => {
|
||
const agg = { settled: 8, hits: 5, misses: 3, pushes: 0, hit_pct: null, pending: 16, by_tier: { A: { settled: 4, hits: 3, misses: 1, hit_pct: null } } };
|
||
const misses = [{ player_name: 'A’ja Wilson', stat: 'points', line: 23.5, side: 'over' }];
|
||
const text = theSettle({ aggregate: agg, misses, dateIso: NOW });
|
||
expect(text).toContain('All grades: 5-3');
|
||
expect(text).toContain('A-tier: 3-1');
|
||
expect(text).not.toContain('%'); // no percentage below the gate
|
||
expect(text).toContain('Record building — 8 settled');
|
||
expect(text).toContain('Wilson PTS o23.5 ❌'); // misses BY NAME
|
||
expect(lintVoice(text)).toEqual([]);
|
||
});
|
||
|
||
test('receipt: lock beside close beside result', () => {
|
||
const row = {
|
||
player_name: 'Aaron Judge', stat: 'total_bases', line: 1.5, side: 'over', grade: 'A',
|
||
graded_at: NOW, locked_odds: '-120', closing_line: 1.5, closing_odds: '-145',
|
||
clv: 0, clv_result: 'flat', outcome: 'hit', actual_value: 2, settled_at: NOW,
|
||
};
|
||
const text = receipt(row);
|
||
expect(text).toContain('Posted');
|
||
expect(text).toContain('Judge TB o1.5 · A · -120');
|
||
expect(text).toContain('Close: o1.5 at -145');
|
||
expect(text).toContain('Result: 2 ✅');
|
||
expect(lintVoice(text)).toEqual([]);
|
||
});
|
||
|
||
test('streak watch: lens reads only, absent lens rows dropped', () => {
|
||
const rows = [
|
||
{ player: 'Brice Turang', lens: { read: '12-game on-base streak; tonight @ Pittsburgh Pirates' } },
|
||
{ player: 'No Lens Guy', lens: { read: null } },
|
||
];
|
||
const text = streakWatch(rows, NOW);
|
||
expect(text).toContain('Turang');
|
||
expect(text).not.toContain('No Lens Guy');
|
||
expect(lintVoice(text)).toEqual([]);
|
||
});
|
||
|
||
test('archetype watch needs ≥2 of a kind, else null', () => {
|
||
expect(archetypeWatch([GRADE], NOW)).toBeNull();
|
||
const two = archetypeWatch([GRADE, { ...GRADE, player: 'Shohei Ohtani' }], NOW);
|
||
expect(two).toContain('BOMBER watch: 2');
|
||
expect(lintVoice(two)).toEqual([]);
|
||
});
|
||
|
||
test('line dispatch from a real movement row', () => {
|
||
const text = lineDispatch({ ...GRADE, movement: { kind: 'steam', delta: 1, currentLine: 2.5 } }, NOW);
|
||
expect(text).toContain('o1.5 → o2.5 ▲');
|
||
expect(text).toContain('Steam.');
|
||
expect(lintVoice(text)).toEqual([]);
|
||
});
|
||
});
|
||
|
||
describe('stark layer — selected, never generated; deterministic', () => {
|
||
test('same date → same line; comes from the committed library', () => {
|
||
const a = starkLine('morning', '2026-07-12T10:00:00Z');
|
||
const b = starkLine('morning', '2026-07-12T22:00:00Z');
|
||
expect(a).toBe(b);
|
||
expect(media.__internals.STARK.morning).toContain(a);
|
||
});
|
||
test('the whole library passes lint', () => {
|
||
for (const lines of Object.values(media.__internals.STARK)) {
|
||
if (!Array.isArray(lines)) continue;
|
||
for (const l of lines) expect(lintVoice(l)).toEqual([]);
|
||
}
|
||
});
|
||
});
|
||
|
||
describe('segmentThread — ≤280 per tweet on line boundaries', () => {
|
||
test('splits long threads and preserves every line', () => {
|
||
const long = Array.from({ length: 30 }, (_, i) => `Line ${i} of the settle with some padding text`).join('\n');
|
||
const tweets = segmentThread(long);
|
||
expect(tweets.length).toBeGreaterThan(1);
|
||
for (const t of tweets) expect(t.length).toBeLessThanOrEqual(280);
|
||
expect(tweets.join('\n')).toBe(long);
|
||
});
|
||
});
|
||
|
||
describe('deskService — pack assembly from pipeline data only', () => {
|
||
const { assembleDeskPack } = require('../../src/services/deskService');
|
||
test('assembles formats + data brief from injected caches', async () => {
|
||
const NOW = '2026-07-12T14:05:00.000Z';
|
||
const store = {
|
||
'snapshot:mlb:latest': { grades: [
|
||
{ player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5, direction: 'over', grade: 'A', confidence: 80, archetype: 'BOMBER', gradedAt: { line: 1.5, odds: -120, timestamp: NOW } },
|
||
{ player: 'Shohei Ohtani', stat_type: 'home_runs', line: 0.5, direction: 'over', grade: 'A', confidence: 76, archetype: 'BOMBER', gradedAt: { line: 0.5, odds: 130, timestamp: NOW } },
|
||
] },
|
||
};
|
||
const pack = await assembleDeskPack({
|
||
cacheGet: async (k) => store[k] ?? null,
|
||
loadRosterLogs: async () => [],
|
||
ledger: { getModelAggregate: async () => ({ settled: 0, hits: 0, misses: 0, pushes: 0, hit_pct: null, pending: 24 }), __internals: { isConfigured: () => false } },
|
||
fetchSettled: async () => [],
|
||
now: () => NOW,
|
||
});
|
||
expect(pack.formats.morning_wire[0].text).toContain('2 MLB.');
|
||
expect(pack.formats.signals.length).toBe(2);
|
||
expect(pack.formats.archetype_watch[0].text).toContain('BOMBER watch: 2');
|
||
// variants: wire + numbers-only when a stark line was present
|
||
expect(pack.formats.morning_wire.length).toBeGreaterThanOrEqual(1);
|
||
expect(pack.data_brief.top_signals[0].player).toBe('Aaron Judge');
|
||
expect(pack.data_brief.record.pending).toBe(24);
|
||
// every emitted text passes lint
|
||
const all = JSON.stringify(pack.formats);
|
||
expect(all).not.toContain('!');
|
||
});
|
||
});
|
||
|
||
describe('ghostPublisher — drafts only, env-gated, no deps', () => {
|
||
const ghost = require('../../src/services/ghostPublisher');
|
||
test('JWT is a valid HS256 three-parter with the key id', () => {
|
||
const t = ghost.ghostJwt('abc123:6465616462656566', 1_752_000_000);
|
||
const [h, p] = t.split('.');
|
||
const header = JSON.parse(Buffer.from(h, 'base64url').toString());
|
||
const payload = JSON.parse(Buffer.from(p, 'base64url').toString());
|
||
expect(header.kid).toBe('abc123');
|
||
expect(payload.aud).toBe('/admin/');
|
||
expect(payload.exp - payload.iat).toBe(300);
|
||
});
|
||
test('unconfigured → no-op, never a throw', async () => {
|
||
const r = await ghost.saveDraft({ title: 't', html: '<p>x</p>' });
|
||
expect(r.ok).toBe(false);
|
||
expect(r.reason).toBe('ghost not configured');
|
||
});
|
||
test('posts DRAFT status only', async () => {
|
||
let body = null;
|
||
const r = await ghost.saveDraft({ title: 't', html: '<p>x</p>' }, {
|
||
force: true, url: 'https://blog.test', adminKey: 'id:6162',
|
||
fetchImpl: async (url, opts) => { body = JSON.parse(opts.body); return { ok: true, json: async () => ({ posts: [{ id: 'p1' }] }) }; },
|
||
});
|
||
expect(r.ok).toBe(true);
|
||
expect(body.posts[0].status).toBe('draft'); // NOTHING auto-publishes
|
||
});
|
||
});
|