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.
1402 lines
65 KiB
JavaScript
1402 lines
65 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Executed-plan return + fs adapter seam — failing-first tests.
|
|
*
|
|
* #2874 (epic #2866 Phase 5), governed by ADR-58
|
|
* (docs/adr/58-runtime-install-policy-module.md).
|
|
*
|
|
* Design: .msd/phase/feat-2874-executed-plan-return/40-design.md
|
|
* Test matrix: .msd/phase/feat-2874-executed-plan-return/50-test-matrix.md
|
|
*
|
|
* This file implements the Red-first order's rows 1-3 from 50-test-matrix.md:
|
|
* - E3 (section E, "Executed-plan return shape"): the opencode-family
|
|
* early return must ALSO return an executed plan, not `undefined`.
|
|
* - E13 (section E): every runtime in the capability registry must return
|
|
* something other than `undefined` — the completeness sweep proving the
|
|
* contract has no per-runtime holes.
|
|
* - F2 (section F, "Fs adapter seam"): a full install driven by an
|
|
* injected fake adapter must touch zero real filesystem paths.
|
|
*
|
|
* All three are RED against the current tree: `installRuntimeArtifacts`
|
|
* (src/install-engine.cts:750) still returns `void` and accepts no `deps`/
|
|
* adapter parameter to route IO through. No production code is touched here
|
|
* — this package is tests only.
|
|
*/
|
|
|
|
process.env.MSD_TEST_MODE = '1';
|
|
|
|
const { test, describe } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const crypto = require('node:crypto');
|
|
|
|
const fc = require('fast-check');
|
|
|
|
const { createTempDir, cleanup } = require('./helpers.cjs');
|
|
|
|
const { installRuntimeArtifacts, hasExistingSymlinkBetween } = require('../msd-core/bin/lib/install-engine.cjs');
|
|
const registry = require('../msd-core/bin/lib/capability-registry.cjs');
|
|
const { loadSkillsManifest, resolveProfile } = require('../msd-core/bin/lib/install-profiles.cjs');
|
|
const runtimeArtifactLayout = require('../msd-core/bin/lib/runtime-artifact-layout.cjs');
|
|
const runtimeArtifactInstallPlan = require('../msd-core/bin/lib/runtime-artifact-install-plan.cjs');
|
|
const { withInstallFs } = require('../msd-core/bin/lib/install-fs-adapter.cjs');
|
|
const commandRoster = require('../msd-core/bin/lib/command-roster.cjs');
|
|
const slashCommandTransformer = require('../scripts/fix-slash-commands.cjs');
|
|
|
|
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'msd');
|
|
const REAL_PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
const MANIFEST = loadSkillsManifest(REAL_COMMANDS_DIR);
|
|
const RESOLVED_CORE = resolveProfile({ modes: ['core'], manifest: MANIFEST });
|
|
const RESOLVED_FULL = resolveProfile({ modes: ['full'], manifest: MANIFEST });
|
|
const TEST_ATTRIBUTION = () => 'Co-Authored-By: Test <t@example.com>';
|
|
|
|
/**
|
|
* Sandbox HOME/USERPROFILE for the duration of a test. Some runtimes (e.g.
|
|
* codex) resolve a kind's `home` via os.homedir(); without this, an in-process
|
|
* install would write into the developer's real home directory.
|
|
*
|
|
* #3712: promoted to tests/helpers.cjs, from the byte-identical copy that used
|
|
* to live here. It now also sets the sandbox marker src/real-home-guard.cts
|
|
* needs to stay permissive on hosts with no readable passwd entry.
|
|
*/
|
|
const { sandboxHome } = require('./helpers.cjs');
|
|
|
|
// ─── E3 — the opencode-family early return (matrix row E3) ──────────────────
|
|
|
|
describe('installRuntimeArtifacts — E3: opencode-family early return', () => {
|
|
test('family install still returns a plan', (t) => {
|
|
const configDir = createTempDir('msd-e3-opencode-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts('opencode', configDir, 'global', RESOLVED_CORE);
|
|
|
|
assert.notStrictEqual(
|
|
result,
|
|
undefined,
|
|
'E3: the combinedFamilyInstall early return (install-engine.cts:774) must return an ' +
|
|
'executed plan, not undefined — a whole runtime family returning undefined is a hole ' +
|
|
'in the contract, not an exemption (40-design.md behavior table row 2)',
|
|
);
|
|
});
|
|
});
|
|
|
|
// ─── E13 — the all-runtimes sweep (matrix row E13) ───────────────────────────
|
|
|
|
describe('installRuntimeArtifacts — E13: no runtime returns undefined', () => {
|
|
const RUNTIMES = Object.keys(registry.runtimes);
|
|
|
|
test('registry enumerates at least one runtime to sweep', () => {
|
|
assert.ok(RUNTIMES.length > 0, 'capability-registry.cjs runtimes must be non-empty');
|
|
});
|
|
|
|
for (const runtime of RUNTIMES) {
|
|
test(`${runtime}: installRuntimeArtifacts does not return undefined`, (t) => {
|
|
const configDir = createTempDir(`msd-e13-${runtime}-`);
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts(runtime, configDir, 'global', RESOLVED_CORE);
|
|
|
|
assert.notStrictEqual(
|
|
result,
|
|
undefined,
|
|
`E13: ${runtime} returned undefined — every runtime in the registry must return an ` +
|
|
'executed plan (40-design.md: "Legitimate undefined returns: none after this phase. ' +
|
|
'If any path can still return undefined, that path is a defect, not an exemption.")',
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
// ─── F2 — zero real filesystem contact (matrix row F2) ───────────────────────
|
|
|
|
// The full write+read surface installRuntimeArtifacts's call tree is known to
|
|
// reach once every gap named in the #2874 follow-up round is closed: the
|
|
// direct mkdirSync/existsSync/rmSync calls, _copyStaged's readdirSync/cpSync/
|
|
// copyFileSync/mkdirSync, _removeMsdEntries's directory scan+delete,
|
|
// _snapshotDir/_restoreDir's read/write of preserved skill dirs, the
|
|
// symlink-escape guard's lstatSync/realpathSync probes, commonjs-marker.cts's
|
|
// lstatSync/writeFileSync/unlinkSync, and installer-migrations.cts's
|
|
// existsSync/readFileSync/openSync/readSync/closeSync (readInstallManifest,
|
|
// classifyArtifact, sha256File — sha256File streams via openSync/readSync/
|
|
// closeSync, restored after a brief round-trip through readFileSync broke
|
|
// tests/installer-migrations.test.cjs's large-file-streaming contract; the
|
|
// fake below implements all three against its store so a fake-adapter
|
|
// install still never touches real fs for hashing).
|
|
//
|
|
// mkdtempSync stays poisoned as a genuine tripwire, not a reachable case:
|
|
// mkInstallTempDir (install-fs-adapter.cts) only ever calls real
|
|
// `fs.mkdtempSync` when `current === REAL_ADAPTER` (no adapter injected at
|
|
// all) — a fake-adapter call always makes `current` a distinct merged
|
|
// object, so it takes the synthesize-name-and-mkdirSync branch instead and
|
|
// never reaches this poison. If this ever fires, `current`'s identity check
|
|
// broke, not a documented gap.
|
|
//
|
|
// One exception this poison list does NOT cover: readMsdCommandNames
|
|
// (command-roster.cts) reads the PACKAGE'S OWN commands/msd/ source tree via
|
|
// real fs.readdirSync — deliberately unrouted (see install-fs-adapter.cts's
|
|
// module doc, "DELIBERATELY NOT ROUTED"). `poisonRealFsAgainstDestination`
|
|
// below allows real calls scoped to that known package-source root and
|
|
// poisons everything else, rather than poisoning every real fs call
|
|
// wholesale regardless of path.
|
|
const REAL_FS_WRITE_SURFACE = [
|
|
'mkdirSync', 'existsSync', 'rmSync', 'readdirSync',
|
|
'cpSync', 'copyFileSync', 'readFileSync', 'writeFileSync', 'lstatSync',
|
|
'realpathSync', 'unlinkSync', 'rmdirSync',
|
|
'mkdtempSync', 'openSync', 'readSync', 'closeSync',
|
|
];
|
|
|
|
// The provider resolver probes upward through the executing package before it
|
|
// reaches commands/msd and agents. All read-only package-tree probes are valid
|
|
// real IO while a fake DESTINATION adapter is active; destination paths remain
|
|
// poisoned below.
|
|
const PACKAGE_SOURCE_ROOTS = [path.resolve(__dirname, '..')];
|
|
|
|
function isPackageSourcePath(resolvedPath) {
|
|
return PACKAGE_SOURCE_ROOTS.some(
|
|
(root) => resolvedPath === root || resolvedPath.startsWith(root + path.sep),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* F2's real-fs poisoning, derived from the rule (40-design.md "Known
|
|
* limits"/install-fs-adapter.cts's module doc) rather than aligned with it by
|
|
* coincidence: a real fs call against the install DESTINATION is a failure —
|
|
* the seam exists precisely so a fake adapter can intercept those — but a
|
|
* real call against the package's OWN source tree (commands/msd/, agents/)
|
|
* is expected and allowed, because that read is deliberately unrouted
|
|
* (readMsdCommandNames et al.). Poisoning every real fs method wholesale,
|
|
* regardless of path, makes a correct install fail for the wrong reason.
|
|
*
|
|
* Returns a Map<method, count> of package-source hits, so a caller can
|
|
* assert POSITIVELY that the expected package-source read actually happened
|
|
* — proving the boundary was exercised, not merely tolerated.
|
|
*/
|
|
function poisonRealFsAgainstDestination(t, label) {
|
|
const packageSourceHits = new Map();
|
|
for (const method of REAL_FS_WRITE_SURFACE) {
|
|
const original = fs[method].bind(fs);
|
|
t.mock.method(fs, method, (...args) => {
|
|
const target = args[0];
|
|
const resolved = (typeof target === 'string' || target instanceof URL || Buffer.isBuffer(target))
|
|
? path.resolve(String(target))
|
|
: null;
|
|
if (resolved !== null && isPackageSourcePath(resolved)) {
|
|
packageSourceHits.set(method, (packageSourceHits.get(method) ?? 0) + 1);
|
|
return original(...args);
|
|
}
|
|
throw new Error(
|
|
`F2${label}: real fs.${method}() was reached against a non-package-source path ` +
|
|
`(${resolved ?? String(target)}) during an install driven by an injected fake adapter`,
|
|
);
|
|
});
|
|
}
|
|
return packageSourceHits;
|
|
}
|
|
|
|
/**
|
|
* A genuinely functional in-memory filesystem, not a set of no-op stubs —
|
|
* required to drive the branches F2 now exercises (opencode-family legacy-dir
|
|
* migration, a nativePlugin runtime, a retiredArtifacts runtime) far enough
|
|
* to reach commonjs-marker.cts and installer-migrations.cts's routed
|
|
* classifyArtifact/readInstallManifest, not just the happy path's first
|
|
* existsSync check. Every method operates against one flat `Map<absPath,
|
|
* entry>` store; `readdirSync` derives listings by prefix-scanning the same
|
|
* store (an entry that readdirSync reports a directory contains is, by
|
|
* construction, also existsSync-true at that exact path — same invariant a
|
|
* real filesystem holds).
|
|
*
|
|
* @param seed - Array<[absPath, {type:'file'|'dir', content?:string|Buffer}]>
|
|
* pre-populated entries.
|
|
*/
|
|
function createFakeInstallFs(seed = []) {
|
|
const store = new Map();
|
|
for (const [p, entry] of seed) store.set(path.normalize(String(p)), entry);
|
|
const fdTable = new Map();
|
|
let nextFd = 1;
|
|
|
|
const norm = (p) => path.normalize(String(p));
|
|
const childPrefix = (dir) => {
|
|
const n = norm(dir);
|
|
return n.endsWith(path.sep) ? n : n + path.sep;
|
|
};
|
|
const enoent = (p) => {
|
|
const err = new Error(`ENOENT: no such file or directory, '${p}'`);
|
|
err.code = 'ENOENT';
|
|
return err;
|
|
};
|
|
|
|
const fakeFs = {
|
|
existsSync: (p) => store.has(norm(p)),
|
|
lstatSync: (p) => {
|
|
const e = store.get(norm(p));
|
|
if (!e) throw enoent(p);
|
|
return {
|
|
isFile: () => e.type === 'file',
|
|
isDirectory: () => e.type === 'dir',
|
|
isSymbolicLink: () => e.type === 'symlink',
|
|
};
|
|
},
|
|
mkdirSync: (p) => { store.set(norm(p), { type: 'dir' }); return undefined; },
|
|
rmSync: (p) => {
|
|
const n = norm(p);
|
|
store.delete(n);
|
|
const prefix = childPrefix(n);
|
|
for (const k of [...store.keys()]) if (k.startsWith(prefix)) store.delete(k);
|
|
},
|
|
unlinkSync: (p) => {
|
|
const n = norm(p);
|
|
if (!store.has(n)) throw enoent(p);
|
|
store.delete(n);
|
|
},
|
|
rmdirSync: (p) => { store.delete(norm(p)); },
|
|
readdirSync: (p, opts) => {
|
|
const prefix = childPrefix(p);
|
|
const names = new Set();
|
|
for (const k of store.keys()) {
|
|
if (!k.startsWith(prefix)) continue;
|
|
const rest = k.slice(prefix.length);
|
|
const sepIdx = rest.indexOf(path.sep);
|
|
const name = sepIdx === -1 ? rest : rest.slice(0, sepIdx);
|
|
if (name) names.add(name);
|
|
}
|
|
const arr = [...names];
|
|
if (opts && opts.withFileTypes) {
|
|
return arr.map((name) => {
|
|
const full = norm(path.join(String(p), name));
|
|
const e = store.get(full);
|
|
return {
|
|
name,
|
|
isFile: () => (e ? e.type === 'file' : false),
|
|
isDirectory: () => (e ? e.type === 'dir' : true),
|
|
};
|
|
});
|
|
}
|
|
return arr;
|
|
},
|
|
readFileSync: (p, encoding) => {
|
|
const e = store.get(norm(p));
|
|
if (!e || e.type !== 'file') throw enoent(p);
|
|
const buf = Buffer.isBuffer(e.content) ? e.content : Buffer.from(e.content ?? '', 'utf8');
|
|
return encoding ? buf.toString(encoding) : buf;
|
|
},
|
|
// sha256File (installer-migrations.cts) streams via openSync/readSync/
|
|
// closeSync instead of readFileSync (large-file hashing must not buffer
|
|
// the whole file — tests/installer-migrations.test.cjs pins this). fdTable
|
|
// maps a synthetic fd to {buf, pos} so this fake never needs a real fd.
|
|
openSync: (p) => {
|
|
const e = store.get(norm(p));
|
|
if (!e || e.type !== 'file') throw enoent(p);
|
|
const buf = Buffer.isBuffer(e.content) ? e.content : Buffer.from(e.content ?? '', 'utf8');
|
|
const fd = nextFd++;
|
|
fdTable.set(fd, { buf, pos: 0 });
|
|
return fd;
|
|
},
|
|
readSync: (fd, buffer, offset, length, position) => {
|
|
const entry = fdTable.get(fd);
|
|
if (!entry) {
|
|
const err = new Error(`EBADF: bad file descriptor, read (fake fd ${fd})`);
|
|
err.code = 'EBADF';
|
|
throw err;
|
|
}
|
|
const readAt = position === null || position === undefined ? entry.pos : position;
|
|
const bytesToRead = Math.max(0, Math.min(length, entry.buf.length - readAt));
|
|
entry.buf.copy(buffer, offset, readAt, readAt + bytesToRead);
|
|
if (position === null || position === undefined) entry.pos += bytesToRead;
|
|
return bytesToRead;
|
|
},
|
|
closeSync: (fd) => { fdTable.delete(fd); },
|
|
writeFileSync: (p, data, opts) => {
|
|
// Emulate `{ flag: 'wx' }` (exclusive create): REAL_ADAPTER.writeFileSync
|
|
// (install-fs-adapter.cts:138) passes `opts` straight through to real
|
|
// `fs.writeFileSync`, which throws EEXIST for `wx` against an existing
|
|
// path. A fake that silently overwrote here would certify something
|
|
// the real implementation refuses — see commonjs-marker.cts's
|
|
// `ensureCommonJsMarker`, which relies on `wx` to close the
|
|
// classify-then-write gap.
|
|
const flag = typeof opts === 'object' && opts !== null ? opts.flag : undefined;
|
|
const n = norm(p);
|
|
if (flag === 'wx' && store.has(n)) {
|
|
const err = new Error(`EEXIST: file already exists, open '${p}'`);
|
|
err.code = 'EEXIST';
|
|
throw err;
|
|
}
|
|
store.set(n, { type: 'file', content: data });
|
|
},
|
|
copyFileSync: (src, dest) => {
|
|
const e = store.get(norm(src));
|
|
store.set(norm(dest), { type: 'file', content: e ? e.content : Buffer.alloc(0) });
|
|
},
|
|
cpSync: (src, dest) => {
|
|
const sn = norm(src);
|
|
const dn = norm(dest);
|
|
const e = store.get(sn);
|
|
if (e) store.set(dn, { ...e });
|
|
const prefix = childPrefix(sn);
|
|
for (const [k, v] of [...store.entries()]) {
|
|
if (k.startsWith(prefix)) store.set(dn + k.slice(sn.length), { ...v });
|
|
}
|
|
},
|
|
realpathSync: (p) => norm(p),
|
|
};
|
|
fakeFs._store = store;
|
|
return fakeFs;
|
|
}
|
|
|
|
// ─── createFakeInstallFs — wx exclusive-create emulation ────────────────────
|
|
//
|
|
// REAL_ADAPTER.writeFileSync (install-fs-adapter.cts:138) passes `opts`
|
|
// through untouched to real fs.writeFileSync, so `{ flag: 'wx' }` throws
|
|
// EEXIST against an existing target (commonjs-marker.cts's
|
|
// ensureCommonJsMarker relies on exactly this to close the
|
|
// classify-then-write TOCTOU gap). A fake that ignored `opts` would silently
|
|
// overwrite where the real adapter refuses — this covers the emulation
|
|
// itself rather than assuming it.
|
|
describe('createFakeInstallFs — wx exclusive-create emulation', () => {
|
|
test('refuses an exclusive create against an existing path (EEXIST)', () => {
|
|
const target = path.join(os.tmpdir(), 'msd-fake-wx-existing.txt');
|
|
const fakeFs = createFakeInstallFs([[target, { type: 'file', content: 'original' }]]);
|
|
|
|
assert.throws(
|
|
() => fakeFs.writeFileSync(target, 'clobber', { flag: 'wx' }),
|
|
(err) => err.code === 'EEXIST',
|
|
'wx write against an existing fake-store path must throw EEXIST, matching real fs.writeFileSync',
|
|
);
|
|
assert.strictEqual(
|
|
fakeFs.readFileSync(target, 'utf8'),
|
|
'original',
|
|
'a refused wx write must leave the existing content untouched',
|
|
);
|
|
});
|
|
|
|
test('allows an exclusive create against an absent path', () => {
|
|
const target = path.join(os.tmpdir(), 'msd-fake-wx-absent.txt');
|
|
const fakeFs = createFakeInstallFs();
|
|
|
|
fakeFs.writeFileSync(target, 'created', { flag: 'wx' });
|
|
|
|
assert.strictEqual(fakeFs.readFileSync(target, 'utf8'), 'created');
|
|
});
|
|
});
|
|
|
|
/** sha256 hex digest matching installer-migrations.cts's sha256File — used to
|
|
* seed a manifest entry that classifies a fake file as 'managed-pristine'. */
|
|
function sha256Hex(content) {
|
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
}
|
|
|
|
function fakeInstalledCorpusSeed(configDir) {
|
|
const coreDir = path.join(configDir, 'msd-core');
|
|
const commandsDir = path.join(coreDir, 'commands');
|
|
const commandsMsdDir = path.join(commandsDir, 'msd');
|
|
const agentsDir = path.join(coreDir, 'agents');
|
|
return [
|
|
[configDir, { type: 'dir' }],
|
|
[coreDir, { type: 'dir' }],
|
|
[commandsDir, { type: 'dir' }],
|
|
[commandsMsdDir, { type: 'dir' }],
|
|
[path.join(commandsMsdDir, 'fixture.md'), { type: 'file', content: '# Fixture command\n' }],
|
|
[agentsDir, { type: 'dir' }],
|
|
[path.join(agentsDir, 'fixture-agent.md'), { type: 'file', content: '# Fixture agent\n' }],
|
|
];
|
|
}
|
|
|
|
function fakeInstalledCorpusManifestFiles() {
|
|
return {
|
|
'msd-core/commands/msd/fixture.md': sha256Hex('# Fixture command\n'),
|
|
'msd-core/agents/fixture-agent.md': sha256Hex('# Fixture agent\n'),
|
|
};
|
|
}
|
|
|
|
describe('installRuntimeArtifacts — F2: fake-adapter install touches no real filesystem', () => {
|
|
test('fake-adapter install touches no real filesystem (claude, skills-only)', (t) => {
|
|
// Every real fs method this call tree could reach is poisoned BY PATH
|
|
// (see poisonRealFsAgainstDestination) for the duration of this test via
|
|
// node:test's mock tracker (auto-restored when the test ends — no
|
|
// try/finally in the test body, per CONTRIBUTING.md's "Never use
|
|
// try/finally inside test bodies").
|
|
const packageSourceHits = poisonRealFsAgainstDestination(t, '');
|
|
|
|
const configDir = path.join(os.tmpdir(), `msd-f2-must-not-exist-${crypto.randomUUID()}`);
|
|
const fakeFs = createFakeInstallFs(fakeInstalledCorpusSeed(configDir));
|
|
|
|
// configDir exists only in the fake store — F2 asserts nothing real ever
|
|
// gets written under it. The installed corpus seed is the new provider
|
|
// contract, not destination setup on the host filesystem.
|
|
|
|
const result = installRuntimeArtifacts(
|
|
'claude', configDir, 'global', RESOLVED_CORE, undefined, undefined,
|
|
{ fs: fakeFs, packageRoot: REAL_PACKAGE_ROOT },
|
|
);
|
|
|
|
assert.notStrictEqual(
|
|
result,
|
|
undefined,
|
|
'F2: a fake-adapter install must still return an executed plan (matrix row F1/E1 shape)',
|
|
);
|
|
// No post-hoc fs.existsSync(configDir) check follows: fs.existsSync is
|
|
// one of the poisoned (non-package-source) methods above for the
|
|
// duration of this test, so the proof of "zero real DESTINATION fs
|
|
// contact" IS that installRuntimeArtifacts returned at all without
|
|
// tripping one of the throws — not a probe that would itself have to
|
|
// touch the poisoned surface.
|
|
assert.ok(
|
|
(packageSourceHits.get('readdirSync') ?? 0) > 0,
|
|
'F2: readMsdCommandNames must have read the real, unrouted commands/msd/ package-source ' +
|
|
'tree at least once — proving the poison boundary was exercised, not merely tolerated',
|
|
);
|
|
});
|
|
|
|
test('fake-adapter install touches no real filesystem (opencode-family legacy command/ dir migration)', (t) => {
|
|
poisonRealFsAgainstDestination(t, ' (opencode legacy migration)');
|
|
|
|
const configDir = path.join(os.tmpdir(), `msd-f2-opencode-legacy-${crypto.randomUUID()}`);
|
|
const legacyDir = path.join(configDir, 'command');
|
|
const legacyFile = path.join(legacyDir, 'msd-old-cmd.md');
|
|
const content = '# stale legacy command\n';
|
|
const manifestPath = path.join(configDir, 'msd-file-manifest.json');
|
|
const manifestJson = JSON.stringify({ files: {
|
|
...fakeInstalledCorpusManifestFiles(),
|
|
'command/msd-old-cmd.md': sha256Hex(content),
|
|
} });
|
|
|
|
const fakeFs = createFakeInstallFs([
|
|
...fakeInstalledCorpusSeed(configDir),
|
|
[legacyDir, { type: 'dir' }],
|
|
[legacyFile, { type: 'file', content }],
|
|
[manifestPath, { type: 'file', content: manifestJson }],
|
|
]);
|
|
|
|
const result = installRuntimeArtifacts(
|
|
'opencode', configDir, 'global', RESOLVED_CORE, undefined, undefined,
|
|
{ fs: fakeFs, packageRoot: REAL_PACKAGE_ROOT },
|
|
);
|
|
|
|
assert.notStrictEqual(result, undefined, 'F2 (opencode legacy migration): must still return a plan');
|
|
// The manifest hash matches the seeded content exactly, so
|
|
// _migrateLegacyOpencodeCommandDir's classifyArtifact call must have
|
|
// classified it 'managed-pristine' and unlinked it (real
|
|
// installerMigrations.readInstallManifest/classifyArtifact/sha256File —
|
|
// all routed through installFs() — computed this via the fake, not real
|
|
// fs, or the poisoned methods above would have thrown first).
|
|
assert.strictEqual(
|
|
fakeFs._store.has(path.normalize(legacyFile)),
|
|
false,
|
|
'F2 (opencode legacy migration): the managed-pristine legacy file must have been removed via the fake store',
|
|
);
|
|
});
|
|
|
|
test('fake-adapter install touches no real filesystem (nativePlugin runtime: pi)', (t) => {
|
|
poisonRealFsAgainstDestination(t, ' (nativePlugin)');
|
|
|
|
// Resolve the SAME pluginSrc path _installNativePluginIfDeclared
|
|
// (install-engine.cts) computes for pi's declared nativePlugin, using the
|
|
// real (unrouted, package-own-source) findInstallSourceRoot — this read
|
|
// happens BEFORE the poison mocks above are installed... no: it must
|
|
// happen before `t.mock.method` calls would matter for IT, but
|
|
// findInstallSourceRoot's own walk uses `fs.statSync`, which is NOT on
|
|
// the poisoned list (see install-fs-adapter.cts's module doc — it is
|
|
// deliberately unrouted, real-fs-only, package-source introspection), so
|
|
// resolving this here is safe even after poisoning existsSync et al.
|
|
const commandsMsdDir = REAL_COMMANDS_DIR;
|
|
const repoRoot = path.dirname(path.dirname(commandsMsdDir));
|
|
const nativePlugin = registry.runtimes.pi.runtime.hostBehaviors.nativePlugin;
|
|
assert.ok(nativePlugin && nativePlugin.source, 'pi must declare hostBehaviors.nativePlugin.source (registry drifted)');
|
|
const pluginSrc = path.join(repoRoot, nativePlugin.source);
|
|
|
|
const configDir = path.join(os.tmpdir(), `msd-f2-pi-nativeplugin-${crypto.randomUUID()}`);
|
|
const fakeFs = createFakeInstallFs([
|
|
[pluginSrc, { type: 'file', content: '// fake plugin adapter\n' }],
|
|
]);
|
|
|
|
const result = installRuntimeArtifacts(
|
|
'pi', configDir, 'global', RESOLVED_CORE, undefined, undefined,
|
|
{ fs: fakeFs, packageRoot: REAL_PACKAGE_ROOT },
|
|
);
|
|
|
|
assert.notStrictEqual(result, undefined, 'F2 (nativePlugin): must still return a plan');
|
|
assert.strictEqual(result.postSteps.nativePlugin, true, 'F2 (nativePlugin): postSteps.nativePlugin must be true for pi');
|
|
const destPath = path.join(configDir, nativePlugin.dir, nativePlugin.file);
|
|
assert.strictEqual(
|
|
fakeFs._store.has(path.normalize(destPath)),
|
|
true,
|
|
'F2 (nativePlugin): the plugin file must have been copied via the fake store (copyFileSync routed)',
|
|
);
|
|
const markerPath = path.join(configDir, nativePlugin.dir, 'package.json');
|
|
assert.strictEqual(
|
|
fakeFs._store.has(path.normalize(markerPath)),
|
|
true,
|
|
'F2 (nativePlugin): ensureCommonJsMarker (commonjs-marker.cts) must have written the CommonJS marker via the fake store',
|
|
);
|
|
});
|
|
|
|
test('fake-adapter install touches no real filesystem (retiredArtifacts runtime: cursor)', (t) => {
|
|
const packageSourceHits = poisonRealFsAgainstDestination(t, ' (retiredArtifacts)');
|
|
|
|
const retired = registry.runtimes.cursor.runtime.hostBehaviors.retiredArtifacts;
|
|
assert.ok(Array.isArray(retired) && retired.length > 0, 'cursor must declare hostBehaviors.retiredArtifacts (registry drifted)');
|
|
const { destSubpath, prefix, suffix } = retired[0];
|
|
|
|
const configDir = path.join(os.tmpdir(), `msd-f2-cursor-retired-${crypto.randomUUID()}`);
|
|
const destDir = path.resolve(configDir, destSubpath);
|
|
const staleName = `${prefix}retired-probe${suffix}`;
|
|
const staleFile = path.join(destDir, staleName);
|
|
const content = '# stale retired artifact\n';
|
|
const relPath = `${destSubpath.replace(/\\/g, '/')}/${staleName}`;
|
|
const manifestPath = path.join(configDir, 'msd-file-manifest.json');
|
|
const manifestJson = JSON.stringify({ files: {
|
|
...fakeInstalledCorpusManifestFiles(),
|
|
[relPath]: sha256Hex(content),
|
|
} });
|
|
|
|
const fakeFs = createFakeInstallFs([
|
|
...fakeInstalledCorpusSeed(configDir),
|
|
[destDir, { type: 'dir' }],
|
|
[staleFile, { type: 'file', content }],
|
|
[manifestPath, { type: 'file', content: manifestJson }],
|
|
]);
|
|
|
|
const result = installRuntimeArtifacts(
|
|
'cursor', configDir, 'global', RESOLVED_CORE, undefined, undefined,
|
|
{ fs: fakeFs },
|
|
);
|
|
|
|
assert.notStrictEqual(result, undefined, 'F2 (retiredArtifacts): must still return a plan');
|
|
// manifest hash matches the seeded content exactly -> classifyArtifact
|
|
// must classify 'managed-pristine' -> pruneRetiredRuntimeArtifacts
|
|
// (retired-artifact-cleanup.cts, routed) unlinks it via the fake store.
|
|
assert.strictEqual(
|
|
fakeFs._store.has(path.normalize(staleFile)),
|
|
false,
|
|
'F2 (retiredArtifacts): the managed-pristine retired artifact must have been removed via the fake store',
|
|
);
|
|
assert.ok(
|
|
(packageSourceHits.get('readdirSync') ?? 0) > 0,
|
|
'F2 (retiredArtifacts): readMsdCommandNames must have read the real, unrouted commands/msd/ ' +
|
|
'package-source tree at least once — proving the poison boundary was exercised, not merely tolerated',
|
|
);
|
|
});
|
|
});
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// #2874 follow-up round — 50-test-matrix.md rows E1/E2/E4-E12, F4-F6, G2,
|
|
// H1-H5, I1-I5, K3, L1-L2. Extends the F2/E3/E13 coverage above rather than a
|
|
// new file (install's file-count prefix is grandfathered at 8, must not grow).
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
// ─── E. Executed-plan return shape (E1, E2, E4-E12) ──────────────────────────
|
|
|
|
describe('installRuntimeArtifacts — E1: claude global, normal install', () => {
|
|
test('returns an executed plan for a normal install', (t) => {
|
|
const configDir = createTempDir('msd-e1-claude-global-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE);
|
|
|
|
assert.ok(Array.isArray(result.kinds) && result.kinds.length > 0, 'E1: plan must name at least one kind');
|
|
for (const k of result.kinds) {
|
|
assert.strictEqual(typeof k.kind, 'string', 'E1: every kind entry must name its kind');
|
|
assert.strictEqual(typeof k.sourceDir, 'string', 'E1: every kind entry must name its sourceDir');
|
|
assert.strictEqual(typeof k.destDir, 'string', 'E1: every kind entry must name its destDir');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — E2: claude local', () => {
|
|
test('executed plan records the scope', (t) => {
|
|
const configDir = createTempDir('msd-e2-claude-local-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts('claude', configDir, 'local', RESOLVED_CORE);
|
|
|
|
assert.strictEqual(result.scope, 'local', 'E2: local scope must be reflected verbatim on the returned plan');
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — E4: kilo (second family member)', () => {
|
|
test('kilo family install still returns a plan', (t) => {
|
|
const configDir = createTempDir('msd-e4-kilo-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts('kilo', configDir, 'global', RESOLVED_CORE);
|
|
|
|
assert.notStrictEqual(
|
|
result, undefined,
|
|
'E4: kilo, the SECOND combined-family runtime, must ALSO return a plan — E3 is not a one-runtime special case',
|
|
);
|
|
// 'agents' was added here deliberately by #2875 Part 2 Task A
|
|
// (installAgentsKindStandalone, install-engine.cts:1614-1618): the
|
|
// combined-family (opencode/kilo) executed plan now also reports the
|
|
// agents kind it stages via installAgentsKindStandalone, mirroring the
|
|
// generic layout-driven loop's own top-level shape (install-engine.cts:1627-1637).
|
|
// This test was written under #2874 (Phase 5), before that kind was
|
|
// wired in — its expected list was never updated. Kilo's resolved layout
|
|
// declares an `agents` kind, so a correct plan MUST include it; a
|
|
// ['commands', 'skills']-only expectation encoded the pre-#2875 shape,
|
|
// not a real contract.
|
|
assert.deepStrictEqual(result.kinds.map((k) => k.kind).sort(), ['agents', 'commands', 'skills']);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — E5/E7: empty layout + nativePlugin post-step (pi)', () => {
|
|
test('empty layout returns an empty plan', (t) => {
|
|
const configDir = createTempDir('msd-e5-pi-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts('pi', configDir, 'global', RESOLVED_CORE);
|
|
|
|
assert.ok(Array.isArray(result.kinds), 'E5: kinds must be an array even when layout.kinds is empty');
|
|
assert.strictEqual(result.kinds.length, 0, 'E5: pi declares an empty artifactLayout — kinds must be [], never undefined');
|
|
});
|
|
|
|
test('native plugin post-step is recorded', (t) => {
|
|
const configDir = createTempDir('msd-e7-pi-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts('pi', configDir, 'global', RESOLVED_CORE);
|
|
|
|
assert.strictEqual(
|
|
result.postSteps.nativePlugin, true,
|
|
'E7: pi declares hostBehaviors.nativePlugin — postSteps.nativePlugin must record it as a post-step',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — E6: hermes post-step is recorded', () => {
|
|
test('hermes post-step is recorded', (t) => {
|
|
const configDir = createTempDir('msd-e6-hermes-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts('hermes', configDir, 'global', RESOLVED_CORE);
|
|
|
|
assert.strictEqual(
|
|
result.postSteps.hermesBareStemCleanup, true,
|
|
'E6: hermes must record _removeHermesBareStemDirs having run as a post-step',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — E8: preserved user skill dirs are recorded', () => {
|
|
test('preserved user skill dirs are recorded', (t) => {
|
|
const configDir = createTempDir('msd-e8-claude-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
const preservedSkillDir = path.join(configDir, 'skills', 'msd-dev-preferences');
|
|
fs.mkdirSync(preservedSkillDir, { recursive: true });
|
|
fs.writeFileSync(path.join(preservedSkillDir, 'SKILL.md'), '# my custom prefs\n');
|
|
|
|
const result = installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE);
|
|
|
|
const skillsKind = result.kinds.find((k) => k.kind === 'skills');
|
|
assert.ok(skillsKind, 'E8 precondition: claude global must write a skills kind');
|
|
assert.deepStrictEqual(
|
|
skillsKind.preserved, ['msd-dev-preferences'],
|
|
'E8: the plan must record msd-dev-preferences as preserved',
|
|
);
|
|
assert.strictEqual(
|
|
fs.readFileSync(path.join(preservedSkillDir, 'SKILL.md'), 'utf8'),
|
|
'# my custom prefs\n',
|
|
'E8: the preserved content must actually have been restored after the prune+copy, not just recorded on the plan',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — E9: non-skills kind records its writes', () => {
|
|
test('non-skills kind records its writes', (t) => {
|
|
const configDir = createTempDir('msd-e9-claude-local-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts('claude', configDir, 'local', RESOLVED_CORE);
|
|
|
|
const commandsKind = result.kinds.find((k) => k.kind === 'commands');
|
|
assert.ok(commandsKind, 'E9 precondition: claude local must write a commands kind');
|
|
assert.strictEqual(commandsKind.destDir, path.join(configDir, 'commands'));
|
|
assert.ok(
|
|
fs.existsSync(commandsKind.destDir) && fs.readdirSync(commandsKind.destDir).length > 0,
|
|
'E9: the destDir the plan records must actually contain the copied files',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — E10: plan item naming a kind absent from layout.kinds', () => {
|
|
test('unknown kind still throws', (t) => {
|
|
const configDir = createTempDir('msd-e10-claude-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const original = runtimeArtifactInstallPlan.createRuntimeArtifactInstallPlan;
|
|
t.after(() => { runtimeArtifactInstallPlan.createRuntimeArtifactInstallPlan = original; });
|
|
// Module-ref monkeypatch (same pattern as
|
|
// tests/runtime-artifact-layout-surface.test.cjs) — install-engine.cts
|
|
// reads this via the module reference, not a destructured local, so
|
|
// reassigning the export is observed at call time.
|
|
runtimeArtifactInstallPlan.createRuntimeArtifactInstallPlan = (args) => {
|
|
const real = original(args);
|
|
if (!real.ok) return real;
|
|
return {
|
|
ok: true,
|
|
plan: {
|
|
items: [...real.plan.items, { kind: 'not-a-real-kind', sourceDir: configDir, destDir: configDir }],
|
|
cleanupDirs: real.plan.cleanupDirs,
|
|
},
|
|
};
|
|
};
|
|
|
|
assert.throws(
|
|
() => installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE),
|
|
/unknown artifact kind/i,
|
|
'E10: a plan item naming a kind absent from layout.kinds must still throw "unknown artifact kind"',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — E11: plan is not shared across calls', () => {
|
|
test('plan is not shared across calls', (t) => {
|
|
const configDir = createTempDir('msd-e11-claude-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const first = installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE);
|
|
first.kinds.push({ kind: 'mutated-by-caller', sourceDir: 'x', destDir: 'y', preserved: [] });
|
|
first.postSteps.mutatedFlag = true;
|
|
|
|
const second = installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE);
|
|
|
|
assert.notStrictEqual(second, first, 'E11: each call must return a fresh object, not the same reference');
|
|
assert.notStrictEqual(second.kinds, first.kinds, 'E11: kinds array must not be shared across calls');
|
|
assert.ok(
|
|
!second.kinds.some((k) => k.kind === 'mutated-by-caller'),
|
|
'E11: mutating the first result must not leak into the second call\'s plan',
|
|
);
|
|
assert.strictEqual(
|
|
second.postSteps.mutatedFlag, undefined,
|
|
'E11: mutating the first result\'s postSteps must not leak into the second call',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — E12: executed plan key set is locked', () => {
|
|
test('executed plan key set is locked', (t) => {
|
|
const configDir = createTempDir('msd-e12-claude-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE);
|
|
|
|
assert.deepStrictEqual(
|
|
Object.keys(result).sort(),
|
|
['cleanup', 'kinds', 'postSteps', 'runtime', 'scope'],
|
|
'E12: the executed-plan top-level key set is a locked contract — an added/renamed/removed key ' +
|
|
'here is a breaking change to AC1/AC4 and must be a deliberate, reviewed decision, not an ' +
|
|
'incidental refactor',
|
|
);
|
|
});
|
|
});
|
|
|
|
// ─── F. Fs adapter seam — F4-F6 ───────────────────────────────────────────────
|
|
|
|
/**
|
|
* Build a fs object that implements EVERY InstallFsAdapter method by
|
|
* delegating to real `node:fs` (mirroring install-fs-adapter.cts's own
|
|
* REAL_ADAPTER), then applies `overrides` on top. buildGuardedAdapter
|
|
* (install-fs-adapter.cts) now throws for any method an injected partial
|
|
* omits (the module doc's "PARTIAL-ADAPTER TRAP" fix), so an end-to-end test
|
|
* that drives a REAL install against a REAL destDir (F4/I2/I5 below — these
|
|
* need real command/agent source content actually copied) while
|
|
* intercepting only one or two specific calls needs a COMPLETE fake that
|
|
* only fakes what it overrides — exactly the "documented, intended usage"
|
|
* install-fs-adapter.cts's own module doc calls out, as opposed to
|
|
* `createFakeInstallFs`'s fully in-memory store (used where the test itself
|
|
* controls all content, e.g. F2/F5/F6).
|
|
*/
|
|
function createRealDelegatingFs(overrides = {}) {
|
|
const base = {
|
|
existsSync: (p) => fs.existsSync(p),
|
|
mkdirSync: (p, opts) => fs.mkdirSync(p, opts),
|
|
// eslint-disable-next-line local/no-raw-rmsync-in-tests -- delegate for a fake-adapter method, not test cleanup
|
|
rmSync: (p, opts) => fs.rmSync(p, opts),
|
|
readdirSync: (p, opts) => (opts ? fs.readdirSync(p, opts) : fs.readdirSync(p)),
|
|
readFileSync: (p, encoding) => (encoding ? fs.readFileSync(p, encoding) : fs.readFileSync(p)),
|
|
writeFileSync: (p, data, opts) => fs.writeFileSync(p, data, opts),
|
|
copyFileSync: (src, dest) => fs.copyFileSync(src, dest),
|
|
cpSync: (src, dest, opts) => fs.cpSync(src, dest, opts),
|
|
lstatSync: (p) => fs.lstatSync(p),
|
|
realpathSync: (p) => fs.realpathSync(p),
|
|
unlinkSync: (p) => fs.unlinkSync(p),
|
|
rmdirSync: (p) => fs.rmdirSync(p),
|
|
symlinkSync: (target, p) => fs.symlinkSync(target, p),
|
|
readlinkSync: (p) => fs.readlinkSync(p),
|
|
openSync: (p, flags) => fs.openSync(p, flags),
|
|
readSync: (fd, buffer, offset, length, position) => fs.readSync(fd, buffer, offset, length, position),
|
|
closeSync: (fd) => fs.closeSync(fd),
|
|
};
|
|
return { ...base, ...overrides };
|
|
}
|
|
|
|
describe('installRuntimeArtifacts — F4: adapter errors propagate, cleanup still runs', () => {
|
|
test('adapter errors propagate, cleanup still runs', (t) => {
|
|
const configDir = createTempDir('msd-f4-augment-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
let capturedCleanupDir;
|
|
const fakeFs = createRealDelegatingFs({
|
|
writeFileSync: (p, data, opts) => {
|
|
if (String(p).includes('msd-cmd-rewrites-') && capturedCleanupDir === undefined) {
|
|
capturedCleanupDir = path.dirname(p);
|
|
}
|
|
fs.writeFileSync(p, data, opts);
|
|
},
|
|
copyFileSync: (src, dest) => {
|
|
const err = new Error(`EACCES: permission denied, copyfile '${src}' -> '${dest}'`);
|
|
err.code = 'EACCES';
|
|
throw err;
|
|
},
|
|
});
|
|
|
|
assert.throws(
|
|
() => installRuntimeArtifacts('augment', configDir, 'global', RESOLVED_FULL, TEST_ATTRIBUTION, undefined, { fs: fakeFs }),
|
|
(err) => err.code === 'EACCES',
|
|
'F4: an EACCES from the injected adapter mid-copy must propagate to the caller unchanged, exactly as a real EACCES would today',
|
|
);
|
|
assert.ok(capturedCleanupDir, 'F4 test precondition: the commands kind rewrite must have run before the copy failure');
|
|
assert.strictEqual(
|
|
fs.existsSync(capturedCleanupDir), false,
|
|
'F4: cleanup must still run (the finally block) even though the copy step threw',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — F5: fake existsSync drives the same branch', () => {
|
|
test('fake existsSync drives the same branch', () => {
|
|
const configDir = path.join(os.tmpdir(), `msd-f5-must-not-exist-${crypto.randomUUID()}`);
|
|
const skillsDest = path.join(configDir, 'skills');
|
|
// Seed ONLY the skills destDir as a pre-existing (empty) directory in the
|
|
// fake store — configDir is never created for real, so existsSync(dest)
|
|
// reports true purely because the FAKE says so, driving the exact same
|
|
// `kind.kind === 'skills' && installFs().existsSync(dest)` pre-existing-
|
|
// dest branch a real pre-existing dir would take.
|
|
const fakeFs = createFakeInstallFs([[skillsDest, { type: 'dir' }]]);
|
|
|
|
const result = installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE, undefined, undefined, { fs: fakeFs });
|
|
|
|
assert.notStrictEqual(result, undefined, 'F5: must still return a plan');
|
|
const skillsKind = result.kinds.find((k) => k.kind === 'skills');
|
|
assert.ok(skillsKind, 'F5 precondition: claude global writes a skills kind');
|
|
assert.strictEqual(skillsKind.destDir, skillsDest);
|
|
assert.deepStrictEqual(
|
|
skillsKind.preserved, [],
|
|
'F5: the branch ran off the fake\'s existsSync=true, found an empty pre-existing dir, and preserved ' +
|
|
'nothing — the same outcome the real existsSync-true branch produces for an empty pre-existing dir',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — F6: incomplete adapter fails loudly, never silently falls back to real fs', () => {
|
|
// #2875 REVERSES this row's earlier pinned contract ("falls back to real
|
|
// fs, never silently no-ops"). That contract was itself the defect
|
|
// buildGuardedAdapter closes (install-fs-adapter.cts's "PARTIAL-ADAPTER
|
|
// TRAP" doc comment): merging an injected partial OVER the real adapter
|
|
// meant any method the partial omitted was silently REAL `node:fs` — e.g.
|
|
// user-artifact-staging.cts's `stageUserArtifacts` calling
|
|
// `installFs().rmSync(entryDir)` unconditionally, where a test fake
|
|
// missing `rmSync` would silently delete the real
|
|
// `<configDir>/.msd-staging/<key>` on disk. Falling through to real fs is
|
|
// exactly how a fake-adapter test can end up performing real, uncontrolled
|
|
// IO — the bug, not a feature. The guarded contract instead throws
|
|
// immediately, naming the missing method, the moment the exercised path
|
|
// reaches it: never a silent no-op AND never a silent real-fs write.
|
|
test('incomplete adapter fails loudly (never silently skips the write)', () => {
|
|
const configDir = path.join(os.tmpdir(), `msd-f6-must-not-exist-${crypto.randomUUID()}`);
|
|
|
|
// Deliberately incomplete: only mkdirSync is implemented, to prove the
|
|
// FIRST other method the call path reaches throws immediately instead of
|
|
// silently degrading to real fs or a no-op.
|
|
const incompleteFs = {
|
|
mkdirSync: () => undefined,
|
|
};
|
|
|
|
assert.throws(
|
|
() => installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE, undefined, undefined, { fs: incompleteFs }),
|
|
(err) => /does not implement it/.test(err.message) && /PARTIAL-ADAPTER TRAP/.test(err.message),
|
|
'F6: an incomplete adapter must fail loudly, naming the missing method, the moment the call path ' +
|
|
'reaches a method it does not implement — never silently no-op or fall back to real fs',
|
|
);
|
|
});
|
|
});
|
|
|
|
// ─── G. Additive contract — G2 ────────────────────────────────────────────────
|
|
|
|
describe('installRuntimeArtifacts — G2: bin/install.js production call site unchanged', () => {
|
|
test('installer call site unchanged', (t) => {
|
|
const binInstall = require('../bin/install.js');
|
|
const tmpDir = createTempDir('msd-g2-');
|
|
const previousCwd = process.cwd();
|
|
process.chdir(tmpDir);
|
|
t.after(() => { process.chdir(previousCwd); cleanup(tmpDir); });
|
|
|
|
const result = binInstall.install(false, 'claude');
|
|
|
|
assert.strictEqual(
|
|
result.runtime, 'claude',
|
|
'G2: bin/install.js\'s production call site (6 positional args, no deps) must be unaffected by the new optional deps param',
|
|
);
|
|
// install(false, ...) is a LOCAL install — claude's local layout writes
|
|
// commands+agents, not skills (skills is global-only for claude).
|
|
assert.ok(
|
|
fs.existsSync(path.join(tmpDir, '.claude', 'commands')),
|
|
'G2: the production install must still write commands/ end-to-end',
|
|
);
|
|
binInstall.uninstall(false, 'claude');
|
|
});
|
|
});
|
|
|
|
// ─── H. Security boundaries must NOT move behind the adapter ─────────────────
|
|
|
|
describe('installRuntimeArtifacts — H1: symlink escape still refuses', () => {
|
|
test('symlink escape still refuses', (t) => {
|
|
const configDir = createTempDir('msd-h1-');
|
|
const outsideDir = createTempDir('msd-h1-outside-');
|
|
t.after(() => { cleanup(configDir); cleanup(outsideDir); });
|
|
sandboxHome(t, configDir);
|
|
// Pre-create the skills destDir AS a symlink pointing outside configDir —
|
|
// the guard must refuse before mkdirSync ever follows it.
|
|
fs.symlinkSync(outsideDir, path.join(configDir, 'skills'), 'dir');
|
|
|
|
assert.throws(
|
|
() => installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE),
|
|
/MSD_ALLOW_SYMLINKED_DEST/,
|
|
'H1: a destDir that is itself a symlink pointing outside the install root must be refused',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — H2: opt-in still follows', () => {
|
|
test('opt-in still follows', (t) => {
|
|
const configDir = createTempDir('msd-h2-');
|
|
const outsideDir = createTempDir('msd-h2-outside-');
|
|
t.after(() => { cleanup(configDir); cleanup(outsideDir); });
|
|
sandboxHome(t, configDir);
|
|
fs.symlinkSync(outsideDir, path.join(configDir, 'skills'), 'dir');
|
|
|
|
const savedOptIn = process.env.MSD_ALLOW_SYMLINKED_DEST;
|
|
process.env.MSD_ALLOW_SYMLINKED_DEST = '1';
|
|
t.after(() => {
|
|
if (savedOptIn === undefined) delete process.env.MSD_ALLOW_SYMLINKED_DEST;
|
|
else process.env.MSD_ALLOW_SYMLINKED_DEST = savedOptIn;
|
|
});
|
|
|
|
const result = installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE);
|
|
|
|
assert.notStrictEqual(result, undefined, 'H2: opt-in must still succeed and return a plan');
|
|
const skillsKind = result.kinds.find((k) => k.kind === 'skills');
|
|
assert.ok(skillsKind, 'H2 precondition: claude global writes a skills kind');
|
|
assert.ok(
|
|
fs.readdirSync(outsideDir).length > 0,
|
|
'H2: with the opt-in set, writes must actually follow the symlink into outsideDir',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — H3: fake adapter cannot bypass the symlink guard', () => {
|
|
test('fake adapter cannot bypass the symlink guard', () => {
|
|
// hasExistingSymlinkBetween's path-traversal refusal (install-engine.cts,
|
|
// part (a) of the guard: "resolvedFullPath !== resolvedRoot &&
|
|
// !resolvedFullPath.startsWith(resolvedRoot + path.sep)") is PURE PATH
|
|
// MATH — path.resolve/startsWith on strings, no fs call at all. Pin that
|
|
// invariant directly: even a fake adapter that lies "nothing exists,
|
|
// nothing is a symlink" everywhere cannot make this refusal pass for an
|
|
// escaping path, because this branch never asks the adapter anything.
|
|
const root = path.join(os.tmpdir(), 'msd-h3-fake-root');
|
|
const escapingPath = path.join(root, '..', '..', 'etc', 'passwd');
|
|
const lyingFs = {
|
|
existsSync: () => false,
|
|
lstatSync: () => {
|
|
throw new Error('H3: lstatSync must never be reached — the path-traversal refusal is pure path math');
|
|
},
|
|
realpathSync: (p) => p,
|
|
};
|
|
|
|
const refused = withInstallFs(lyingFs, () => hasExistingSymlinkBetween(root, escapingPath));
|
|
|
|
assert.strictEqual(
|
|
refused, true,
|
|
'H3: a fake adapter reporting "nothing exists, nothing is a symlink" must not be able to certify ' +
|
|
'an install the real filesystem would refuse — the path-traversal decision does not consult the ' +
|
|
'adapter at all',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — H4: dest confinement still enforced', () => {
|
|
test('dest confinement still enforced', () => {
|
|
assert.throws(
|
|
() => runtimeArtifactInstallPlan.assertDestWithinConfigHome('/fake/config/home', '../../etc'),
|
|
/escapes configHome|strict subpath/i,
|
|
'H4: assertDestWithinConfigHome must still throw for a destSubpath escaping configHome',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — H5: nul byte in dest is rejected', () => {
|
|
test('nul byte in dest is rejected', () => {
|
|
assert.throws(
|
|
() => runtimeArtifactInstallPlan.assertDestWithinConfigHome('/fake/config/home', 'skills\0evil'),
|
|
/NUL/,
|
|
'H5: assertDestWithinConfigHome must still throw for a destSubpath containing a NUL byte',
|
|
);
|
|
});
|
|
});
|
|
|
|
// ─── I. Cleanup visibility ─────────────────────────────────────────────────
|
|
|
|
describe('installRuntimeArtifacts — I1: successful cleanup is recorded', () => {
|
|
test('successful cleanup is recorded', (t) => {
|
|
const configDir = createTempDir('msd-i1-augment-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts('augment', configDir, 'global', RESOLVED_FULL, TEST_ATTRIBUTION);
|
|
|
|
assert.ok(result.cleanup.length > 0, 'I1: augment install must produce at least one cleanupDirs entry to prove this row');
|
|
for (const entry of result.cleanup) {
|
|
assert.strictEqual(typeof entry.dir, 'string');
|
|
assert.strictEqual(entry.ok, true, `I1: successful cleanup entries must record ok:true (dir=${entry.dir})`);
|
|
assert.strictEqual(fs.existsSync(entry.dir), false, 'I1: a successfully cleaned dir must no longer exist on disk');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — I2: failed cleanup is visible, not silent', () => {
|
|
test('failed cleanup is visible, not silent', (t) => {
|
|
const configDir = createTempDir('msd-i2-augment-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const fakeFs = createRealDelegatingFs({
|
|
rmSync: (p, opts) => {
|
|
if (String(p).includes('msd-cmd-rewrites-')) {
|
|
throw new Error('I2: simulated cleanup failure');
|
|
}
|
|
// eslint-disable-next-line local/no-raw-rmsync-in-tests -- delegate, not test cleanup
|
|
return fs.rmSync(p, opts);
|
|
},
|
|
});
|
|
|
|
const result = installRuntimeArtifacts('augment', configDir, 'global', RESOLVED_FULL, TEST_ATTRIBUTION, undefined, { fs: fakeFs });
|
|
|
|
assert.notStrictEqual(result, undefined, 'I2: install must still succeed (never fail) even when cleanup throws');
|
|
assert.ok(result.cleanup.length > 0, 'I2: augment must have at least one cleanupDirs entry to fail');
|
|
assert.ok(
|
|
result.cleanup.every((c) => c.ok === false),
|
|
'I2: a cleanup rmSync throw must be reported as ok:false on the returned plan, never silently dropped',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — I3: no cleanup dirs is an empty array', () => {
|
|
test('no cleanup dirs is an empty array', (t) => {
|
|
const configDir = createTempDir('msd-i3-claude-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
const result = installRuntimeArtifacts('claude', configDir, 'global', RESOLVED_CORE);
|
|
|
|
assert.ok(Array.isArray(result.cleanup), 'I3: cleanup must be an array even when empty');
|
|
assert.strictEqual(
|
|
result.cleanup.length, 0,
|
|
'I3: a claude/core install with no rewritten temp dirs must report an EMPTY cleanup array, not undefined/absent',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — I4: stage failure before any item cleans up and throws', () => {
|
|
test('stage failure cleans up and throws', (t) => {
|
|
const configDir = createTempDir('msd-i4-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
assert.throws(
|
|
() => installRuntimeArtifacts('claude', configDir, 'global', { skills: 123, agents: 123 }),
|
|
(err) => err instanceof Error,
|
|
'I4: a malformed resolvedProfile that fails the FIRST kind\'s stage() (before any cleanupDirs exist) ' +
|
|
'must still surface as a thrown Error, with the (empty) cleanupDirs still swept by the finally block',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — I5: rewrite failure mid-plan cleans up and throws', () => {
|
|
test('rewrite failure cleans up and throws', (t) => {
|
|
const configDir = createTempDir('msd-i5-augment-');
|
|
t.after(() => cleanup(configDir));
|
|
sandboxHome(t, configDir);
|
|
|
|
let capturedCleanupDir;
|
|
const fakeFs = createRealDelegatingFs({
|
|
mkdirSync: (p, opts) => {
|
|
if (String(p).includes('msd-profile-runtime-skills-')) {
|
|
throw new Error('I5: simulated skills-stage failure AFTER commands already rewrote+registered a cleanup dir');
|
|
}
|
|
fs.mkdirSync(p, opts);
|
|
return undefined;
|
|
},
|
|
writeFileSync: (p, data, opts) => {
|
|
if (String(p).includes('msd-cmd-rewrites-') && capturedCleanupDir === undefined) {
|
|
capturedCleanupDir = path.dirname(p);
|
|
}
|
|
fs.writeFileSync(p, data, opts);
|
|
},
|
|
});
|
|
|
|
assert.throws(
|
|
() => installRuntimeArtifacts('augment', configDir, 'global', RESOLVED_FULL, TEST_ATTRIBUTION, undefined, { fs: fakeFs }),
|
|
/I5: simulated skills-stage failure/,
|
|
'I5: a failure in a LATER kind\'s stage step must still propagate as a thrown error',
|
|
);
|
|
assert.ok(capturedCleanupDir, 'I5 test precondition: the commands kind\'s rewrite dir must have been observed before the skills-stage failure');
|
|
assert.strictEqual(
|
|
fs.existsSync(capturedCleanupDir), false,
|
|
'I5: the EARLIER (successfully rewritten) commands cleanupDir must still be removed by the finally ' +
|
|
'block even though a LATER kind\'s stage step failed',
|
|
);
|
|
});
|
|
});
|
|
|
|
// ─── K. Byte-identical writes — K3 ─────────────────────────────────────────
|
|
|
|
function walkFilesRecursively(root) {
|
|
const out = new Map();
|
|
const walk = (relPath, absPath) => {
|
|
for (const entry of fs.readdirSync(absPath, { withFileTypes: true })) {
|
|
const childRel = relPath ? path.join(relPath, entry.name) : entry.name;
|
|
const childAbs = path.join(absPath, entry.name);
|
|
if (entry.isDirectory()) walk(childRel, childAbs);
|
|
else if (entry.isFile()) out.set(childRel, fs.readFileSync(childAbs));
|
|
}
|
|
};
|
|
if (fs.existsSync(root)) walk('', root);
|
|
return out;
|
|
}
|
|
|
|
describe('installRuntimeArtifacts — K3: real install before/after, full recursive diff', () => {
|
|
test('writes are byte-identical', (t) => {
|
|
for (const runtime of ['claude', 'qwen']) {
|
|
const dirA = createTempDir(`msd-k3-${runtime}-a-`);
|
|
const dirB = createTempDir(`msd-k3-${runtime}-b-`);
|
|
// Two SEQUENTIAL sandboxes for one test: sandboxHome()'s per-call
|
|
// t.after hooks each save the env as they found it, so the second call
|
|
// saves the FIRST sandbox as its "original" — hook ordering then leaves
|
|
// HOME pointing at dirA after the test, leaking into later tests in the
|
|
// shard (observed on the windows matrix: a leaked msd-k3-qwen-* home
|
|
// made the L2 property's antigravity/global run refuse via the
|
|
// #3712 real-home guard). Manage the env directly with ONE restore.
|
|
const savedHome = process.env.HOME;
|
|
const savedUserProfile = process.env.USERPROFILE;
|
|
const savedMarker = process.env.MSD_TEST_HOME_SANDBOX;
|
|
t.after(() => {
|
|
if (savedHome === undefined) delete process.env.HOME; else process.env.HOME = savedHome;
|
|
if (savedUserProfile === undefined) delete process.env.USERPROFILE; else process.env.USERPROFILE = savedUserProfile;
|
|
if (savedMarker === undefined) delete process.env.MSD_TEST_HOME_SANDBOX; else process.env.MSD_TEST_HOME_SANDBOX = savedMarker;
|
|
cleanup(dirA); cleanup(dirB);
|
|
});
|
|
|
|
for (const dir of [dirA, dirB]) {
|
|
process.env.HOME = dir;
|
|
process.env.USERPROFILE = dir;
|
|
process.env.MSD_TEST_HOME_SANDBOX = dir;
|
|
installRuntimeArtifacts(runtime, dir, 'global', RESOLVED_FULL);
|
|
}
|
|
|
|
const filesA = walkFilesRecursively(dirA);
|
|
const filesB = walkFilesRecursively(dirB);
|
|
assert.deepStrictEqual(
|
|
[...filesA.keys()].sort(), [...filesB.keys()].sort(),
|
|
`K3 (${runtime}): the file sets written by two independent installs must match`,
|
|
);
|
|
for (const [relPath, contentA] of filesA) {
|
|
if (relPath === '.msd-source') {
|
|
assert.strictEqual(
|
|
contentA.toString('utf8'),
|
|
`${path.join(dirA, 'msd-core', 'commands', 'msd')}\n`,
|
|
`K3 (${runtime}): first marker must target its own installed corpus`,
|
|
);
|
|
assert.strictEqual(
|
|
filesB.get(relPath).toString('utf8'),
|
|
`${path.join(dirB, 'msd-core', 'commands', 'msd')}\n`,
|
|
`K3 (${runtime}): second marker must target its own installed corpus`,
|
|
);
|
|
continue;
|
|
}
|
|
assert.ok(
|
|
contentA.equals(filesB.get(relPath)),
|
|
`K3 (${runtime}): ${relPath} content drifted between two independent installs`,
|
|
);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// ─── L. Property tests ────────────────────────────────────────────────────
|
|
|
|
describe('installRuntimeArtifacts — L1: plan kinds mirror layout kinds (property)', () => {
|
|
test('plan kinds mirror layout kinds', (t) => {
|
|
const runtimes = Object.keys(registry.runtimes);
|
|
const RUNTIME_ARB = fc.constantFrom(...runtimes);
|
|
const SCOPE_ARB = fc.constantFrom('global', 'local');
|
|
const observedKindSets = new Set();
|
|
const createdDirs = [];
|
|
const savedHome = process.env.HOME;
|
|
const savedUserProfile = process.env.USERPROFILE;
|
|
t.after(() => {
|
|
if (savedHome === undefined) delete process.env.HOME; else process.env.HOME = savedHome;
|
|
if (savedUserProfile === undefined) delete process.env.USERPROFILE; else process.env.USERPROFILE = savedUserProfile;
|
|
for (const d of createdDirs) cleanup(d);
|
|
});
|
|
|
|
// Seeded, bounded numRuns, replay data on failure (verbose:true prints
|
|
// the failing/shrunk (runtime, scope) pair fast-check found).
|
|
fc.assert(
|
|
fc.property(RUNTIME_ARB, SCOPE_ARB, (runtime, scope) => {
|
|
const configDir = createTempDir(`msd-l1-${runtime}-`);
|
|
createdDirs.push(configDir);
|
|
process.env.HOME = configDir;
|
|
process.env.USERPROFILE = configDir;
|
|
|
|
const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, configDir, scope);
|
|
const expectedKinds = [...new Set(layout.kinds.map((k) => k.kind))].sort();
|
|
const plan = installRuntimeArtifacts(runtime, configDir, scope, RESOLVED_CORE);
|
|
const actualKinds = [...new Set(plan.kinds.map((k) => k.kind))].sort();
|
|
observedKindSets.add(JSON.stringify(actualKinds));
|
|
|
|
assert.deepStrictEqual(
|
|
actualKinds, expectedKinds,
|
|
`L1 (${runtime}/${scope}): plan.kinds must be a bijection with layout.kinds — ` +
|
|
`plan=${JSON.stringify(actualKinds)} vs layout=${JSON.stringify(expectedKinds)}`,
|
|
);
|
|
}),
|
|
{ numRuns: 30, seed: 2874, verbose: true },
|
|
);
|
|
|
|
// Non-vacuity: the registry has runtimes with empty, single-kind, and
|
|
// multi-kind layouts (verified across the whole registry — see this
|
|
// row's PR notes) — a generator that only ever produced ONE kind-set
|
|
// would be exercising nothing.
|
|
assert.ok(
|
|
observedKindSets.size > 1,
|
|
`L1 non-vacuity: the generator must exercise more than one distinct kind-set — observed only ` +
|
|
`${observedKindSets.size} (${[...observedKindSets].join(', ')})`,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('installRuntimeArtifacts — L2: plan is deterministic (property)', () => {
|
|
function normalizePlanForIdempotence(plan) {
|
|
// mkInstallTempDir names every rewrite/staging temp dir with a random hex
|
|
// suffix (install-fs-adapter.cts) — expected to differ between two
|
|
// independent calls even when everything else about the plan is
|
|
// identical. Normalize those away; everything else must match exactly.
|
|
const stripTemp = (p) => (typeof p === 'string' && p.startsWith(os.tmpdir()) ? '<TEMP>' : p);
|
|
return {
|
|
runtime: plan.runtime,
|
|
scope: plan.scope,
|
|
kinds: plan.kinds.map((k) => ({
|
|
kind: k.kind, sourceDir: stripTemp(k.sourceDir), destDir: k.destDir,
|
|
preserved: k.preserved, written: k.written,
|
|
})),
|
|
cleanup: plan.cleanup.map((c) => ({ dir: stripTemp(c.dir), ok: c.ok })),
|
|
postSteps: plan.postSteps,
|
|
};
|
|
}
|
|
|
|
test('plan is deterministic', (t) => {
|
|
const runtimes = Object.keys(registry.runtimes);
|
|
const RUNTIME_ARB = fc.constantFrom(...runtimes);
|
|
const SCOPE_ARB = fc.constantFrom('global', 'local');
|
|
let hits = 0;
|
|
// #3738: the per-run HOME sandbox must EXIST on disk — the #3712 guard's
|
|
// sandbox exemption fails closed when it cannot stat the effective home
|
|
// (identify() -> 'absent'), which is exactly what a never-created configDir
|
|
// gives it on the windows matrix where tmpdir sits under the real home.
|
|
const createdL2Dirs = [];
|
|
t.after(() => { for (const d of createdL2Dirs) cleanup(d); });
|
|
|
|
fc.assert(
|
|
fc.property(RUNTIME_ARB, SCOPE_ARB, (runtime, scope) => {
|
|
// configDir is never created for real — both calls run against fresh,
|
|
// independent fake adapters, so no real fs cleanup is needed here.
|
|
const configDir = path.join(os.tmpdir(), `msd-l2-${runtime}-${crypto.randomUUID()}`);
|
|
fs.mkdirSync(configDir, { recursive: true });
|
|
createdL2Dirs.push(configDir);
|
|
// #3738: a home-override runtime (antigravity → <HOME>/.gemini/config)
|
|
// resolves its dest from os.homedir(), NOT configDir — sandbox HOME to
|
|
// configDir for the duration of both calls (mirroring L1 above) so the
|
|
// plan never escapes into an ambient or leaked home and the #3712
|
|
// real-home guard stays satisfied on hosts where tmpdir sits under the
|
|
// real home (windows).
|
|
const savedL2Home = process.env.HOME;
|
|
const savedL2UserProfile = process.env.USERPROFILE;
|
|
process.env.HOME = configDir;
|
|
process.env.USERPROFILE = configDir;
|
|
try {
|
|
const planA = installRuntimeArtifacts(runtime, configDir, scope, RESOLVED_CORE, undefined, undefined, { fs: createFakeInstallFs() });
|
|
const planB = installRuntimeArtifacts(runtime, configDir, scope, RESOLVED_CORE, undefined, undefined, { fs: createFakeInstallFs() });
|
|
hits++;
|
|
|
|
assert.deepStrictEqual(
|
|
normalizePlanForIdempotence(planA),
|
|
normalizePlanForIdempotence(planB),
|
|
`L2 (${runtime}/${scope}): two installs against fresh fake adapters with the same inputs must ` +
|
|
'yield structurally identical plans (temp-dir names normalized — see normalizePlanForIdempotence)',
|
|
);
|
|
} finally {
|
|
if (savedL2Home === undefined) delete process.env.HOME; else process.env.HOME = savedL2Home;
|
|
if (savedL2UserProfile === undefined) delete process.env.USERPROFILE; else process.env.USERPROFILE = savedL2UserProfile;
|
|
}
|
|
}),
|
|
{ numRuns: 30, seed: 2874, verbose: true },
|
|
);
|
|
|
|
assert.strictEqual(hits, 30, 'L2 non-vacuity: every generated (runtime, scope) pair must actually have exercised a comparison');
|
|
});
|
|
});
|
|
|
|
// ─── readMsdCommandNames — single-source parity ──────────────────────────────
|
|
//
|
|
// command-roster.cts's readMsdCommandNames reimplements
|
|
// scripts/fix-slash-commands.cjs's readCmdNames' directory-scan rule against
|
|
// the injectable install-fs seam instead of delegating to it — see
|
|
// command-roster.cts's module comment for why (readCmdNames is deliberately
|
|
// a zero-dependency standalone CLI/library with no build-order dependency on
|
|
// msd-core/bin/lib, so it cannot itself require the compiled
|
|
// install-fs-adapter.cjs). Two implementations of one filtering rule is this
|
|
// repo's recorded Generative Fix Divergence class; this test is the
|
|
// enforcement the coordinator required in exchange for keeping the
|
|
// reimplementation: it fails the moment the two disagree about which stems
|
|
// commands/msd/ contains.
|
|
|
|
describe('readMsdCommandNames — single-source parity (command-roster.cts vs scripts/fix-slash-commands.cjs)', () => {
|
|
test('both implementations report the identical stem set for commands/msd/', () => {
|
|
const fromCommandRoster = [...commandRoster.readMsdCommandNames()].sort();
|
|
const fromSlashCommandTransformer = [...slashCommandTransformer.readCmdNames()].sort();
|
|
assert.deepStrictEqual(
|
|
fromCommandRoster,
|
|
fromSlashCommandTransformer,
|
|
'command-roster.cts readMsdCommandNames() and scripts/fix-slash-commands.cjs readCmdNames() ' +
|
|
'diverged — these are two implementations of the SAME directory-scan rule (Generative Fix ' +
|
|
'Divergence); fix the one that is wrong, do not just silence this test',
|
|
);
|
|
assert.ok(fromCommandRoster.length > 0, 'sanity: commands/msd/ must contain at least one command');
|
|
});
|
|
});
|