#!/usr/bin/env node /* READ-HISTORY UX TEETH. Inject, prove the suite CATCHES it, restore exactly. Every tooth asserts its injection landed first — a green teeth run means the test is missing, not that the risk is absent. */ const fs = require('fs'); const path = require('path'); const cp = require('child_process'); const F = (p) => path.join(__dirname, p); const U = 'tests/unit/readHistoryUx.test.js'; const C = 'tests/unit/lineageCloseout.test.js'; const P = 'tests/unit/lineageProductization.test.js'; const TEETH = [ { n: 1, name: 'the history reuses the grade-revision strike', file: 'web/src/components/vyndr/ReadHistory.tsx', from: "fontSize: 11, fontWeight: 700, color: TONE[e.tone]", to: "fontSize: 11, fontWeight: 700, textDecoration: 'line-through', color: TONE[e.tone]", suite: U }, { n: 2, name: 'legacy renders as a complete empty history', file: 'web/src/lib/readHistory.js', from: " if (state !== 'LINEAGE_AVAILABLE') {", to: " if (state === 'NEVER') {", suite: U }, { n: 3, name: 'LINEAGE_UNAVAILABLE silently reads as legacy', file: 'web/src/lib/readHistory.js', from: " LINEAGE_UNAVAILABLE: {\n headline: 'History unavailable',", to: " LINEAGE_UNAVAILABLE: {\n headline: 'History not recorded',", suite: U }, { n: 4, name: 'an incomplete chronology claims to be complete', file: 'web/src/lib/readHistory.js', from: " const incomplete = a.chronology_complete === false || unrecorded > 0 || before > 0;", to: " const incomplete = false;", suite: U }, { n: 5, name: 'invalid lineage elects a head anyway', file: 'src/services/read/readAncestry.js', from: " state: ANCESTRY_STATE.INVALID_LINEAGE,\n reason: chron.reason,", to: " state: ANCESTRY_STATE.LINEAGE_AVAILABLE,\n reason: chron.reason,", suite: `${C} ${P}` }, { n: 6, name: 'the public route is keyed on the raw natural key', file: 'src/routes/ancestry.js', from: "const LEDGER_COLUMNS = 'id, sport", to: "const NATURAL = 'read_natural_key';\nconst LEDGER_COLUMNS = 'id, sport", suite: `${U} ${C}` }, { n: 7, name: 'ancestry succeeds unauthenticated', file: 'src/routes/ancestry.js', from: "router.get('/ledger/:id', requireAuth, async (req, res) => {", to: "router.get('/ledger/:id', async (req, res) => {", suite: `${U} ${C}` }, { n: 8, name: 'the UI bypasses the API client with ad hoc fetch', file: 'web/src/components/vyndr/ReadHistory.tsx', from: " const { ok, status, data } = await getReadAncestry(ledgerId, token);", to: " const r = await fetch('/api/ancestry/ledger/' + ledgerId); const ok = r.ok, status = r.status, data = await r.json();", suite: U }, { n: 9, name: 'recaptures are destroyed instead of summarised', file: 'web/src/lib/readHistory.js', from: " if (list.length === 0) return null;\n return {\n kind: 'recapture-summary',", to: " if (list.length >= 0) return null;\n return {\n kind: 'recapture-summary',", suite: U }, { n: 10, name: 'published values are synthesised from lineage metadata', file: 'src/services/read/readAncestry.js', from: " published_grade: row.grade ?? null,", to: " published_grade: row.claim_digest ? row.claim_digest.slice(0, 1) : null,", suite: U }, { n: 11, name: 'the existing grade strike is removed from the ledger card', file: 'web/src/app/ledger/page.tsx', from: "textDecoration: 'line-through'", to: "textDecoration: 'none'", suite: U }, { n: 12, name: 'persistent shadow becomes lease-dependent', file: 'src/services/lineageWriteMode.js', from: " return Object.prototype.hasOwnProperty.call(evaluate(now).modes, sp);", to: " return canary.isEnabled(sp, now);", suite: P }, { n: 13, name: 'the coverage monitor denominator reads lineage_action', file: 'src/services/lineageCoverage.js', from: " if (!r || r.published !== true) continue;", to: " if (!r || r.published !== true || r.lineage_action == null) continue;", suite: `${C} ${P}` }, { n: 14, name: 'participant identity convergence reverted', file: 'src/services/model/participantIdentity.js', from: " const canonical = p.mlb_person_id != null ? p.canonical_player_name : null;", to: " const canonical = null;", suite: `${P} tests/unit/participantIdentityConvergence.test.js` }, { n: 15, name: 'the retention collision gate is weakened', file: 'src/services/retentionService.js', from: " if (seen.has(id)) collisions += 1;", to: " if (false) collisions += 1;", suite: `${P} tests/unit/canonicalParticipant.test.js` }, ]; let caught = 0; const missed = []; for (const t of TEETH) { const p = F(t.file); const orig = fs.readFileSync(p, 'utf8'); if (!orig.includes(t.from)) { missed.push(`${t.n} ANCHOR NOT FOUND: ${t.name}`); continue; } const broken = orig.replace(t.from, t.to); if (broken === orig) { missed.push(`${t.n} INJECTION NO-OP: ${t.name}`); continue; } fs.writeFileSync(p, broken); const present = fs.readFileSync(p, 'utf8').includes(t.to); let failed = false; try { cp.execSync(`npx jest ${t.suite} --testPathIgnorePatterns "/node_modules/" --silent --forceExit`, { cwd: __dirname, stdio: 'pipe' }); } catch { failed = true; } fs.writeFileSync(p, orig); if (fs.readFileSync(p, 'utf8') !== orig) { console.error('RESTORE FAILED', t.file); process.exit(2); } if (!present) { missed.push(`${t.n} INJECTION NOT PRESENT: ${t.name}`); continue; } if (failed) { caught += 1; console.log(` tooth ${String(t.n).padStart(2)} CAUGHT ${t.name}`); } else { missed.push(`${t.n} NOT CAUGHT: ${t.name}`); console.log(` tooth ${String(t.n).padStart(2)} MISSED ${t.name}`); } } console.log(`\n ${caught}/${TEETH.length} teeth landed`); if (missed.length) { console.log(' PROBLEMS:'); missed.forEach((m) => console.log(' ', m)); process.exit(1); }