Records the reference artifacts Phase 5 swaps will be measured against, and
the gate that reads them. No extraction yet: this is the "before" picture,
and it had to land first or every later comparison would be confounded.
Typed test/ rather than feat/ deliberately - the only production change is
one widened export; the rest is gate, artifacts and generator.
Written failing-first (7 red on missing artifacts), then generated.
Three layers, because the plan's "assert .source/.flags of every regex" is
necessary but not sufficient:
1. regex records - .source/.flags off the COMPILED object. After a swap a
pattern is new RegExp(jsonString, flags), so the plan's named hazard
(JSON backslash-doubling on 83+18 regexes) is visible here and nowhere
else. Source-text comparison cannot see it.
2. table records - key/value digests. Most of what Phase 4 moves is not a
regex at all: HOMOGLYPH_MAP (x3, AS-IS), the typosquat tokens and the
four OWASP maps are char->char and string->string data. A regex-only
dump is blind to a broken homoglyph swap, i.e. to the bulk of the
payload. Operator decision: widen the dump.
3. file records - sha256 of the five moving-set sources. This dissolves
STATE's open question (how to enumerate every regex): it is complete by
construction, covering inline regexes in function bodies that no export
walk reaches, with no JS parser in a zero-dep repo. A lexical count
would have pinned a lie - severity.mjs scores 4 "regexes" that way and
exports none.
Both layers were proven to fire, not assumed to: mutating one HOMOGLYPH_MAP
entry reddens the table layer, and widening an inline regex inside
decodeHexEscapes (unreachable by any export walk) reddens the file layer.
HOMOGLYPH_MAP is now exported from string-utils.mjs. That export is a source
change the plan already flags as a surface hazard ("private tables become
loaded"), so it is pre-paid here rather than confounding the before/after.
Reference run: the 61 showcase payloads through the real hook entry points,
sequentially - array order is semantic and the plan forbids key-sorting, so
concurrency is removed rather than sorted away. 61/61 match expectation,
which also settles the plan's open assumption that payloads.json expectations
match current behaviour. Coverage is recorded, not assumed: 47/83 patterns,
with the other 36 listed by key so the gate never implies coverage it lacks.
Suite counts are per-file, each file run alone - a total is unattributable,
and the three known timing-sensitive files flake only under concurrency.
2045 pass / 0 fail across 91 files, matching the pre-existing count exactly.
The gate's own file is excluded (it reads the artifact the run produces) and
that exclusion is named in the artifact.
Suite: 2053/2053, 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BB4vXvwvtW4dxbPRd6vsez
88 lines
3.1 KiB
JavaScript
88 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
|
// golden-baseline.mjs — regenerate the v8 Phase 5 golden baseline artifacts.
|
|
//
|
|
// node scripts/golden-baseline.mjs # print a summary, write nothing
|
|
// node scripts/golden-baseline.mjs --write # (re-)bless the artifacts
|
|
//
|
|
// Re-blessing is a deliberate act. During Phase 5, a diff here means the
|
|
// table swap under test changed observable behaviour — the correct response
|
|
// is to roll the swap back, not to re-run this with --write.
|
|
|
|
import { mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
import { resolve, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import {
|
|
buildPatternDump,
|
|
buildReferenceRun,
|
|
buildSuiteCounts,
|
|
serialize,
|
|
} from './lib/golden-dump.mjs';
|
|
|
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const GOLDEN_DIR = resolve(ROOT, 'tests/golden');
|
|
const write = process.argv.includes('--write');
|
|
// Opt-in: one node process per test file, sequentially. Minutes, not seconds.
|
|
const withSuite = process.argv.includes('--suite');
|
|
|
|
const artifacts = [
|
|
['patterns.json', await buildPatternDump(ROOT)],
|
|
['reference-run.json', await buildReferenceRun(ROOT)],
|
|
];
|
|
|
|
/** `fs.globSync` is Node 22+; package.json promises >=18, so walk by hand. */
|
|
function findTestFiles(dir, acc = []) {
|
|
for (const e of readdirSync(dir, { withFileTypes: true }).sort((a, b) =>
|
|
a.name.localeCompare(b.name)
|
|
)) {
|
|
const full = resolve(dir, e.name);
|
|
if (e.isDirectory()) findTestFiles(full, acc);
|
|
else if (e.name.endsWith('.test.mjs')) acc.push(full);
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
if (withSuite) {
|
|
artifacts.push([
|
|
'suite-counts.json',
|
|
buildSuiteCounts(ROOT, findTestFiles(resolve(ROOT, 'tests'))),
|
|
]);
|
|
}
|
|
|
|
mkdirSync(GOLDEN_DIR, { recursive: true });
|
|
|
|
let drift = false;
|
|
for (const [name, data] of artifacts) {
|
|
const path = resolve(GOLDEN_DIR, name);
|
|
const next = serialize(data);
|
|
const prev = existsSync(path) ? readFileSync(path, 'utf8') : null;
|
|
const status = prev === null ? 'new' : prev === next ? 'unchanged' : 'CHANGED';
|
|
if (status === 'CHANGED') drift = true;
|
|
|
|
if (write) writeFileSync(path, next);
|
|
console.log(`${write ? 'wrote' : 'checked'} tests/golden/${name} [${status}]`);
|
|
}
|
|
|
|
const [, dump] = artifacts[0];
|
|
const [, run] = artifacts[1];
|
|
console.log(
|
|
`\n regex records : ${dump.counts.regex}` +
|
|
`\n table records : ${dump.counts.table}` +
|
|
`\n file digests : ${dump.counts.file}` +
|
|
`\n reference run : ${run.summary.total} cases, ` +
|
|
`${run.summary.matchingExpectation} match expectation` +
|
|
`\n coverage : ${run.coverage.patternsExercised}/${run.coverage.patternsTotal} patterns` +
|
|
` | HOMOGLYPH_MAP ${run.coverage.tablesExercised.HOMOGLYPH_MAP ? 'exercised' : 'NOT exercised'}`
|
|
);
|
|
|
|
const suite = artifacts.find(([n]) => n === 'suite-counts.json');
|
|
if (suite) {
|
|
const [, s] = suite;
|
|
console.log(
|
|
` suite : ${s.totals.pass} pass / ${s.totals.fail} fail across ${s.totals.files} files`
|
|
);
|
|
}
|
|
|
|
if (!write && drift) {
|
|
console.error('\nBaseline drift detected. Investigate before re-blessing with --write.');
|
|
process.exit(1);
|
|
}
|