// 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 });
}