// Layer 3 scaffold: a product's load-bearing invariants live in a rules table // (INVARIANTS.md, stable IDs) instead of scattered comments, and a parity guard // keeps table and tests in lockstep — every invariant is referenced by at least // one test, and no test invents an ID the table doesn't define. See // INVARIANTS.template.md for the table format and the copy-paste guard test. import { readdirSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; // IDs are INV--: greppable, stable under renumbering-free growth, // and specific enough that a bare prose mention can't collide by accident. const ID = /INV-[A-Z0-9]+-\d{3}/; const ID_TOKEN = new RegExp(`\\b${ID.source}\\b`, "g"); const TABLE_ROW = new RegExp(`^\\|\\s*(${ID.source})\\s*\\|\\s*(.*?)\\s*\\|`); export interface Invariant { id: string; rule: string; line: number; } export function parseInvariants(markdown: string): Invariant[] { const invariants: Invariant[] = []; markdown.split("\n").forEach((text, i) => { const row = text.match(TABLE_ROW); if (row?.[1] !== undefined && row[2] !== undefined) { invariants.push({ id: row[1], rule: row[2], line: i + 1 }); } }); return invariants; } export interface ParityReport { ok: boolean; problems: string[]; } export interface ParityOptions { // Runtime-only invariants no emitted-graph test can show (e.g. a timing // guarantee). Still parity-checked: each must exist in the table, and a // citing test appearing later flags the marker as stale. untestableIds?: string[]; } export function invariantParity( markdown: string, testSources: Record, opts: ParityOptions = {}, ): ParityReport { const problems: string[] = []; const invariants = parseInvariants(markdown); const untestable = new Set(opts.untestableIds ?? []); if (invariants.length === 0) { problems.push("no invariants parsed — rows must look like `| INV-AREA-001 | rule text | guarded-by |`."); } const seen = new Set(); for (const inv of invariants) { if (seen.has(inv.id)) problems.push(`duplicate invariant ID ${inv.id} (line ${inv.line}).`); seen.add(inv.id); } const referenced = new Set(); for (const [file, source] of Object.entries(testSources)) { for (const token of source.match(ID_TOKEN) ?? []) { referenced.add(token); if (invariants.length > 0 && !seen.has(token)) { problems.push(`${file} references ${token}, which is not in the invariants table — add the row or fix the ID.`); } } } for (const inv of invariants) { if (referenced.has(inv.id) && untestable.has(inv.id)) { problems.push( `invariant ${inv.id} is declared untestable but a test cites it — remove the stale untestableIds entry.`, ); } if (!referenced.has(inv.id) && !untestable.has(inv.id)) { problems.push( `invariant ${inv.id} (${inv.rule}) has no referencing test — cite its ID in the test that guards it, or declare it in untestableIds.`, ); } } for (const id of untestable) { if (!invariants.some((inv) => inv.id === id)) { problems.push( `untestableIds declares ${id}, which is not in the invariants table — add the row or drop the entry.`, ); } } return { ok: problems.length === 0, problems }; } function collectTestFiles(dir: string, out: string[]): void { for (const entry of readdirSync(dir)) { const path = join(dir, entry); if (statSync(path).isDirectory()) { if (entry !== "node_modules") collectTestFiles(path, out); } else if (/\.test\.tsx?$/.test(entry)) { out.push(path); } } } export function invariantParityFromDisk( opts: { invariantsPath: string; testDirs: string[] } & ParityOptions, ): ParityReport { const markdown = readFileSync(opts.invariantsPath, "utf8"); const files: string[] = []; for (const dir of opts.testDirs) collectTestFiles(dir, files); const sources: Record = {}; for (const file of files) sources[file] = readFileSync(file, "utf8"); return invariantParity(markdown, sources, { untestableIds: opts.untestableIds }); }