Files
waggle-os/scripts/oss-drift-check.mjs
Oleg Maslov b20b138fe4 moving
2026-09-02 10:14:22 +02:00

567 lines
19 KiB
JavaScript

#!/usr/bin/env node
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import {
lstatSync,
readFileSync,
readdirSync,
realpathSync,
} from 'node:fs';
import { dirname, isAbsolute, join, posix, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
const BASELINE_PATH = join(SCRIPT_DIR, 'oss-drift-baseline.json');
const MAPPING = Object.freeze({
canonical: 'packages/hive-mind-core/src',
oss: 'packages/core/src',
ignoredDirectories: ['dist', 'node_modules'],
ignoredFileSuffixes: ['.test.ts', '.tsbuildinfo'],
});
const FORBIDDEN_EXPORTS = Object.freeze({
paths: [
'mind/evolution-runs.ts',
'mind/execution-traces.ts',
'mind/improvement-signals.ts',
],
pathPrefixes: ['vault.ts', 'compliance/'],
markers: [
{ path: 'mind/db.ts', token: 'install_audit' },
{ path: 'mind/schema.ts', token: 'install_audit' },
],
});
const VALID_ADAPTATION_KINDS = new Set(['branding', 'import', 'layout', 'logger']);
const VALID_STATES = new Set(['different', 'only-canonical', 'only-oss']);
const VALID_DISPOSITIONS = new Set([
'forward-port',
'product-curation',
'reconcile',
'reverse-port',
]);
const HASH_PATTERN = /^[a-f0-9]{64}$/;
const decoder = new TextDecoder('utf-8', { fatal: true });
class ConfigurationError extends Error {}
function compareOrdinal(left, right) {
return left < right ? -1 : left > right ? 1 : 0;
}
function assertRecord(value, label) {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
throw new ConfigurationError(`${label} must be an object`);
}
}
function assertExactKeys(value, expected, label) {
assertRecord(value, label);
const actual = Object.keys(value).sort(compareOrdinal);
const wanted = [...expected].sort(compareOrdinal);
if (JSON.stringify(actual) !== JSON.stringify(wanted)) {
throw new ConfigurationError(
`${label} has unexpected keys: expected ${wanted.join(', ')}, got ${actual.join(', ')}`,
);
}
}
function assertExactArray(actual, expected, label) {
if (!Array.isArray(actual) || JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new ConfigurationError(`${label} does not match the hardcoded publication policy`);
}
}
function assertCanonicalPath(value, label) {
if (
typeof value !== 'string' ||
value.length === 0 ||
value.includes('\\') ||
isAbsolute(value) ||
value.startsWith('/') ||
value.split('/').some((segment) => segment === '' || segment === '.' || segment === '..') ||
posix.normalize(value) !== value
) {
throw new ConfigurationError(`${label} must be a canonical repository-relative path`);
}
}
function assertSortedUnique(values, label, pathSelector = (value) => value) {
if (!Array.isArray(values)) {
throw new ConfigurationError(`${label} must be an array`);
}
const paths = values.map(pathSelector);
const expected = [...new Set(paths)].sort(compareOrdinal);
if (JSON.stringify(paths) !== JSON.stringify(expected)) {
throw new ConfigurationError(`${label} must be sorted by path and contain no duplicates`);
}
}
function loadBaseline() {
let baseline;
try {
baseline = JSON.parse(readFileSync(BASELINE_PATH, 'utf-8'));
} catch (error) {
throw new ConfigurationError(`cannot read baseline ${BASELINE_PATH}: ${error.message}`);
}
assertExactKeys(
baseline,
[
'schemaVersion',
'mapping',
'parityPaths',
'intentionalAdaptations',
'knownReviewedBlockers',
'unreviewedDifferences',
'forbiddenExports',
],
'baseline',
);
if (baseline.schemaVersion !== 1) {
throw new ConfigurationError('baseline.schemaVersion must be 1');
}
assertExactKeys(
baseline.mapping,
['canonical', 'oss', 'ignoredDirectories', 'ignoredFileSuffixes'],
'baseline.mapping',
);
for (const key of Object.keys(MAPPING)) {
const expected = MAPPING[key];
if (Array.isArray(expected)) {
assertExactArray(baseline.mapping[key], expected, `baseline.mapping.${key}`);
} else if (baseline.mapping[key] !== expected) {
throw new ConfigurationError(`baseline.mapping.${key} does not match the hardcoded mapping`);
}
}
assertExactKeys(
baseline.forbiddenExports,
['paths', 'pathPrefixes', 'markers'],
'baseline.forbiddenExports',
);
assertExactArray(
baseline.forbiddenExports.paths,
FORBIDDEN_EXPORTS.paths,
'baseline.forbiddenExports.paths',
);
assertExactArray(
baseline.forbiddenExports.pathPrefixes,
FORBIDDEN_EXPORTS.pathPrefixes,
'baseline.forbiddenExports.pathPrefixes',
);
assertExactArray(
baseline.forbiddenExports.markers,
FORBIDDEN_EXPORTS.markers,
'baseline.forbiddenExports.markers',
);
assertSortedUnique(baseline.parityPaths, 'baseline.parityPaths', (entry) => entry?.path);
for (const [index, entry] of baseline.parityPaths.entries()) {
const label = `baseline.parityPaths[${index}]`;
assertExactKeys(entry, ['path', 'sha256'], label);
assertCanonicalPath(entry.path, `${label}.path`);
if (!HASH_PATTERN.test(entry.sha256)) {
throw new ConfigurationError(`${label}.sha256 must be a lowercase SHA-256 hash`);
}
}
assertSortedUnique(
baseline.intentionalAdaptations,
'baseline.intentionalAdaptations',
(entry) => entry?.path,
);
for (const [index, entry] of baseline.intentionalAdaptations.entries()) {
const label = `baseline.intentionalAdaptations[${index}]`;
assertExactKeys(entry, ['path', 'kinds', 'canonicalSha256', 'ossSha256'], label);
assertCanonicalPath(entry.path, `${label}.path`);
assertSortedUnique(entry.kinds, `${label}.kinds`);
if (entry.kinds.length === 0 || entry.kinds.some((kind) => !VALID_ADAPTATION_KINDS.has(kind))) {
throw new ConfigurationError(`${label}.kinds contains an unsupported adaptation kind`);
}
if (!HASH_PATTERN.test(entry.canonicalSha256) || !HASH_PATTERN.test(entry.ossSha256)) {
throw new ConfigurationError(`${label} must contain lowercase SHA-256 hashes`);
}
if (entry.canonicalSha256 === entry.ossSha256) {
throw new ConfigurationError(`${label} must describe an actual adaptation, not parity`);
}
}
assertSortedUnique(
baseline.knownReviewedBlockers,
'baseline.knownReviewedBlockers',
(entry) => entry?.path,
);
for (const [index, entry] of baseline.knownReviewedBlockers.entries()) {
const label = `baseline.knownReviewedBlockers[${index}]`;
assertExactKeys(entry, ['path', 'state', 'disposition'], label);
assertCanonicalPath(entry.path, `${label}.path`);
if (!VALID_STATES.has(entry.state)) {
throw new ConfigurationError(`${label}.state is unsupported`);
}
if (!VALID_DISPOSITIONS.has(entry.disposition)) {
throw new ConfigurationError(`${label}.disposition is unsupported`);
}
}
assertSortedUnique(
baseline.unreviewedDifferences,
'baseline.unreviewedDifferences',
(entry) => entry?.path,
);
for (const [index, entry] of baseline.unreviewedDifferences.entries()) {
const label = `baseline.unreviewedDifferences[${index}]`;
assertExactKeys(entry, ['path', 'state'], label);
assertCanonicalPath(entry.path, `${label}.path`);
if (!VALID_STATES.has(entry.state)) {
throw new ConfigurationError(`${label}.state is unsupported`);
}
}
const classified = [
...baseline.parityPaths.map((entry) => entry.path),
...baseline.intentionalAdaptations.map((entry) => entry.path),
...baseline.knownReviewedBlockers.map((entry) => entry.path),
...baseline.unreviewedDifferences.map((entry) => entry.path),
];
const duplicate = classified.find((path, index) => classified.indexOf(path) !== index);
if (duplicate) {
throw new ConfigurationError(`baseline classifies ${duplicate} more than once`);
}
const privateCategory = classified.find((path) => isPrivateExclusion(path));
if (privateCategory) {
throw new ConfigurationError(`baseline must not reclassify private exclusion ${privateCategory}`);
}
return baseline;
}
function resolveGitRoot(candidate, label) {
let resolved;
try {
resolved = realpathSync(candidate);
const root = execFileSync('git', ['-C', resolved, 'rev-parse', '--show-toplevel'], {
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true,
}).trim();
return realpathSync(root);
} catch (error) {
throw new ConfigurationError(`${label} is not a readable Git worktree: ${candidate}`);
}
}
function readUtf8Normalized(filePath) {
let text;
try {
text = decoder.decode(readFileSync(filePath));
} catch (error) {
throw new ConfigurationError(`cannot read UTF-8 source file ${filePath}: ${error.message}`);
}
return text.replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
}
function inventory(root, label) {
let rootStat;
try {
rootStat = lstatSync(root);
} catch {
throw new ConfigurationError(`${label} mapped source directory is missing: ${root}`);
}
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
throw new ConfigurationError(`${label} mapped source must be a real directory: ${root}`);
}
const files = new Map();
const casing = new Map();
const walk = (directory, prefix = '') => {
let entries;
try {
entries = readdirSync(directory, { withFileTypes: true }).sort((left, right) =>
compareOrdinal(left.name, right.name),
);
} catch (error) {
throw new ConfigurationError(`cannot inventory ${label} directory ${directory}: ${error.message}`);
}
for (const entry of entries) {
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
const absolutePath = join(directory, entry.name);
const stat = lstatSync(absolutePath);
if (stat.isSymbolicLink()) {
throw new ConfigurationError(`${label} mapped inventory contains symlink: ${relativePath}`);
}
if (stat.isDirectory()) {
if (!MAPPING.ignoredDirectories.includes(entry.name)) {
walk(absolutePath, relativePath);
}
continue;
}
if (!stat.isFile()) {
throw new ConfigurationError(`${label} mapped inventory contains special node: ${relativePath}`);
}
if (MAPPING.ignoredFileSuffixes.some((suffix) => relativePath.endsWith(suffix))) {
continue;
}
const folded = relativePath.toLowerCase();
const previous = casing.get(folded);
if (previous && previous !== relativePath) {
throw new ConfigurationError(
`${label} mapped inventory has case collision: ${previous} / ${relativePath}`,
);
}
casing.set(folded, relativePath);
const text = readUtf8Normalized(absolutePath);
files.set(relativePath, {
hash: createHash('sha256').update(text, 'utf-8').digest('hex'),
text,
});
}
};
walk(root);
if (files.size === 0) {
throw new ConfigurationError(`${label} mapped inventory is empty: ${root}`);
}
return files;
}
function resolveMappedRoot(repositoryRoot, relativePath, label) {
let current = repositoryRoot;
for (const segment of relativePath.split('/')) {
current = join(current, segment);
let stat;
try {
stat = lstatSync(current);
} catch {
throw new ConfigurationError(`${label} mapped path component is missing: ${current}`);
}
if (stat.isSymbolicLink()) {
throw new ConfigurationError(`${label} mapped path contains ancestor symlink: ${current}`);
}
if (!stat.isDirectory()) {
throw new ConfigurationError(`${label} mapped path component is not a directory: ${current}`);
}
}
return current;
}
function isPrivateExclusion(path) {
if (FORBIDDEN_EXPORTS.paths.includes(path)) return true;
return FORBIDDEN_EXPORTS.pathPrefixes.some((prefix) =>
prefix.endsWith('/') ? path.startsWith(prefix) : path === prefix || path.startsWith(`${prefix}/`),
);
}
function observedState(canonical, oss) {
if (canonical && oss) return canonical.hash === oss.hash ? 'equal' : 'different';
if (canonical) return 'only-canonical';
if (oss) return 'only-oss';
return 'absent';
}
function hasForbiddenMarker(text, token) {
const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return new RegExp(`\\b${escaped}\\b`, 'i').test(text);
}
function gitReceipt(root, scopedPaths) {
const head = execFileSync('git', ['-C', root, 'rev-parse', 'HEAD'], {
encoding: 'utf-8',
windowsHide: true,
}).trim();
const status = execFileSync(
'git',
['-C', root, 'status', '--porcelain=v1', '--untracked-files=all'],
{ encoding: 'utf-8', windowsHide: true },
).trim();
const scopedStatus = execFileSync(
'git',
['-C', root, 'status', '--porcelain=v1', '--untracked-files=all', '--', ...scopedPaths],
{ encoding: 'utf-8', windowsHide: true },
).trim();
return { head, dirty: status.length > 0, scopedStatus };
}
function showSection(title, entries) {
console.log(`${title}: ${entries.length}`);
for (const entry of [...entries].sort(compareOrdinal)) {
console.log(` ${entry}`);
}
}
function run() {
if (process.argv.length > 3) {
throw new ConfigurationError('usage: node scripts/oss-drift-check.mjs [path-to-oss-checkout]');
}
const baseline = loadBaseline();
const canonicalRoot = resolveGitRoot(join(SCRIPT_DIR, '..'), 'canonical repository');
const requestedOss =
process.argv[2] ??
(process.env.OSS_HIVE_MIND_DIR?.trim() || resolve(canonicalRoot, '..', 'hive-mind'));
const ossRoot = resolveGitRoot(resolve(process.cwd(), requestedOss), 'OSS repository');
if (canonicalRoot.toLowerCase() === ossRoot.toLowerCase()) {
throw new ConfigurationError('canonical and OSS repositories must be different Git worktrees');
}
const canonicalFiles = inventory(
resolveMappedRoot(canonicalRoot, MAPPING.canonical, 'canonical'),
'canonical',
);
const ossFiles = inventory(resolveMappedRoot(ossRoot, MAPPING.oss, 'OSS'), 'OSS');
const crossRepoCasing = new Map();
for (const path of [...canonicalFiles.keys(), ...ossFiles.keys()]) {
const folded = path.toLowerCase();
const previous = crossRepoCasing.get(folded);
if (previous && previous !== path) {
throw new ConfigurationError(`mapped inventories have case collision: ${previous} / ${path}`);
}
crossRepoCasing.set(folded, path);
}
const parity = new Map(baseline.parityPaths.map((entry) => [entry.path, entry]));
const adaptations = new Map(baseline.intentionalAdaptations.map((entry) => [entry.path, entry]));
const blockers = new Map(baseline.knownReviewedBlockers.map((entry) => [entry.path, entry]));
const knownUnreviewed = new Map(
baseline.unreviewedDifferences.map((entry) => [entry.path, entry]),
);
const reviewedAdaptations = [];
const intentionalExclusions = [];
const knownBlockers = [];
const unreviewed = [];
const forbidden = [];
const observedPaths = new Set();
const union = [...new Set([...canonicalFiles.keys(), ...ossFiles.keys()])].sort(compareOrdinal);
for (const path of union) {
observedPaths.add(path);
const canonical = canonicalFiles.get(path);
const oss = ossFiles.get(path);
const state = observedState(canonical, oss);
if (isPrivateExclusion(path)) {
if (oss) {
forbidden.push(`FORBIDDEN-OSS-CONTENT ${path}`);
} else if (canonical) {
intentionalExclusions.push(path);
}
continue;
}
const parityEntry = parity.get(path);
if (parityEntry) {
if (
state === 'equal' &&
canonical.hash === parityEntry.sha256 &&
oss.hash === parityEntry.sha256
) {
continue;
}
unreviewed.push(`BASELINE-DRIFT ${path} expected equal, observed ${state}`);
continue;
}
const adaptation = adaptations.get(path);
if (adaptation) {
if (
state === 'different' &&
canonical.hash === adaptation.canonicalSha256 &&
oss.hash === adaptation.ossSha256
) {
reviewedAdaptations.push(`${path} [${adaptation.kinds.join(',')}]`);
} else {
unreviewed.push(`BASELINE-DRIFT ${path} reviewed adaptation bytes changed (${state})`);
}
continue;
}
const blocker = blockers.get(path);
if (blocker) {
if (state === blocker.state) {
knownBlockers.push(`${path} (${state}; ${blocker.disposition})`);
} else {
unreviewed.push(
`BASELINE-DRIFT ${path} expected ${blocker.state}, observed ${state}`,
);
}
continue;
}
const unreviewedEntry = knownUnreviewed.get(path);
if (unreviewedEntry) {
if (state === unreviewedEntry.state) {
unreviewed.push(`${path} (${state})`);
} else {
unreviewed.push(
`BASELINE-DRIFT ${path} expected ${unreviewedEntry.state}, observed ${state}`,
);
}
continue;
}
unreviewed.push(`NEW ${path} (${state})`);
}
for (const path of [
...parity.keys(),
...adaptations.keys(),
...blockers.keys(),
...knownUnreviewed.keys(),
]) {
if (!observedPaths.has(path)) {
unreviewed.push(`STALE-BASELINE ${path} (absent)`);
}
}
for (const marker of FORBIDDEN_EXPORTS.markers) {
const file = ossFiles.get(marker.path);
if (!file) continue;
if (hasForbiddenMarker(file.text, marker.token)) {
forbidden.push(`FORBIDDEN-OSS-MARKER ${marker.path}:${marker.token}`);
}
}
const canonicalReceipt = gitReceipt(canonicalRoot, [
MAPPING.canonical,
'scripts/oss-drift-check.mjs',
'scripts/oss-drift-baseline.json',
'scripts/oss-drift-check.sh',
]);
const ossReceipt = gitReceipt(ossRoot, [MAPPING.oss]);
if (canonicalReceipt.scopedStatus) {
unreviewed.push('SCOPED-DIRTY canonical checker, baseline, wrapper, or mapped source');
}
if (ossReceipt.scopedStatus) {
unreviewed.push('SCOPED-DIRTY OSS mapped source');
}
console.log(
`CANONICAL ${canonicalReceipt.head} ${canonicalReceipt.dirty ? 'dirty' : 'clean'} ${canonicalRoot}`,
);
console.log(`OSS ${ossReceipt.head} ${ossReceipt.dirty ? 'dirty' : 'clean'} ${ossRoot}`);
console.log(`PARITY: ${parity.size}`);
showSection('REVIEWED ADAPTATIONS', reviewedAdaptations);
showSection('INTENTIONAL OSS EXCLUSIONS', intentionalExclusions);
showSection('KNOWN REVIEWED BLOCKERS', knownBlockers);
showSection('UNREVIEWED DIFFERENCES', unreviewed);
showSection('FORBIDDEN EXPORTS', forbidden);
const blocked = knownBlockers.length || unreviewed.length || forbidden.length;
if (blocked) {
console.log(
'[oss-drift-check] release blocked: reconcile in the canonical monorepo, then use a curated forward-port; never publish a raw split.',
);
}
return blocked ? 1 : 0;
}
try {
process.exitCode = run();
} catch (error) {
if (error instanceof ConfigurationError) {
console.error(`[oss-drift-check] CONFIGURATION ERROR: ${error.message}`);
} else {
console.error(`[oss-drift-check] UNEXPECTED ERROR: ${error?.stack ?? error}`);
}
process.exitCode = 2;
}