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:
Joe Slitzker
2026-06-20 13:46:30 -05:00
parent cd47d9d317
commit 9727e2ae94
3 changed files with 69 additions and 5 deletions

View File

@@ -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;