// STATIC GUARD: every mutation logged. Scans shared/turn.js source. // Invariant: any return with patch != null MUST also have log != null. // return { patch: {...}, log: {...} } — OK (logged mutation) // return { patch: null, log: null } — OK (no-op) // return { patch: {...}, log: null } — FAIL (unlogged mutation = BUG-7 class) // // BUG-7 history: reorderParticipants + deathSave + addParticipants + // updateParticipant all returned log:null with real patches. Per-op test // missed them because gaps section asserted null as "expected." Static scan // catches any future regression regardless of test enumeration. 'use strict'; const fs = require('fs'); const path = require('path'); const SRC = fs.readFileSync(path.join(__dirname, '..', 'turn.js'), 'utf8'); // Extract function name + body via brace counting. function collectBody(src, fromIdx) { let i = fromIdx; while (i < src.length && src[i] !== '{') i++; let depth = 0; const start = i; for (; i < src.length; i++) { if (src[i] === '{') depth++; else if (src[i] === '}') { depth--; if (depth === 0) break; } } return src.slice(start, i + 1); } function extractFunctions(src) { const out = []; const fnRe = /\bfunction\s+([A-Za-z0-9_$]+)\s*\(/g; const arrowRe = /(?:const|let|var)\s+([A-Za-z0-9_$]+)\s*=\s*[^=;]*?=>/g; let m; while ((m = fnRe.exec(src)) !== null) { out.push({ name: m[1], body: collectBody(src, m.index + m[0].length) }); } while ((m = arrowRe.exec(src)) !== null) { out.push({ name: m[1], body: collectBody(src, m.index + m[0].length) }); } return out; } // Split body into return statements at top level of the function. // Returns array of return-expr strings. function extractReturns(body) { const returns = []; // find `return ` at depth 1 (inside fn body) let i = 0; // body starts with `{`. Skip into depth 1. while (i < body.length && body[i] !== '{') i++; i++; // past { let depth = 1; let start = -1; for (; i < body.length; i++) { const ch = body[i]; if (ch === '{') depth++; else if (ch === '}') depth--; if (depth === 1 && body.slice(i, i + 7) === 'return ') { start = i + 7; } if (depth === 1 && ch === ';' && start !== -1) { returns.push(body.slice(start, i)); start = -1; } } return returns; } // For a return expr, detect: has `log: null` AND has `patch:` that is NOT null. // Returns true if suspicious (unlogged mutation). function isUnloggedMutation(retExpr) { if (!/log:\s*null/.test(retExpr)) return false; // patch: null → no-op, OK if (/patch:\s*null/.test(retExpr)) return false; // patch: { ... } or patch: someVar → mutation with null log = BAD if (/patch:/.test(retExpr)) return true; return false; } describe('STATIC: every mutation logged (logging contract)', () => { const fns = extractFunctions(SRC); test('no function returns patch!=null with log:null', () => { const offenders = []; for (const { name, body } of fns) { const rets = extractReturns(body); for (const r of rets) { if (isUnloggedMutation(r)) { offenders.push(`${name}: return ${r.trim().slice(0, 80)}`); } } } expect(offenders).toEqual([]); }); });