fix(#4132): verify durable runtime surface sources (#4182)

* fix(#4132): verify durable runtime surface sources

* chore(#4132): record PR number in changeset

* test(#4132): cover rejected commands source alias

* fix(#4132): reject aliased package fallback

* test(#4132): cover rejected agents source alias

* test(#4132): cover partially aliased marker provider

* fix(#4132): reject partially aliased source providers

* test(#4132): cover routed source identity probes

* fix(#4132): route installed source identity probes

* refactor(#4132): tighten installer source metadata

* test(#4132): cover corpus trust boundary attacks

* fix(#4132): close installed corpus trust gaps

* refactor(#4132): keep installer authority private

* fix(#4132): preserve private installer fallback

* test(#4132): preserve fixture source authority

* fix(#4132): reject overlapping source fallback

* fix(#4132): avoid redundant installed corpus reads

* refactor(#4132): simplify provider resolution

* test(#4132): sync install tree fixtures after rebase

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
Zy Deng
2026-09-05 21:32:52 +02:00
committed by GitHub
parent 1017898cb9
commit 4c60879b5d
49 changed files with 3956 additions and 307 deletions

View File

@@ -21,12 +21,14 @@ const { test, describe, beforeEach, afterEach, mock } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const crypto = require('node:crypto');
const { resolveRuntimeArtifactLayout, findInstallSourceRoot } = require('../gsd-core/bin/lib/runtime-artifact-layout.cjs');
const capabilityRegistry = require('../gsd-core/bin/lib/capability-registry.cjs');
const installProfiles = require('../gsd-core/bin/lib/install-profiles.cjs');
const { withInstallFs } = require('../gsd-core/bin/lib/install-fs-adapter.cjs');
const { install } = require('../bin/install.js');
const { createTempDir, cleanup, scrubConfigLocationEnv } = require('./helpers.cjs');
const { createTempDir, cleanup, scrubConfigLocationEnv, writePackageSourceMarkerFixture } = require('./helpers.cjs');
const REPO_ROOT = path.join(__dirname, '..');
@@ -533,11 +535,16 @@ const CORE_SKILLS = new Set(['help', 'phase', 'new-project']);
const CORE_AGENTS = new Set(['gsd-planner']);
const PROFILE_CORE = { skills: CORE_SKILLS, agents: CORE_AGENTS };
const PROFILE_FULL = { skills: '*', agents: new Set() };
const FAKE_STAGE_DIR = '/tmp/fake-config-dir-stage';
function createStageConfigDir(t) {
const configDir = writePackageSourceMarkerFixture(createTempDir('gsd-layout-stage-'));
t.after(() => cleanup(configDir));
return configDir;
}
describe('stage — agents kind (claude local)', () => {
test('stage returns a valid directory for the agents kind', () => {
const layout = resolveRuntimeArtifactLayout('claude', FAKE_STAGE_DIR, 'local');
test('stage returns a valid directory for the agents kind', (t) => {
const layout = resolveRuntimeArtifactLayout('claude', createStageConfigDir(t), 'local');
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
assert.ok(agentsKind, 'should have an agents kind');
@@ -548,8 +555,40 @@ describe('stage — agents kind (claude local)', () => {
});
describe('stage — skills kind (claude global)', () => {
test('stage returns a directory containing gsd-<stem>/SKILL.md entries', () => {
const layout = resolveRuntimeArtifactLayout('claude', FAKE_STAGE_DIR, 'global');
test('#4132: an independent source checkout can replace an invalid installed corpus', (t) => {
const configDir = createTempDir('gsd-4132-installer-source-');
t.after(() => cleanup(configDir));
const installedCommands = path.join(configDir, 'gsd-core', 'commands', 'gsd');
const installedAgents = path.join(configDir, 'gsd-core', 'agents');
fs.mkdirSync(installedCommands, { recursive: true });
fs.mkdirSync(installedAgents, { recursive: true });
fs.writeFileSync(path.join(installedCommands, 'help.md'), 'OLD INSTALLED COMMAND\n');
fs.writeFileSync(path.join(installedAgents, 'gsd-planner.md'), 'OLD INSTALLED AGENT\n');
fs.writeFileSync(path.join(configDir, '.gsd-source'), path.join(REPO_ROOT, 'commands', 'gsd') + '\n');
const layout = resolveRuntimeArtifactLayout('claude', configDir, 'global');
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
assert.ok(skillsKind);
assert.ok(agentsKind);
const stagedSkills = skillsKind.stage(PROFILE_CORE);
const stagedAgents = agentsKind.stage(PROFILE_CORE);
t.after(() => cleanup(stagedSkills));
t.after(() => cleanup(stagedAgents));
assert.match(
fs.readFileSync(path.join(stagedSkills, 'gsd-help', 'SKILL.md'), 'utf8'),
/Show available GSD commands and usage guide/,
);
assert.doesNotMatch(
fs.readFileSync(path.join(stagedAgents, 'gsd-planner.md'), 'utf8'),
/OLD INSTALLED AGENT/,
);
});
test('stage returns a directory containing gsd-<stem>/SKILL.md entries', (t) => {
const layout = resolveRuntimeArtifactLayout('claude', createStageConfigDir(t), 'global');
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
assert.ok(skillsKind, 'should have a skills kind');
@@ -564,8 +603,8 @@ describe('stage — skills kind (claude global)', () => {
assert.ok(entries.length >= 1, 'at least one skill dir should be staged');
});
test('stage with skills="*" produces flat layout for claude (#924: reverted from nested)', () => {
const layout = resolveRuntimeArtifactLayout('claude', FAKE_STAGE_DIR, 'global');
test('stage with skills="*" produces flat layout for claude (#924: reverted from nested)', (t) => {
const layout = resolveRuntimeArtifactLayout('claude', createStageConfigDir(t), 'global');
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
assert.ok(skillsKind, 'should have a skills kind');
@@ -592,8 +631,8 @@ describe('stage — skills kind (claude global)', () => {
});
describe('stage — skills kind (kimi global)', () => {
test('stage returns Kimi SKILL.md dirs and agent YAML/prompt artifacts', () => {
const layout = resolveRuntimeArtifactLayout('kimi', FAKE_STAGE_DIR, 'global');
test('stage returns Kimi SKILL.md dirs and agent YAML/prompt artifacts', (t) => {
const layout = resolveRuntimeArtifactLayout('kimi', createStageConfigDir(t), 'global');
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
assert.ok(skillsKind, 'should have a skills kind');
@@ -635,8 +674,8 @@ describe('stage — skills kind (kimi global)', () => {
assert.doesNotMatch(executorYaml, /mcp__/);
});
test('tracks Kimi agent staging dir before writing artifacts', () => {
const layout = resolveRuntimeArtifactLayout('kimi', FAKE_STAGE_DIR, 'global');
test('tracks Kimi agent staging dir before writing artifacts', (t) => {
const layout = resolveRuntimeArtifactLayout('kimi', createStageConfigDir(t), 'global');
const agentsKind = layout.kinds.find(k => k.kind === 'kimi-agents');
assert.ok(agentsKind, 'should have a kimi-agents kind');
@@ -673,8 +712,8 @@ describe('stage — skills kind (kimi global)', () => {
});
describe('stage — opencode commands kind', () => {
test('opencode stage returns directory with .md files for selected skills', () => {
const layout = resolveRuntimeArtifactLayout('opencode', FAKE_STAGE_DIR);
test('opencode stage returns directory with .md files for selected skills', (t) => {
const layout = resolveRuntimeArtifactLayout('opencode', createStageConfigDir(t));
const commandsKind = layout.kinds.find(k => k.kind === 'commands');
assert.ok(commandsKind, 'should have a commands kind');
@@ -690,8 +729,8 @@ describe('stage — opencode commands kind', () => {
describe('stage — opencode/kilo skills kind (#784)', () => {
for (const runtime of ['opencode', 'kilo']) {
test(`${runtime} skills stage writes gsd-<stem>/SKILL.md with name + description`, () => {
const layout = resolveRuntimeArtifactLayout(runtime, FAKE_STAGE_DIR);
test(`${runtime} skills stage writes gsd-<stem>/SKILL.md with name + description`, (t) => {
const layout = resolveRuntimeArtifactLayout(runtime, createStageConfigDir(t));
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
assert.ok(skillsKind, 'should have a skills kind');
@@ -717,7 +756,7 @@ describe('stage — opencode/kilo skills kind (#784)', () => {
describe('stage — cursor retired commands kind (#2644)', () => {
test('cursor layout exposes no commands staging surface', () => {
const layout = resolveRuntimeArtifactLayout('cursor', FAKE_STAGE_DIR);
const layout = resolveRuntimeArtifactLayout('cursor', FAKE_DIR);
const commandsKind = layout.kinds.find(k => k.kind === 'commands');
assert.equal(commandsKind, undefined);
});
@@ -921,9 +960,10 @@ describe('#1477 .gsd-source marker provisioning', () => {
});
// ── Failure 1 end-to-end: resolution succeeds FROM the deployed tree ─────────
// The deployed module's __dirname is <claudeDir>/gsd-core/bin/lib, which has no
// commands/gsd ancestor (global skills layout). Only the marker rescues it.
test('deployed global layout resolves the source root via the marker', () => {
// Fixed installs own a raw corpus below the deployed gsd-core tree. The
// marker remains compatible, but offline resolution no longer depends on
// the executing package path surviving.
test('deployed global layout resolves its installation-owned source corpus', () => {
const claudeDir = path.join(tmpRoot, '.claude');
fs.mkdirSync(claudeDir, { recursive: true });
runInstall(true /* isGlobal */, 'claude');
@@ -939,21 +979,93 @@ describe('#1477 .gsd-source marker provisioning', () => {
delete require.cache[deployedLayoutPath];
const deployed = require(deployedLayoutPath);
// Negative proof that the bug condition exists: WITHOUT consulting the marker
// (no configDir argument), walk-up from the deployed tree has nothing to find.
assert.throws(
() => deployed.findInstallSourceRoot(),
/could not locate commands\/gsd/,
'deployed walk-up must fail without the marker — this is the regression condition',
);
const installedCorpus = path.join(claudeDir, 'gsd-core', 'commands', 'gsd');
assert.ok(fs.existsSync(installedCorpus), 'fixed install must own commands/gsd below gsd-core');
assert.equal(fs.realpathSync(deployed.findInstallSourceRoot()), fs.realpathSync(installedCorpus));
// With the marker (configDir provided), list/status resolution succeeds.
let resolved;
assert.doesNotThrow(() => {
resolved = deployed.findInstallSourceRoot(claudeDir);
}, 'findInstallSourceRoot must resolve via the .gsd-source marker');
assert.equal(path.basename(resolved), 'gsd');
assert.ok(fs.existsSync(resolved));
assert.equal(fs.realpathSync(resolved), fs.realpathSync(installedCorpus));
});
test('#4132: deployed findInstallSourceRoot rejects an installed commands corpus whose hash changed', () => {
const claudeDir = path.join(tmpRoot, '.claude');
fs.mkdirSync(claudeDir, { recursive: true });
runInstall(true /* isGlobal */, 'claude');
const manifest = JSON.parse(fs.readFileSync(path.join(claudeDir, 'gsd-file-manifest.json'), 'utf8'));
const commandKey = Object.keys(manifest.files).find((key) => key.startsWith('gsd-core/commands/gsd/'));
assert.ok(commandKey, 'precondition: install manifest must own at least one command corpus file');
const commandPath = path.join(claudeDir, ...commandKey.split('/'));
fs.appendFileSync(commandPath, '\n# corrupted after install\n');
const deployedLayoutPath = path.join(claudeDir, 'gsd-core', 'bin', 'lib', 'runtime-artifact-layout.cjs');
delete require.cache[deployedLayoutPath];
const deployed = require(deployedLayoutPath);
assert.throws(
() => deployed.findInstallSourceRoot(claudeDir),
/install or upgrade gsd-core/,
);
});
test('#4132: deployed finder rejects an installed corpus reached through an ancestor symlink', (t) => {
const claudeDir = path.join(tmpRoot, '.claude');
fs.mkdirSync(claudeDir, { recursive: true });
runInstall(true /* isGlobal */, 'claude');
const commandsParent = path.join(claudeDir, 'gsd-core', 'commands');
const outsideParent = path.join(tmpRoot, 'outside-commands');
fs.renameSync(commandsParent, outsideParent);
try {
fs.symlinkSync(outsideParent, commandsParent, process.platform === 'win32' ? 'junction' : 'dir');
} catch (error) {
fs.renameSync(outsideParent, commandsParent);
if (['EPERM', 'EACCES', 'ENOSYS'].includes(error.code)) {
t.skip(`directory symlink creation unavailable: ${error.code || error.message}`);
return;
}
throw error;
}
const deployedLayoutPath = path.join(claudeDir, 'gsd-core', 'bin', 'lib', 'runtime-artifact-layout.cjs');
delete require.cache[deployedLayoutPath];
const deployed = require(deployedLayoutPath);
const priorOptIn = process.env.GSD_ALLOW_SYMLINKED_DEST;
process.env.GSD_ALLOW_SYMLINKED_DEST = '1';
t.after(() => {
if (priorOptIn === undefined) delete process.env.GSD_ALLOW_SYMLINKED_DEST;
else process.env.GSD_ALLOW_SYMLINKED_DEST = priorOptIn;
});
assert.throws(
() => deployed.findInstallSourceRoot(claudeDir),
/install or upgrade gsd-core/,
);
});
test('#4132: deployed agents layout rejects an installed agents corpus whose hash changed', () => {
const claudeDir = path.join(tmpRoot, '.claude');
fs.mkdirSync(claudeDir, { recursive: true });
runInstall(true /* isGlobal */, 'claude');
const manifest = JSON.parse(fs.readFileSync(path.join(claudeDir, 'gsd-file-manifest.json'), 'utf8'));
const agentKey = Object.keys(manifest.files).find((key) => key.startsWith('gsd-core/agents/'));
assert.ok(agentKey, 'precondition: install manifest must own at least one agent corpus file');
fs.appendFileSync(path.join(claudeDir, ...agentKey.split('/')), '\n# corrupted after install\n');
const deployedLayoutPath = path.join(claudeDir, 'gsd-core', 'bin', 'lib', 'runtime-artifact-layout.cjs');
delete require.cache[deployedLayoutPath];
const deployed = require(deployedLayoutPath);
assert.throws(
() => deployed.resolveRuntimeArtifactLayout('claude', claudeDir, 'global')
.kinds.find((kind) => kind.kind === 'agents').stage(PROFILE_CORE),
/install or upgrade gsd-core/,
);
});
// ── Adversarial marker-reader cases (no full install needed) ─────────────────
@@ -965,6 +1077,7 @@ describe('#1477 .gsd-source marker provisioning', () => {
test('marker pointing at a valid commands/gsd takes precedence over walk-up', () => {
const fakeSrc = path.join(cfgDir, 'pkg', 'commands', 'gsd');
fs.mkdirSync(fakeSrc, { recursive: true });
fs.writeFileSync(path.join(fakeSrc, 'gsd-test.md'), '# marker source\n');
fs.writeFileSync(path.join(cfgDir, '.gsd-source'), fakeSrc + '\n', 'utf8');
const resolved = findInstallSourceRoot(cfgDir);
@@ -981,11 +1094,335 @@ describe('#1477 .gsd-source marker provisioning', () => {
assert.equal(path.resolve(resolved), path.resolve(REPO_ROOT, 'commands', 'gsd'));
});
test('#4132: a missing installed leaf still rejects a marker containing it through a symlinked ancestor', (t) => {
const outsideCore = path.join(cfgDir, 'outside-core');
const markerCommands = path.join(outsideCore, 'commands');
const markerAgents = path.join(cfgDir, 'agents');
fs.mkdirSync(markerCommands, { recursive: true });
fs.mkdirSync(markerAgents, { recursive: true });
fs.writeFileSync(path.join(markerCommands, 'help.md'), '# marker command\n');
fs.writeFileSync(path.join(markerAgents, 'gsd-planner.md'), '# marker agent\n');
try {
fs.symlinkSync(
outsideCore,
path.join(cfgDir, 'gsd-core'),
process.platform === 'win32' ? 'junction' : 'dir',
);
} catch (error) {
t.skip(`directory symlink creation unavailable: ${error.code || error.message}`);
return;
}
fs.writeFileSync(path.join(cfgDir, '.gsd-source'), markerCommands + '\n');
assert.throws(
() => resolveRuntimeArtifactLayout('claude', cfgDir, 'global')
.kinds.find((kind) => kind.kind === 'skills').stage(PROFILE_CORE),
/install or upgrade gsd-core/,
);
});
test('empty / whitespace-only marker is ignored', () => {
fs.writeFileSync(path.join(cfgDir, '.gsd-source'), ' \n', 'utf8');
const resolved = findInstallSourceRoot(cfgDir);
assert.equal(path.resolve(resolved), path.resolve(REPO_ROOT, 'commands', 'gsd'));
});
test('one complete installed provider wins over a different complete marker provider', (t) => {
const installedCommands = path.join(cfgDir, 'gsd-core', 'commands', 'gsd');
const installedAgents = path.join(cfgDir, 'gsd-core', 'agents');
fs.mkdirSync(installedCommands, { recursive: true });
fs.mkdirSync(installedAgents, { recursive: true });
const installedCommandPath = path.join(installedCommands, 'help.md');
const installedAgentPath = path.join(installedAgents, 'gsd-planner.md');
fs.writeFileSync(installedCommandPath, '# installed command\n');
fs.writeFileSync(installedAgentPath, '# installed agent\n');
fs.writeFileSync(path.join(cfgDir, 'gsd-file-manifest.json'), JSON.stringify({ files: {
'gsd-core/commands/gsd/help.md': crypto.createHash('sha256').update(fs.readFileSync(installedCommandPath)).digest('hex'),
'gsd-core/agents/gsd-planner.md': crypto.createHash('sha256').update(fs.readFileSync(installedAgentPath)).digest('hex'),
} }));
const markerCommands = path.join(cfgDir, 'legacy-package', 'commands', 'gsd');
const markerAgents = path.join(cfgDir, 'legacy-package', 'agents');
fs.mkdirSync(markerCommands, { recursive: true });
fs.mkdirSync(markerAgents, { recursive: true });
fs.writeFileSync(path.join(markerCommands, 'help.md'), '# marker command\n');
fs.writeFileSync(path.join(markerAgents, 'gsd-planner.md'), '# marker agent\n');
fs.writeFileSync(path.join(cfgDir, '.gsd-source'), markerCommands + '\n');
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
const stagedSkills = layout.kinds.find((kind) => kind.kind === 'skills').stage(PROFILE_CORE);
const stagedAgents = layout.kinds.find((kind) => kind.kind === 'agents').stage(PROFILE_CORE);
t.after(() => cleanup(stagedSkills));
t.after(() => cleanup(stagedAgents));
assert.match(fs.readFileSync(path.join(stagedSkills, 'gsd-help', 'SKILL.md'), 'utf8'), /installed command/);
assert.match(fs.readFileSync(path.join(stagedAgents, 'gsd-planner.md'), 'utf8'), /installed agent/);
});
test('a partial installed corpus is not mixed with a complete marker provider', (t) => {
const installedCommands = path.join(cfgDir, 'gsd-core', 'commands', 'gsd');
fs.mkdirSync(installedCommands, { recursive: true });
fs.writeFileSync(path.join(installedCommands, 'help.md'), '# installed command\n');
const markerCommands = path.join(cfgDir, 'legacy-package', 'commands', 'gsd');
const markerAgents = path.join(cfgDir, 'legacy-package', 'agents');
fs.mkdirSync(markerCommands, { recursive: true });
fs.mkdirSync(markerAgents, { recursive: true });
fs.writeFileSync(path.join(markerCommands, 'help.md'), '# marker command\n');
fs.writeFileSync(path.join(markerAgents, 'gsd-planner.md'), '# marker agent\n');
fs.writeFileSync(path.join(cfgDir, '.gsd-source'), markerCommands + '\n');
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
const stagedSkills = layout.kinds.find((kind) => kind.kind === 'skills').stage(PROFILE_CORE);
const stagedAgents = layout.kinds.find((kind) => kind.kind === 'agents').stage(PROFILE_CORE);
t.after(() => cleanup(stagedSkills));
t.after(() => cleanup(stagedAgents));
assert.match(fs.readFileSync(path.join(stagedSkills, 'gsd-help', 'SKILL.md'), 'utf8'), /marker command/);
assert.match(fs.readFileSync(path.join(stagedAgents, 'gsd-planner.md'), 'utf8'), /marker agent/);
});
test('partial providers are not mixed when package fallback is disabled', () => {
const installedCommands = path.join(cfgDir, 'gsd-core', 'commands', 'gsd');
fs.mkdirSync(installedCommands, { recursive: true });
fs.writeFileSync(path.join(installedCommands, 'gsd-test.md'), '# installed command\n');
const markerCommands = path.join(cfgDir, 'legacy-package', 'commands', 'gsd');
fs.mkdirSync(markerCommands, { recursive: true });
fs.writeFileSync(path.join(markerCommands, 'gsd-test.md'), '# marker command\n');
fs.writeFileSync(path.join(cfgDir, '.gsd-source'), markerCommands + '\n');
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
assert.throws(() => layout.kinds.find((kind) => kind.kind === 'skills').stage(PROFILE_CORE), /install or upgrade gsd-core/);
});
test('ordinary Runtime Surface staging never falls back to the source checkout package', () => {
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
const skills = layout.kinds.find((kind) => kind.kind === 'skills');
assert.ok(skills);
assert.throws(
() => skills.stage(PROFILE_CORE),
/install or upgrade gsd-core/,
);
});
test('#4132: caller-supplied stage context cannot select installer source authority', () => {
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
const skills = layout.kinds.find((kind) => kind.kind === 'skills');
assert.throws(
() => skills.stage(PROFILE_CORE, {
[Symbol.for('@open-gsd/runtime-artifact-installer-source-authority')]: true,
}),
/install or upgrade gsd-core/,
);
assert.throws(
() => withInstallFs(undefined, () => skills.stage(PROFILE_CORE)),
/install or upgrade gsd-core/,
);
});
test('an installed corpus without a manifest is not a working fallback', () => {
const installedCommands = path.join(cfgDir, 'gsd-core', 'commands', 'gsd');
const installedAgents = path.join(cfgDir, 'gsd-core', 'agents');
fs.mkdirSync(installedCommands, { recursive: true });
fs.mkdirSync(installedAgents, { recursive: true });
fs.writeFileSync(path.join(installedCommands, 'help.md'), '# unverified command\n');
fs.writeFileSync(path.join(installedAgents, 'gsd-planner.md'), '# unverified agent\n');
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
assert.throws(
() => layout.kinds.find((kind) => kind.kind === 'skills').stage(PROFILE_CORE),
/install or upgrade gsd-core/,
);
});
test('an installed corpus whose bytes do not match its manifest is not a working fallback', () => {
const installedCommands = path.join(cfgDir, 'gsd-core', 'commands', 'gsd');
const installedAgents = path.join(cfgDir, 'gsd-core', 'agents');
fs.mkdirSync(installedCommands, { recursive: true });
fs.mkdirSync(installedAgents, { recursive: true });
fs.writeFileSync(path.join(installedCommands, 'help.md'), '# corrupted command\n');
fs.writeFileSync(path.join(installedAgents, 'gsd-planner.md'), '# corrupted agent\n');
fs.writeFileSync(path.join(cfgDir, 'gsd-file-manifest.json'), JSON.stringify({ files: {
'gsd-core/commands/gsd/help.md': '0'.repeat(64),
'gsd-core/agents/gsd-planner.md': '0'.repeat(64),
} }));
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
assert.throws(
() => layout.kinds.find((kind) => kind.kind === 'skills').stage(PROFILE_CORE),
/install or upgrade gsd-core/,
);
});
test('a hash-mismatched installed corpus falls back to an independent complete marker provider', (t) => {
const installedCommands = path.join(cfgDir, 'gsd-core', 'commands', 'gsd');
const installedAgents = path.join(cfgDir, 'gsd-core', 'agents');
fs.mkdirSync(installedCommands, { recursive: true });
fs.mkdirSync(installedAgents, { recursive: true });
fs.writeFileSync(path.join(installedCommands, 'help.md'), '# corrupted command\n');
fs.writeFileSync(path.join(installedAgents, 'gsd-planner.md'), '# corrupted agent\n');
fs.writeFileSync(path.join(cfgDir, 'gsd-file-manifest.json'), JSON.stringify({ files: {
'gsd-core/commands/gsd/help.md': '0'.repeat(64),
'gsd-core/agents/gsd-planner.md': '0'.repeat(64),
} }));
const markerCommands = path.join(cfgDir, 'legacy-package', 'commands', 'gsd');
const markerAgents = path.join(cfgDir, 'legacy-package', 'agents');
fs.mkdirSync(markerCommands, { recursive: true });
fs.mkdirSync(markerAgents, { recursive: true });
fs.writeFileSync(path.join(markerCommands, 'help.md'), '# marker command\n');
fs.writeFileSync(path.join(markerAgents, 'gsd-planner.md'), '# marker agent\n');
fs.writeFileSync(path.join(cfgDir, '.gsd-source'), markerCommands + '\n');
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
const stagedSkills = layout.kinds.find((kind) => kind.kind === 'skills').stage(PROFILE_CORE);
const stagedAgents = layout.kinds.find((kind) => kind.kind === 'agents').stage(PROFILE_CORE);
t.after(() => cleanup(stagedSkills));
t.after(() => cleanup(stagedAgents));
assert.match(fs.readFileSync(path.join(stagedSkills, 'gsd-help', 'SKILL.md'), 'utf8'), /marker command/);
assert.match(fs.readFileSync(path.join(stagedAgents, 'gsd-planner.md'), 'utf8'), /marker agent/);
});
test('#4132: a marker aliasing any rejected installed root is not an independent provider', (t) => {
const installedCommandsParent = path.join(cfgDir, 'gsd-core', 'commands');
const installedCommands = path.join(installedCommandsParent, 'gsd');
const installedAgents = path.join(cfgDir, 'gsd-core', 'agents');
fs.mkdirSync(installedCommands, { recursive: true });
fs.mkdirSync(installedAgents, { recursive: true });
fs.writeFileSync(path.join(installedCommands, 'help.md'), '# corrupted installed command\n');
const installedAgentPath = path.join(installedAgents, 'gsd-planner.md');
fs.writeFileSync(installedAgentPath, '# installed agent\n');
fs.writeFileSync(path.join(cfgDir, 'gsd-file-manifest.json'), JSON.stringify({ files: {
'gsd-core/commands/gsd/help.md': '0'.repeat(64),
'gsd-core/agents/gsd-planner.md': crypto.createHash('sha256').update(fs.readFileSync(installedAgentPath)).digest('hex'),
} }));
const markerRoot = path.join(cfgDir, 'hybrid-marker');
fs.mkdirSync(markerRoot, { recursive: true });
try {
fs.symlinkSync(
installedCommandsParent,
path.join(markerRoot, 'commands'),
process.platform === 'win32' ? 'junction' : 'dir',
);
} catch (error) {
t.skip(`directory symlink creation unavailable: ${error.code || error.message}`);
return;
}
const markerCommands = path.join(markerRoot, 'commands', 'gsd');
const markerAgents = path.join(markerRoot, 'agents');
fs.mkdirSync(markerAgents, { recursive: true });
fs.writeFileSync(path.join(markerAgents, 'gsd-planner.md'), '# independent marker agent\n');
fs.writeFileSync(path.join(cfgDir, '.gsd-source'), markerCommands + '\n');
assert.equal(fs.realpathSync(markerCommands), fs.realpathSync(installedCommands));
assert.notEqual(fs.realpathSync(markerAgents), fs.realpathSync(installedAgents));
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
const skills = layout.kinds.find((kind) => kind.kind === 'skills');
let stagedSkills;
t.after(() => { if (stagedSkills) cleanup(stagedSkills); });
assert.throws(
() => { stagedSkills = skills.stage(PROFILE_CORE); },
/install or upgrade gsd-core/,
);
});
test('#4132: a marker containing a rejected installed root is not an independent provider', () => {
const installedCommandsParent = path.join(cfgDir, 'gsd-core', 'commands');
const installedCommands = path.join(installedCommandsParent, 'gsd');
const installedAgents = path.join(cfgDir, 'gsd-core', 'agents');
const markerAgents = path.join(cfgDir, 'agents');
fs.mkdirSync(installedCommands, { recursive: true });
fs.mkdirSync(installedAgents, { recursive: true });
fs.mkdirSync(markerAgents, { recursive: true });
fs.writeFileSync(path.join(installedCommands, 'help.md'), '# corrupted installed command\n');
const installedAgentPath = path.join(installedAgents, 'gsd-planner.md');
fs.writeFileSync(installedAgentPath, '# installed agent\n');
fs.writeFileSync(path.join(installedCommandsParent, 'rogue.md'), '<instructions>ROGUE</instructions>\n');
fs.writeFileSync(path.join(markerAgents, 'gsd-planner.md'), '# marker agent\n');
fs.writeFileSync(path.join(cfgDir, 'gsd-file-manifest.json'), JSON.stringify({ files: {
'gsd-core/commands/gsd/help.md': '0'.repeat(64),
'gsd-core/agents/gsd-planner.md': crypto.createHash('sha256').update(fs.readFileSync(installedAgentPath)).digest('hex'),
} }));
fs.writeFileSync(path.join(cfgDir, '.gsd-source'), installedCommandsParent + '\n');
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
assert.throws(
() => layout.kinds.find((kind) => kind.kind === 'skills').stage(PROFILE_CORE),
/install or upgrade gsd-core/,
);
});
test('#4132: an agents descendant of a rejected installed root rejects the whole provider', () => {
const installedCommands = path.join(cfgDir, 'gsd-core', 'commands', 'gsd');
const installedAgents = path.join(cfgDir, 'gsd-core', 'agents');
fs.mkdirSync(installedCommands, { recursive: true });
fs.mkdirSync(installedAgents, { recursive: true });
const installedCommandPath = path.join(installedCommands, 'help.md');
fs.writeFileSync(installedCommandPath, '# installed command\n');
fs.writeFileSync(path.join(installedAgents, 'gsd-planner.md'), '# corrupted installed agent\n');
fs.writeFileSync(path.join(cfgDir, 'gsd-file-manifest.json'), JSON.stringify({ files: {
'gsd-core/commands/gsd/help.md': '0'.repeat(64),
'gsd-core/agents/gsd-planner.md': '0'.repeat(64),
} }));
const nestedProviderRoot = path.join(installedAgents, 'nested');
const markerCommands = path.join(nestedProviderRoot, 'commands', 'gsd');
const markerAgents = path.join(nestedProviderRoot, 'agents');
fs.mkdirSync(markerCommands, { recursive: true });
fs.mkdirSync(markerAgents, { recursive: true });
fs.writeFileSync(path.join(markerCommands, 'help.md'), '# marker command\n');
fs.writeFileSync(path.join(markerAgents, 'gsd-planner.md'), '# marker agent\n');
fs.writeFileSync(path.join(cfgDir, '.gsd-source'), markerCommands + '\n');
assert.equal(
fs.realpathSync(findInstallSourceRoot(cfgDir)),
fs.realpathSync(markerCommands),
'commands-only resolution must ignore overlap in the non-required agents class',
);
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
assert.throws(
() => layout.kinds.find((kind) => kind.kind === 'skills').stage(PROFILE_CORE),
/install or upgrade gsd-core/,
);
});
test('manifest-expected missing corpus file fails closed without reusing its marker alias', () => {
const installedCommands = path.join(cfgDir, 'gsd-core', 'commands', 'gsd');
const installedAgents = path.join(cfgDir, 'gsd-core', 'agents');
fs.mkdirSync(installedCommands, { recursive: true });
fs.mkdirSync(installedAgents, { recursive: true });
fs.writeFileSync(path.join(installedCommands, 'gsd-test.md'), '# installed command\n');
fs.writeFileSync(path.join(installedAgents, 'gsd-test-agent.md'), '# installed agent\n');
fs.writeFileSync(path.join(cfgDir, 'gsd-file-manifest.json'), JSON.stringify({ files: {
'gsd-core/commands/gsd/gsd-test.md': '0'.repeat(64),
'gsd-core/agents/gsd-test-agent.md': '0'.repeat(64),
'gsd-core/agents/removed.md': '0'.repeat(64),
} }));
fs.writeFileSync(path.join(cfgDir, '.gsd-source'), installedCommands + '\n');
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
assert.throws(() => layout.kinds.find((kind) => kind.kind === 'skills').stage(PROFILE_CORE), /install or upgrade gsd-core/);
});
test('a symlink inside the installed corpus is not a confined source', (t) => {
const installedCommands = path.join(cfgDir, 'gsd-core', 'commands', 'gsd');
const installedAgents = path.join(cfgDir, 'gsd-core', 'agents');
fs.mkdirSync(installedCommands, { recursive: true });
fs.mkdirSync(installedAgents, { recursive: true });
fs.writeFileSync(path.join(installedCommands, 'gsd-test.md'), '# installed command\n');
const outside = path.join(cfgDir, 'outside.md');
fs.writeFileSync(outside, '# outside\n');
try {
fs.symlinkSync(outside, path.join(installedAgents, 'gsd-test-agent.md'));
} catch (error) {
t.skip(`symlink creation unavailable: ${error.code || error.message}`);
return;
}
const layout = resolveRuntimeArtifactLayout('claude', cfgDir, 'global');
assert.throws(() => layout.kinds.find((kind) => kind.kind === 'skills').stage(PROFILE_CORE), /install or upgrade gsd-core/);
assert.strictEqual(fs.readFileSync(outside, 'utf8'), '# outside\n');
});
});
});
@@ -1054,8 +1491,7 @@ describe('#2624 .gsd-source marker is rewritten before staging reads it', () =>
cleanup(tmpRoot);
});
// The current package's commands/gsd — what the marker MUST point at after install.
const CURRENT_SOURCE = path.join(REPO_ROOT, 'commands', 'gsd');
const installedSource = (claudeDir) => path.join(claudeDir, 'gsd-core', 'commands', 'gsd');
test('claude-global install overwrites a stale .gsd-source marker before staging reads it', (t) => {
const claudeDir = path.join(tmpRoot, '.claude');
@@ -1090,8 +1526,8 @@ describe('#2624 .gsd-source marker is rewritten before staging reads it', () =>
const markerPath = path.join(claudeDir, '.gsd-source');
assert.ok(fs.existsSync(markerPath), 'marker must exist after install');
const finalMarker = path.resolve(fs.readFileSync(markerPath, 'utf8').trim());
assert.equal(finalMarker, path.resolve(CURRENT_SOURCE),
`final marker must point at the current package source, not ${finalMarker}`);
assert.equal(finalMarker, path.resolve(installedSource(claudeDir)),
`final marker must point at the installed current-version corpus, not ${finalMarker}`);
// The decisive assertion: staging must NEVER have resolved the stale source. Before the
// fix, at least one staging resolution returned the stale path (read before rewrite).
@@ -1112,8 +1548,8 @@ describe('#2624 .gsd-source marker is rewritten before staging reads it', () =>
const resolved = fs.readFileSync(markerPath, 'utf8').trim();
assert.equal(
path.resolve(resolved),
path.resolve(CURRENT_SOURCE),
'fresh install marker must point at the current package source',
path.resolve(installedSource(claudeDir)),
'fresh install marker must point at the installed corpus',
);
});
@@ -1132,8 +1568,8 @@ describe('#2624 .gsd-source marker is rewritten before staging reads it', () =>
const resolved = fs.readFileSync(markerPath, 'utf8').trim();
assert.equal(
path.resolve(resolved),
path.resolve(CURRENT_SOURCE),
'ghost marker must be rewritten to the current package source',
path.resolve(installedSource(claudeDir)),
'ghost marker must be rewritten to the installed corpus',
);
});
});