fix(#1477): key getInstallExports cache per configDir; document marker contract
Address second maintainer review on #1487: - Blocker 1: getInstallExports now caches per runtimeConfigDir (Map) so a no-arg warm-up via the legacy walk-up path cannot poison a later getInstallExports(configDir) call. Add a regression test (verified to fail on the singleton cache) proving the no-arg warm-up does not poison the configDir-keyed resolution. - Blocker 3: document the .gsd-source two-party provisioning contract in the CONTEXT.md Runtime Artifact Layout Module entry (writer, reader, content, guard, fall-through, package-root invariant, per-configDir cache). Claude-Session: https://claude.ai/code/session_01XNT3SWgzjmEycNuweURDme
This commit is contained in:
@@ -775,6 +775,58 @@ describe('#1477 .gsd-source marker provisioning + deployed install-exports resol
|
||||
'getInstallExports must expose applyRuntimeContentRewritesInPlace (used by applySurface)');
|
||||
});
|
||||
|
||||
// ── Failure 2 cache correctness: getInstallExports keys on runtimeConfigDir ──
|
||||
// A module-level singleton cache would let a no-arg warm-up call (legacy
|
||||
// walk-up path) poison every later getInstallExports(configDir) call —
|
||||
// applySurface would then load the wrong install.js. Proves the cache is
|
||||
// keyed per configDir so the marker-derived path always wins for its key.
|
||||
test('getInstallExports caches per configDir — a no-arg warm-up does not poison a later configDir call', () => {
|
||||
// A standalone package whose commands/gsd marker derives a sibling
|
||||
// bin/install.js exporting a sentinel that the real repo install.js lacks.
|
||||
const pkgRoot = path.join(tmpRoot, 'sentinel-pkg');
|
||||
fs.mkdirSync(path.join(pkgRoot, 'commands', 'gsd'), { recursive: true });
|
||||
fs.mkdirSync(path.join(pkgRoot, 'bin'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pkgRoot, 'bin', 'install.js'),
|
||||
"module.exports = { sentinel: 'PKG', computePathPrefix: () => '', applyRuntimeContentRewritesInPlace: () => {} };\n",
|
||||
'utf8',
|
||||
);
|
||||
const cfgDir = path.join(tmpRoot, 'sentinel-cfg');
|
||||
fs.mkdirSync(cfgDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(cfgDir, '.gsd-source'),
|
||||
path.join(pkgRoot, 'commands', 'gsd') + '\n',
|
||||
'utf8',
|
||||
);
|
||||
|
||||
// Fresh module instance so the per-key cache starts empty for this test.
|
||||
const layoutPath = require.resolve('../gsd-core/bin/lib/runtime-artifact-layout.cjs');
|
||||
const savedModule = require.cache[layoutPath];
|
||||
delete require.cache[layoutPath];
|
||||
try {
|
||||
const fresh = require(layoutPath);
|
||||
|
||||
// Warm the cache via the no-arg legacy walk-up path (resolves the real
|
||||
// repo bin/install.js, which has no `sentinel`).
|
||||
const warm = fresh.getInstallExports();
|
||||
assert.equal(warm.sentinel, undefined, 'no-arg path resolves the repo install.js');
|
||||
|
||||
// The configDir call must re-derive from the marker, NOT return the
|
||||
// cached no-arg result. A singleton cache would return `warm` here.
|
||||
const derived = fresh.getInstallExports(cfgDir);
|
||||
assert.equal(derived.sentinel, 'PKG',
|
||||
'no-arg warm-up must not poison the configDir-keyed resolution');
|
||||
|
||||
// Re-querying the same key returns its cached instance, not a re-derive.
|
||||
assert.strictEqual(fresh.getInstallExports(cfgDir), derived,
|
||||
'same configDir key must return the cached instance');
|
||||
} finally {
|
||||
// Restore the original shared module instance for later tests.
|
||||
if (savedModule) require.cache[layoutPath] = savedModule;
|
||||
else delete require.cache[layoutPath];
|
||||
}
|
||||
});
|
||||
|
||||
// ── Adversarial marker-reader cases (no full install needed) ─────────────────
|
||||
describe('findInstallSourceRoot marker handling', () => {
|
||||
let cfgDir;
|
||||
|
||||
Reference in New Issue
Block a user