Files
msd-core/tests/install-fs-adapter-seam.test.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
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.
2026-10-06 01:47:40 +02:00

351 lines
16 KiB
JavaScript

'use strict';
/**
* Install fs seam — two real-fs leaks that AC2 ("an install can be exercised
* end-to-end against an injected fs adapter with no real filesystem") does
* not tolerate.
*
* #2874 (epic #2866 Phase 5), governed by ADR-58
* (docs/adr/58-runtime-install-policy-module.md).
*
* (a) command-roster.cts's `readMsdCommandNames` reads the PACKAGE'S OWN
* `commands/msd/` tree — not an install destination — so it must stay on
* real `node:fs`, unrouted, even while a fake install adapter is active
* for the surrounding call (mirrors findInstallSourceRoot /
* findAgentsSourceRoot's documented precedent in
* runtime-artifact-layout.cts).
*
* (b) install-profiles.cts's `cleanupStagedSkills` runs from a
* `process.on('exit'/'SIGINT'/…)` handler — AFTER `withInstallFs` has
* already restored the real adapter — so a dir staged during a
* fake-adapter call must be cleaned up with the SAME fake adapter that
* staged it, never with real fs.
*/
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 { createTempDir, cleanup } = require('./helpers.cjs');
const commandRoster = require('../msd-core/bin/lib/command-roster.cjs');
const {
withInstallFs,
} = require('../msd-core/bin/lib/install-fs-adapter.cjs');
const {
findInstallSourceRoot,
resolveRuntimeArtifactLayout,
} = require('../msd-core/bin/lib/runtime-artifact-layout.cjs');
const {
stageSkillsForRuntimeAsSkills,
cleanupStagedSkills,
STAGED_DIRS,
} = require('../msd-core/bin/lib/install-profiles.cjs');
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'msd');
/**
* A minimal in-memory fake install adapter. Every method that
* `stageSkillsForRuntimeAsSkills` (nested=false path) can reach is
* implemented against one flat Map store — deliberately NOT seeded with
* anything under the real `commands/msd/` tree, so a call that accidentally
* routes a package-source read through this fake would either throw or
* return the wrong (empty) stems instead of the real package's own names.
*/
function createFakeFs(seed = []) {
const store = new Map();
for (const [p, entry] of seed) store.set(path.normalize(String(p)), entry);
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;
};
return {
_store: store,
existsSync: (p) => store.has(norm(p)),
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);
},
readdirSync: (p, opts) => {
const e = store.get(norm(p));
if (!e) throw enoent(p);
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 fe = store.get(full);
return { name, isFile: () => (fe ? fe.type === 'file' : false) };
});
}
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;
},
writeFileSync: (p, data) => { store.set(norm(p), { 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) });
},
lstatSync: (p) => {
const e = store.get(norm(p));
if (!e) throw enoent(p);
return { isFile: () => e.type === 'file', isDirectory: () => e.type === 'dir', isSymbolicLink: () => false };
},
realpathSync: (p) => norm(p),
unlinkSync: (p) => { store.delete(norm(p)); },
rmdirSync: (p) => { store.delete(norm(p)); },
};
}
// ─── (a) command-roster reads the package's OWN source, unrouted ───────────
describe('command-roster readMsdCommandNames — package-source read stays unrouted', () => {
test('returns the real package command stems even while a poisoning fake adapter is active', () => {
const expectedStems = fs.readdirSync(REAL_COMMANDS_DIR)
.filter((f) => f.endsWith('.md'))
.map((f) => f.replace(/\.md$/, ''))
.sort();
assert.ok(expectedStems.length > 0, 'REAL_COMMANDS_DIR must contain real .md command files (fixture drift)');
// A fake whose readdirSync/existsSync ALWAYS throw or lie — if
// readMsdCommandNames routed its read through installFs(), this would
// either throw (poisoned) or return the wrong (empty) set instead of the
// real package's own stems.
const poisonFs = {
existsSync: () => { throw new Error('leak (a): installFs().existsSync reached for the package-own commands dir'); },
readdirSync: () => { throw new Error('leak (a): installFs().readdirSync reached for the package-own commands dir'); },
readFileSync: () => { throw new Error('leak (a): installFs().readFileSync reached for the package-own commands dir'); },
mkdirSync: () => { throw new Error('leak (a): installFs().mkdirSync reached'); },
writeFileSync: () => { throw new Error('leak (a): installFs().writeFileSync reached'); },
copyFileSync: () => { throw new Error('leak (a): installFs().copyFileSync reached'); },
rmSync: () => { throw new Error('leak (a): installFs().rmSync reached'); },
};
const actualStems = withInstallFs(poisonFs, () => commandRoster.readMsdCommandNames()).sort();
assert.deepStrictEqual(
actualStems,
expectedStems,
'readMsdCommandNames must return the real package command stems, reading real fs directly, ' +
'not the injected (poisoning) fake install adapter',
);
});
});
describe('runtime artifact source identity — destination probes stay routed', () => {
test('#4132: installed provider admission streams hashes before reading corpus content', (t) => {
const configDir = createTempDir('msd-installed-provider-');
t.after(() => cleanup(configDir));
const installedCommands = path.join(configDir, 'msd-core', 'commands', 'msd');
const installedAgents = path.join(configDir, 'msd-core', 'agents');
const commandPath = path.join(installedCommands, 'help.md');
const agentPath = path.join(installedAgents, 'msd-planner.md');
const manifestPath = path.join(configDir, 'msd-file-manifest.json');
fs.mkdirSync(installedCommands, { recursive: true });
fs.mkdirSync(installedAgents, { recursive: true });
fs.writeFileSync(commandPath, '# installed command\n');
fs.writeFileSync(agentPath, '# installed agent\n');
fs.writeFileSync(manifestPath, JSON.stringify({ files: {
'msd-core/commands/msd/help.md': crypto.createHash('sha256').update(fs.readFileSync(commandPath)).digest('hex'),
'msd-core/agents/msd-planner.md': crypto.createHash('sha256').update(fs.readFileSync(agentPath)).digest('hex'),
} }));
const corpusPaths = new Set([commandPath, agentPath].map((candidate) => path.resolve(candidate)));
const hashingDescriptors = new Map();
const streamedHashes = new Set();
const hashedBeforeRead = new Set();
const corpusReadsBeforeHash = [];
const readCalls = [];
const recordingFs = {
...fs,
readFileSync: (candidate, ...args) => {
const resolved = path.resolve(String(candidate));
readCalls.push(resolved);
if (corpusPaths.has(resolved) && !hashedBeforeRead.has(resolved)) {
corpusReadsBeforeHash.push(resolved);
}
return fs.readFileSync(candidate, ...args);
},
openSync: (candidate, ...args) => {
const fd = fs.openSync(candidate, ...args);
const resolved = path.resolve(String(candidate));
if (corpusPaths.has(resolved)) hashingDescriptors.set(fd, resolved);
return fd;
},
readSync: (fd, ...args) => {
const resolved = hashingDescriptors.get(fd);
if (resolved) streamedHashes.add(resolved);
return fs.readSync(fd, ...args);
},
closeSync: (fd) => {
fs.closeSync(fd);
const resolved = hashingDescriptors.get(fd);
if (resolved) hashedBeforeRead.add(resolved);
},
};
const layout = resolveRuntimeArtifactLayout('claude', configDir, 'global');
let stagedSkills;
let stagedAgents;
t.after(() => {
if (stagedSkills) cleanup(stagedSkills);
if (stagedAgents) cleanup(stagedAgents);
});
withInstallFs(recordingFs, () => {
stagedSkills = layout.kinds.find((kind) => kind.kind === 'skills').stage({ skills: '*', agents: '*' });
stagedAgents = layout.kinds.find((kind) => kind.kind === 'agents').stage({ skills: '*', agents: '*' });
});
assert.deepStrictEqual(corpusReadsBeforeHash, [], 'installed corpus files must not be pre-read before manifest hashing');
assert.deepStrictEqual(streamedHashes, corpusPaths, 'commands and agents hashes must stream through readSync');
assert.deepStrictEqual(hashedBeforeRead, corpusPaths, 'commands and agents hashes must use the streaming fd path');
assert.ok(readCalls.includes(path.resolve(manifestPath)), 'provider admission must still read the install manifest');
assert.match(fs.readFileSync(path.join(stagedSkills, 'msd-help', 'SKILL.md'), 'utf8'), /installed command/);
assert.match(fs.readFileSync(path.join(stagedAgents, 'msd-planner.md'), 'utf8'), /installed agent/);
});
test('#4132: an installed-root alias is resolved through installFs without routing the package root', () => {
const configDir = path.join(os.tmpdir(), `msd-fake-config-${crypto.randomUUID()}`);
const installedCommands = path.join(configDir, 'msd-core', 'commands', 'msd');
const probes = [];
const missing = (p) => {
const error = new Error(`ENOENT: no such file or directory, '${p}'`);
error.code = 'ENOENT';
throw error;
};
const fakeFs = {
existsSync: () => false,
lstatSync: missing,
realpathSync: (p) => {
probes.push(path.normalize(String(p)));
return path.normalize(String(p)) === path.normalize(installedCommands)
? fs.realpathSync(REAL_COMMANDS_DIR)
: path.normalize(String(p));
},
};
assert.throws(
() => withInstallFs(fakeFs, () => findInstallSourceRoot(configDir)),
/install or upgrade msd-core/,
);
assert.deepStrictEqual(
probes,
[path.normalize(installedCommands)],
'installed destination identity must use installFs, while package-source identity stays on raw fs',
);
});
test('#4132: an unreadable physical root cannot prove a marker is independent', () => {
const configDir = path.join(os.tmpdir(), `msd-fake-config-${crypto.randomUUID()}`);
const installedCommands = path.join(configDir, 'msd-core', 'commands', 'msd');
const markerCommands = path.join(configDir, 'independent', 'commands', 'msd');
const fakeFs = createFakeFs([
[configDir, { type: 'dir' }],
[path.join(configDir, 'msd-core'), { type: 'dir' }],
[path.join(configDir, 'msd-core', 'commands'), { type: 'dir' }],
[installedCommands, { type: 'dir' }],
[path.join(installedCommands, 'help.md'), { type: 'file', content: '# unverified installed command\n' }],
[path.join(configDir, 'independent'), { type: 'dir' }],
[path.join(configDir, 'independent', 'commands'), { type: 'dir' }],
[markerCommands, { type: 'dir' }],
[path.join(markerCommands, 'help.md'), { type: 'file', content: '# marker command\n' }],
[path.join(configDir, '.msd-source'), { type: 'file', content: markerCommands + '\n' }],
]);
fakeFs.realpathSync = (p) => {
if ([installedCommands, markerCommands].includes(path.normalize(String(p)))) {
const error = new Error(`EACCES: permission denied, realpath '${p}'`);
error.code = 'EACCES';
throw error;
}
return path.normalize(String(p));
};
assert.throws(
() => withInstallFs(fakeFs, () => findInstallSourceRoot(configDir)),
/install or upgrade msd-core/,
);
});
});
// ─── (b) cleanupStagedSkills must not perform real IO on fake-staged dirs ──
describe('install-profiles cleanupStagedSkills — deferred cleanup does not leak past the restore', () => {
test('a fake-adapter install followed by the exit handler performs zero real fs.rmSync calls', (t) => {
const fakeSrcDir = path.join(os.tmpdir(), `msd-fake-src-${crypto.randomUUID()}`);
const fakeFs = createFakeFs([
[fakeSrcDir, { type: 'dir' }],
[path.join(fakeSrcDir, 'alpha.md'), { type: 'file', content: '# alpha\n' }],
]);
// Poison real fs.rmSync for the duration of this test — auto-restored by
// node:test's mock tracker when the test ends (no try/finally needed).
let realRmSyncCalls = 0;
t.mock.method(fs, 'rmSync', () => {
realRmSyncCalls++;
throw new Error('leak (b): real fs.rmSync() was reached for a dir staged under a fake adapter');
});
const converter = (content, _skillName) => content;
const stagedDir = withInstallFs(
fakeFs,
() => stageSkillsForRuntimeAsSkills(fakeSrcDir, { skills: '*' }, converter, 'msd-'),
);
assert.ok(STAGED_DIRS.has(stagedDir), 'stageSkillsForRuntimeAsSkills must register the staged dir for cleanup');
assert.ok(fakeFs._store.has(path.normalize(stagedDir)), 'staged dir must exist in the fake store');
// Simulate the exit handler: `current` (install-fs-adapter.cts) is back
// to the real adapter here — withInstallFs already restored it above.
cleanupStagedSkills();
assert.strictEqual(realRmSyncCalls, 0, 'cleanupStagedSkills must never call real fs.rmSync for a fake-staged dir');
assert.strictEqual(fakeFs._store.has(path.normalize(stagedDir)), false, 'the fake-staged dir must be removed via the fake adapter');
assert.strictEqual(STAGED_DIRS.has(stagedDir), false, 'STAGED_DIRS must be cleared after cleanup');
});
test('negative proof: a REAL install still cleans up its own staged dirs', (t) => {
const srcDir = createTempDir('msd-real-src-');
t.after(() => cleanup(srcDir));
fs.writeFileSync(path.join(srcDir, 'alpha.md'), '# alpha\n');
const converter = (content, _skillName) => content;
const stagedDir = stageSkillsForRuntimeAsSkills(srcDir, { skills: '*' }, converter, 'msd-');
t.after(() => { if (fs.existsSync(stagedDir)) cleanup(stagedDir); });
assert.ok(fs.existsSync(stagedDir), 'real staged dir must exist on real fs before cleanup');
assert.ok(STAGED_DIRS.has(stagedDir), 'real staged dir must be registered');
cleanupStagedSkills();
assert.strictEqual(fs.existsSync(stagedDir), false, 'a REAL install must still remove its staged dir on cleanup — no temp-dir leak');
assert.strictEqual(STAGED_DIRS.has(stagedDir), false, 'STAGED_DIRS must be cleared after cleanup');
});
});