Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
123 lines
5.1 KiB
JavaScript
123 lines
5.1 KiB
JavaScript
'use strict';
|
|
process.env.MSD_TEST_MODE = '1';
|
|
|
|
/**
|
|
* Table-schema drift lint (ADR-2143 §3 Generative-Fix-Divergence guard, §7
|
|
* "prohibition with teeth", epic #2143, Phase 4).
|
|
*
|
|
* scripts/lint-table-schema-drift.cjs is the standalone lint (wired into
|
|
* `lint:ci`) that fails the moment a `TABLE_SCHEMAS` variant's header drifts
|
|
* from the one template/workflow file that emits it — the durable fix for
|
|
* #2137 / #2133 / #2119 (reader and writer disagreeing on a table's shape).
|
|
* These tests exercise its pure `findTableSchemaDrift()` with a synthetic
|
|
* drifted schema (fail-first) and confirm the live repo passes `scanRepo()`.
|
|
*
|
|
* Also locks in the ADR-2143 §7 zero-orphaned-marker invariant: no literal
|
|
* "pending #1372" / "pending #2143" marker remains anywhere in src/ (Phase 4
|
|
* asserts zero of either, so a future author cannot silently re-introduce an
|
|
* un-burned-down deferral marker).
|
|
*/
|
|
|
|
const { test, describe } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const ROOT = path.join(__dirname, '..');
|
|
const { findTableSchemaDrift, scanRepo, buildHeader } = require(
|
|
path.join(ROOT, 'scripts', 'lint-table-schema-drift.cjs'),
|
|
);
|
|
|
|
describe('#2143 table-schema drift lint: findTableSchemaDrift (pure)', () => {
|
|
test('a variant whose header appears verbatim in its source file is not drift', () => {
|
|
const schemas = { Foo: [{ label: 'default', columns: ['A', 'B'] }] };
|
|
const readFile = (rel) => (rel === 'foo.md' ? 'intro\n| A | B |\n|---|---|\n' : null);
|
|
assert.deepEqual(findTableSchemaDrift(schemas, readFile, { Foo: 'foo.md' }), []);
|
|
});
|
|
|
|
test('a synthetic drifted schema (header absent from its source file) IS flagged (fail-first)', () => {
|
|
// The registry claims a 3-column header but the file only ever emitted 2 —
|
|
// exactly the writer/reader divergence ADR-2143 §3 targets.
|
|
const schemas = { Foo: [{ label: 'default', columns: ['A', 'B', 'C'] }] };
|
|
const readFile = (rel) => (rel === 'foo.md' ? 'intro\n| A | B |\n|---|---|\n' : null);
|
|
const v = findTableSchemaDrift(schemas, readFile, { Foo: 'foo.md' });
|
|
assert.equal(v.length, 1);
|
|
assert.equal(v[0].schemaId, 'Foo');
|
|
assert.equal(v[0].label, 'default');
|
|
assert.equal(v[0].header, '| A | B | C |');
|
|
assert.equal(v[0].file, 'foo.md');
|
|
});
|
|
|
|
test('whitespace-around-pipes differences are normalized (not false drift)', () => {
|
|
const schemas = { Foo: [{ label: 'default', columns: ['A', 'B'] }] };
|
|
const readFile = (rel) => (rel === 'foo.md' ? '|A|B|\n|-|-|\n' : null);
|
|
assert.deepEqual(findTableSchemaDrift(schemas, readFile, { Foo: 'foo.md' }), []);
|
|
});
|
|
|
|
test('a schema id with no registered source file is flagged', () => {
|
|
const schemas = { Untracked: [{ label: 'default', columns: ['X'] }] };
|
|
const v = findTableSchemaDrift(schemas, () => null, {});
|
|
assert.equal(v.length, 1);
|
|
assert.equal(v[0].schemaId, 'Untracked');
|
|
assert.match(v[0].reason, /no canonical source file registered/);
|
|
});
|
|
|
|
test('a missing/unreadable source file flags every variant of that schema', () => {
|
|
const schemas = {
|
|
Foo: [
|
|
{ label: 'a', columns: ['X'] },
|
|
{ label: 'b', columns: ['Y'] },
|
|
],
|
|
};
|
|
const v = findTableSchemaDrift(schemas, () => null, { Foo: 'missing.md' });
|
|
assert.equal(v.length, 2);
|
|
assert.ok(v.every((d) => d.reason === 'source file not found or unreadable'));
|
|
});
|
|
|
|
test('buildHeader renders the exact `| col | col |` shape', () => {
|
|
assert.equal(buildHeader({ columns: ['Phase', 'Status'] }), '| Phase | Status |');
|
|
});
|
|
});
|
|
|
|
describe('#2143 table-schema drift lint: the live repo is clean', () => {
|
|
test('scanRepo finds zero drift against the current TABLE_SCHEMAS registry', () => {
|
|
const violations = scanRepo(ROOT);
|
|
assert.deepEqual(
|
|
violations,
|
|
[],
|
|
'TABLE_SCHEMAS variant(s) drifted from their canonical template/workflow:\n'
|
|
+ violations.map((d) => ` ${d.schemaId}.${d.label} (${d.file}): ${d.reason}`).join('\n'),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('#2143 Phase 4: zero orphaned pending-marker literals in src/', () => {
|
|
function walkCts(dir, acc) {
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const full = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) walkCts(full, acc);
|
|
else if (entry.isFile() && entry.name.endsWith('.cts')) acc.push(full);
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
test('no "pending #1372" or "pending #2143" literal marker remains in src/', () => {
|
|
const srcDir = path.join(ROOT, 'src');
|
|
const offenders = [];
|
|
for (const file of walkCts(srcDir, [])) {
|
|
const text = fs.readFileSync(file, 'utf8');
|
|
const rel = path.relative(ROOT, file);
|
|
text.split(/\r?\n/).forEach((line, idx) => {
|
|
if (line.includes('pending #1372') || line.includes('pending #2143')) {
|
|
offenders.push(`${rel}:${idx + 1}`);
|
|
}
|
|
});
|
|
}
|
|
assert.deepEqual(
|
|
offenders,
|
|
[],
|
|
`orphaned "pending #1372"/"pending #2143" marker(s) found (ADR-2143 §7 requires zero):\n ${offenders.join('\n ')}`,
|
|
);
|
|
});
|
|
});
|