Files
msd-core/tests/agent-install-check.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

1507 lines
68 KiB
JavaScript

'use strict';
/**
* Agent Install Check Module — behaviour tests (#1268 T0, T1 #1277)
*
* Seam: msd-core/bin/lib/agent-install-check.cjs
* Interface: getAgentsDir, checkAgentsInstalled
*
* Verifies:
* 1. getAgentsDir behaviour: MSD_AGENTS_DIR override, claude path, non-claude path
* 2. checkAgentsInstalled behaviour against temp dirs via MSD_AGENTS_DIR:
* - missing dir → agents_installed:false, missing_agents = all expected
* - existing-but-empty dir → installed_agents:[], agents_installed:false
* - no manifest → completeness skipped (incomplete_agents empty)
* - partial manifest (agent.toml absent, agent.md present) → incomplete_agents includes agent
* - malformed manifest → no throw, completeness skipped
*/
const { describe, test, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { createTempDir, cleanup, captureFdSync } = require('./helpers.cjs');
const AGENT_INSTALL_CHECK_PATH = path.join(
__dirname, '..', 'msd-core', 'bin', 'lib', 'agent-install-check.cjs'
);
const RUNTIME_HOMES_PATH = path.join(
__dirname, '..', 'msd-core', 'bin', 'lib', 'runtime-homes.cjs'
);
const agentInstallCheck = require(AGENT_INSTALL_CHECK_PATH);
const { getGlobalConfigDir } = require(RUNTIME_HOMES_PATH);
const { getDirName } = require(path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'runtime-name-policy.cjs'));
// Get EXPECTED_AGENTS from model-profiles (same source of truth)
const MODEL_PROFILES = require(path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'model-profiles.cjs')).MODEL_PROFILES;
const EXPECTED_AGENTS = Object.keys(MODEL_PROFILES);
// #3242 — single source of truth for the Anthropic-flavored alias/id set (Phase 1
// moved the predicate here), so the posture tests below can't silently drift from
// what the posture check is actually supposed to reject.
const { CLAUDE_AGENT_ALIASES } = require(
path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'model-catalog.cjs'),
);
// ─── Environment isolation ────────────────────────────────────────────────────
let savedAgentsDir;
let savedRuntime;
let savedCodexHome;
beforeEach(() => {
savedAgentsDir = process.env['MSD_AGENTS_DIR'];
savedRuntime = process.env['MSD_RUNTIME'];
savedCodexHome = process.env['CODEX_HOME'];
delete process.env['MSD_AGENTS_DIR'];
delete process.env['MSD_RUNTIME'];
delete process.env['CODEX_HOME'];
});
afterEach(() => {
if (savedAgentsDir === undefined) {
delete process.env['MSD_AGENTS_DIR'];
} else {
process.env['MSD_AGENTS_DIR'] = savedAgentsDir;
}
if (savedRuntime === undefined) {
delete process.env['MSD_RUNTIME'];
} else {
process.env['MSD_RUNTIME'] = savedRuntime;
}
if (savedCodexHome === undefined) {
delete process.env['CODEX_HOME'];
} else {
process.env['CODEX_HOME'] = savedCodexHome;
}
});
function createCompleteAgents(agentsDir) {
fs.mkdirSync(agentsDir, { recursive: true });
for (const agent of EXPECTED_AGENTS) {
fs.writeFileSync(path.join(agentsDir, `${agent}.toml`), `name = "${agent}"\n`);
}
}
function markLocalMsdInstall(configDir) {
fs.writeFileSync(
path.join(configDir, 'msd-file-manifest.json'),
JSON.stringify({ files: {} }),
);
}
function createCompleteLocalMsdInstall(configDir) {
const agentsDir = path.join(configDir, 'agents');
createCompleteAgents(agentsDir);
markLocalMsdInstall(configDir);
return agentsDir;
}
// ─── 1. getAgentsDir behaviour ────────────────────────────────────────────────
describe('getAgentsDir', () => {
test('MSD_AGENTS_DIR override takes priority', () => {
process.env['MSD_AGENTS_DIR'] = '/tmp/x';
assert.strictEqual(agentInstallCheck.getAgentsDir(), '/tmp/x');
assert.strictEqual(agentInstallCheck.getAgentsDir('cursor'), '/tmp/x');
});
test('claude runtime outside node_modules returns the install-relative path', () => {
// Repo runs and runtime-config-dir installs: the sibling agents/ IS the
// user's agents dir, so install-relative resolution is correct there.
const expected = path.resolve(
path.dirname(AGENT_INSTALL_CHECK_PATH), '..', '..', '..', 'agents'
);
assert.strictEqual(agentInstallCheck.getAgentsDir('claude'), expected);
});
test('npm-global install resolves the config dir, never the bundled agents (#3203)', (t) => {
// Mirror the published npm-global layout: the package sits inside a
// node_modules tree and ships its own agents/. Pre-fix, getAgentsDir
// resolved that bundled copy, so checkAgentsInstalled validated the
// package against itself and agents_installed could never be false.
const tmp = createTempDir('msd-npm-global-');
const savedClaudeDir = process.env['CLAUDE_CONFIG_DIR'];
t.after(() => cleanup(tmp));
t.after(() => {
if (savedClaudeDir === undefined) {
delete process.env['CLAUDE_CONFIG_DIR'];
} else {
process.env['CLAUDE_CONFIG_DIR'] = savedClaudeDir;
}
});
const pkgRoot = path.join(tmp, 'node_modules', '@opengsd', 'msd-core');
fs.cpSync(
path.join(__dirname, '..', 'msd-core', 'bin'),
path.join(pkgRoot, 'msd-core', 'bin'),
{ recursive: true }
);
// Bundled agents/ is always complete — that is exactly why the pre-fix
// self-validation could never report a missing agent.
const bundledAgents = path.join(pkgRoot, 'agents');
fs.mkdirSync(bundledAgents, { recursive: true });
for (const agent of EXPECTED_AGENTS) {
fs.writeFileSync(path.join(bundledAgents, `${agent}.md`), `# ${agent}\n`);
}
// Config dir carries every expected agent except the first — the issue
// repro's negative control (msd-verifier removed from ~/.claude/agents).
const configDir = path.join(tmp, 'claude-config');
const agentsDir = path.join(configDir, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
const [removedAgent, ...presentAgents] = EXPECTED_AGENTS;
for (const agent of presentAgents) {
fs.writeFileSync(path.join(agentsDir, `${agent}.md`), `# ${agent}\n`);
}
process.env['CLAUDE_CONFIG_DIR'] = configDir;
const globalInstallCheck = require(
path.join(pkgRoot, 'msd-core', 'bin', 'lib', 'agent-install-check.cjs')
);
// Pin the resolved directory, not just an /agents suffix — the pre-fix
// resolver also ended with /agents, which is how the bug survived.
assert.strictEqual(globalInstallCheck.getAgentsDir('claude'), agentsDir);
const result = globalInstallCheck.checkAgentsInstalled('claude');
assert.strictEqual(result.agents_dir, agentsDir);
assert.strictEqual(result.agents_installed, false);
assert.deepStrictEqual(result.missing_agents, [removedAgent]);
});
test('non-claude runtime returns getGlobalConfigDir(runtime)/agents', () => {
const runtime = 'cursor';
const expected = path.join(getGlobalConfigDir(runtime), 'agents');
assert.strictEqual(agentInstallCheck.getAgentsDir(runtime), expected);
});
test('MSD_RUNTIME env var is respected when no argument provided', () => {
process.env['MSD_RUNTIME'] = 'codex';
const expected = path.join(getGlobalConfigDir('codex'), 'agents');
assert.strictEqual(agentInstallCheck.getAgentsDir(), expected);
});
test('defaults to claude when no arg and no MSD_RUNTIME', () => {
const fromModule = agentInstallCheck.getAgentsDir();
const fromClaude = agentInstallCheck.getAgentsDir('claude');
assert.strictEqual(fromModule, fromClaude);
});
test('a manifest-backed local runtime installation wins over global agents', (t) => {
const projectRoot = createTempDir('msd-local-codex-');
const globalHome = createTempDir('msd-global-codex-');
const localAgentsDir = createCompleteLocalMsdInstall(path.join(projectRoot, '.codex'));
t.after(() => cleanup(projectRoot));
t.after(() => cleanup(globalHome));
createCompleteAgents(path.join(globalHome, 'agents'));
process.env['CODEX_HOME'] = globalHome;
assert.strictEqual(agentInstallCheck.getAgentsDir('codex', projectRoot), localAgentsDir);
assert.strictEqual(agentInstallCheck.checkAgentsInstalled('codex', projectRoot).agents_installed, true);
});
test('MSD_AGENTS_DIR remains terminal when a local Codex installation exists', (t) => {
const projectRoot = createTempDir('msd-local-codex-');
const overrideDir = path.join(projectRoot, 'override-agents');
createCompleteLocalMsdInstall(path.join(projectRoot, '.codex'));
t.after(() => cleanup(projectRoot));
fs.mkdirSync(overrideDir, { recursive: true });
process.env['MSD_AGENTS_DIR'] = overrideDir;
const result = agentInstallCheck.checkAgentsInstalled('codex', projectRoot);
assert.strictEqual(result.agents_dir, overrideDir);
assert.strictEqual(result.agents_installed, false);
assert.deepStrictEqual(result.missing_agents, EXPECTED_AGENTS);
});
test('a manifest-backed empty local directory is authoritative over complete global agents', (t) => {
const projectRoot = createTempDir('msd-local-codex-');
const globalHome = createTempDir('msd-global-codex-');
const localAgentsDir = path.join(projectRoot, '.codex', 'agents');
t.after(() => cleanup(projectRoot));
t.after(() => cleanup(globalHome));
fs.mkdirSync(localAgentsDir, { recursive: true });
markLocalMsdInstall(path.dirname(localAgentsDir));
createCompleteAgents(path.join(globalHome, 'agents'));
process.env['CODEX_HOME'] = globalHome;
const result = agentInstallCheck.checkAgentsInstalled('codex', projectRoot);
assert.strictEqual(result.agents_dir, localAgentsDir);
assert.strictEqual(result.agents_installed, false);
assert.deepStrictEqual(result.missing_agents, EXPECTED_AGENTS);
});
test('Codex falls back to global agents when no local directory exists', (t) => {
const projectRoot = createTempDir('msd-local-codex-');
const globalHome = createTempDir('msd-global-codex-');
t.after(() => cleanup(projectRoot));
t.after(() => cleanup(globalHome));
createCompleteAgents(path.join(globalHome, 'agents'));
process.env['CODEX_HOME'] = globalHome;
const result = agentInstallCheck.checkAgentsInstalled('codex', projectRoot);
assert.strictEqual(result.agents_dir, path.join(globalHome, 'agents'));
assert.strictEqual(result.agents_installed, true);
});
test('Codex falls back to global agents when the local candidate is a regular file', (t) => {
const projectRoot = createTempDir('msd-local-codex-');
const globalHome = createTempDir('msd-global-codex-');
const localCandidate = path.join(projectRoot, '.codex', 'agents');
t.after(() => cleanup(projectRoot));
t.after(() => cleanup(globalHome));
fs.mkdirSync(path.dirname(localCandidate), { recursive: true });
fs.writeFileSync(localCandidate, 'not an agents directory\n');
createCompleteAgents(path.join(globalHome, 'agents'));
process.env['CODEX_HOME'] = globalHome;
const result = agentInstallCheck.checkAgentsInstalled('codex', projectRoot);
assert.strictEqual(result.agents_dir, path.join(globalHome, 'agents'));
assert.strictEqual(result.agents_installed, true);
});
test('Codex falls back to global agents when the local candidate cannot be inspected', (t) => {
const projectRoot = createTempDir('msd-local-codex-');
const globalHome = createTempDir('msd-global-codex-');
const localAgentsDir = path.join(projectRoot, '.codex', 'agents');
const realLstatSync = fs.lstatSync;
t.after(() => cleanup(projectRoot));
t.after(() => cleanup(globalHome));
fs.mkdirSync(localAgentsDir, { recursive: true });
markLocalMsdInstall(path.dirname(localAgentsDir));
createCompleteAgents(path.join(globalHome, 'agents'));
process.env['CODEX_HOME'] = globalHome;
t.mock.method(fs, 'lstatSync', function injectedLocalProbeFailure(target, ...args) {
if (target === localAgentsDir) {
throw Object.assign(new Error('injected EACCES'), { code: 'EACCES' });
}
return realLstatSync.call(fs, target, ...args);
});
const result = agentInstallCheck.checkAgentsInstalled('codex', projectRoot);
assert.strictEqual(result.agents_dir, path.join(globalHome, 'agents'));
assert.strictEqual(result.agents_installed, true);
});
test('Codex does not follow a symlinked local agents directory', (t) => {
const projectRoot = createTempDir('msd-local-codex-');
const globalHome = createTempDir('msd-global-codex-');
const localConfigDir = path.join(projectRoot, '.codex');
const localAgentsDir = path.join(localConfigDir, 'agents');
const symlinkTarget = path.join(projectRoot, 'shared-agents');
t.after(() => cleanup(projectRoot));
t.after(() => cleanup(globalHome));
createCompleteAgents(symlinkTarget);
fs.mkdirSync(localConfigDir, { recursive: true });
markLocalMsdInstall(localConfigDir);
createCompleteAgents(path.join(globalHome, 'agents'));
process.env['CODEX_HOME'] = globalHome;
try {
fs.symlinkSync(symlinkTarget, localAgentsDir, process.platform === 'win32' ? 'junction' : 'dir');
} catch (error) {
if (error && ['EPERM', 'EACCES', 'ENOTSUP'].includes(error.code)) {
t.skip('symlink creation is not available on this platform');
return;
}
throw error;
}
const result = agentInstallCheck.checkAgentsInstalled('codex', projectRoot);
assert.strictEqual(result.agents_dir, path.join(globalHome, 'agents'));
assert.strictEqual(result.agents_installed, true);
});
test('a project-native agents directory without a MSD manifest does not override global agents', (t) => {
const projectRoot = createTempDir('msd-local-codex-');
const globalHome = createTempDir('msd-global-codex-');
const localAgentsDir = path.join(projectRoot, '.codex', 'agents');
t.after(() => cleanup(projectRoot));
t.after(() => cleanup(globalHome));
createCompleteAgents(localAgentsDir);
createCompleteAgents(path.join(globalHome, 'agents'));
process.env['CODEX_HOME'] = globalHome;
const result = agentInstallCheck.checkAgentsInstalled('codex', projectRoot);
assert.strictEqual(result.agents_dir, path.join(globalHome, 'agents'));
assert.strictEqual(result.agents_installed, true);
});
test('a manifest-backed Cursor installation resolves from the project root', (t) => {
const projectRoot = createTempDir('msd-local-cursor-');
const localAgentsDir = createCompleteLocalMsdInstall(path.join(projectRoot, getDirName('cursor')));
t.after(() => cleanup(projectRoot));
assert.strictEqual(agentInstallCheck.getAgentsDir('cursor', projectRoot), localAgentsDir);
assert.strictEqual(agentInstallCheck.checkAgentsInstalled('cursor', projectRoot).agents_installed, true);
});
test('a manifest-backed Cline installation resolves from the project root', (t) => {
const projectRoot = createTempDir('msd-local-cline-');
const localAgentsDir = createCompleteLocalMsdInstall(projectRoot);
t.after(() => cleanup(projectRoot));
assert.strictEqual(agentInstallCheck.getAgentsDir('cline', projectRoot), localAgentsDir);
assert.strictEqual(agentInstallCheck.checkAgentsInstalled('cline', projectRoot).agents_installed, true);
});
test('a partial manifest-backed local installation remains selected and incomplete', (t) => {
const projectRoot = createTempDir('msd-local-codex-');
const localConfigDir = path.join(projectRoot, '.codex');
const localAgentsDir = createCompleteLocalMsdInstall(localConfigDir);
const partialAgent = EXPECTED_AGENTS[0];
t.after(() => cleanup(projectRoot));
fs.writeFileSync(path.join(localAgentsDir, `${partialAgent}.md`), `# ${partialAgent}\n`);
fs.unlinkSync(path.join(localAgentsDir, `${partialAgent}.toml`));
fs.writeFileSync(
path.join(localConfigDir, 'msd-file-manifest.json'),
JSON.stringify({ files: { [`agents/${partialAgent}.md`]: {}, [`agents/${partialAgent}.toml`]: {} } }),
);
const result = agentInstallCheck.checkAgentsInstalled('codex', projectRoot);
assert.strictEqual(result.agents_dir, localAgentsDir);
assert.strictEqual(result.agents_installed, false);
assert.deepStrictEqual(result.missing_agents, []);
assert.deepStrictEqual(result.incomplete_agents, [partialAgent]);
});
test('Claude and other runtimes ignore a supplied Codex-local candidate', (t) => {
const projectRoot = createTempDir('msd-local-codex-');
t.after(() => cleanup(projectRoot));
createCompleteLocalMsdInstall(path.join(projectRoot, '.codex'));
assert.strictEqual(
agentInstallCheck.getAgentsDir('claude', projectRoot),
agentInstallCheck.getAgentsDir('claude'),
);
assert.strictEqual(
agentInstallCheck.getAgentsDir('cursor', projectRoot),
path.join(getGlobalConfigDir('cursor'), 'agents'),
);
});
});
// ─── 2. checkAgentsInstalled behaviour ───────────────────────────────────────
describe('checkAgentsInstalled', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempDir('msd-agent-check-');
// Point MSD_AGENTS_DIR at a path we control
process.env['MSD_AGENTS_DIR'] = path.join(tmpDir, 'agents');
});
afterEach(() => {
cleanup(tmpDir);
});
test('missing dir → agents_installed:false, missing_agents = all expected', () => {
// agents dir does not exist
const result = agentInstallCheck.checkAgentsInstalled();
assert.strictEqual(result.agents_installed, false);
assert.deepStrictEqual(result.missing_agents, EXPECTED_AGENTS);
assert.deepStrictEqual(result.installed_agents, []);
assert.deepStrictEqual(result.incomplete_agents, []);
});
test('existing-but-empty dir → installed_agents:[], agents_installed:false', () => {
const agentsDir = path.join(tmpDir, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
const result = agentInstallCheck.checkAgentsInstalled();
assert.strictEqual(result.agents_installed, false);
assert.deepStrictEqual(result.installed_agents, []);
assert.ok(result.missing_agents.length > 0, 'missing_agents should not be empty');
// No manifest → completeness skipped
assert.deepStrictEqual(result.incomplete_agents, []);
});
test('all agents present, no manifest → agents_installed:true, incomplete_agents:[]', () => {
const agentsDir = path.join(tmpDir, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
// Write all expected agent .md files
for (const agent of EXPECTED_AGENTS) {
fs.writeFileSync(path.join(agentsDir, `${agent}.md`), `# ${agent}\n`);
}
const result = agentInstallCheck.checkAgentsInstalled();
assert.strictEqual(result.agents_installed, true);
assert.deepStrictEqual(result.missing_agents, []);
assert.deepStrictEqual(result.installed_agents, EXPECTED_AGENTS);
assert.deepStrictEqual(result.incomplete_agents, []);
});
test('partial manifest: agent.toml absent but agent.md present → incomplete_agents includes agent', () => {
const agentsDir = path.join(tmpDir, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
// Write all agent .md files so presence check passes
for (const agent of EXPECTED_AGENTS) {
fs.writeFileSync(path.join(agentsDir, `${agent}.md`), `# ${agent}\n`);
}
// Pick the first expected agent to make "incomplete" via manifest
const targetAgent = EXPECTED_AGENTS[0];
// Write manifest that tracks agent.toml for targetAgent (absent on disk)
// and tracks agent.md for all others (present)
const manifestFiles = {};
for (const agent of EXPECTED_AGENTS) {
manifestFiles[`agents/${agent}.md`] = {};
}
// Add a .toml for targetAgent to manifest (not present on disk)
manifestFiles[`agents/${targetAgent}.toml`] = {};
const manifest = { files: manifestFiles };
fs.writeFileSync(
path.join(tmpDir, 'msd-file-manifest.json'),
JSON.stringify(manifest)
);
const result = agentInstallCheck.checkAgentsInstalled();
assert.ok(result.incomplete_agents.includes(targetAgent),
`Expected ${targetAgent} in incomplete_agents, got: ${JSON.stringify(result.incomplete_agents)}`);
assert.strictEqual(result.agents_installed, false,
'agents_installed must be false when any agent is incomplete');
});
test('malformed manifest → no throw, completeness skipped (incomplete_agents:[])', () => {
const agentsDir = path.join(tmpDir, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
// Write all agent files
for (const agent of EXPECTED_AGENTS) {
fs.writeFileSync(path.join(agentsDir, `${agent}.md`), `# ${agent}\n`);
}
// Write malformed manifest
fs.writeFileSync(path.join(tmpDir, 'msd-file-manifest.json'), '{not json"');
let result;
assert.doesNotThrow(() => {
result = agentInstallCheck.checkAgentsInstalled();
});
// Malformed → completeness skipped → incomplete_agents empty
assert.deepStrictEqual(result.incomplete_agents, []);
// But presence check still passed
assert.strictEqual(result.agents_installed, true);
});
test('agents_dir and agent_runtime are returned in result', () => {
const agentsDir = path.join(tmpDir, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
const result = agentInstallCheck.checkAgentsInstalled('cursor');
// MSD_AGENTS_DIR overrides, so agents_dir = our tmp path
assert.strictEqual(result.agents_dir, agentsDir);
assert.strictEqual(result.agent_runtime, 'cursor');
});
});
// ─── 3. checkCodexModelPosture behaviour (#3242, ADR-2313 D6) ─────────────────
//
// Spec: .msd/phase/feat-3242-codex-posture-health-check/{40-design,50-test-matrix}.md
// Interface (not yet implemented — every test below is red until it lands):
// POSTURE_REASON: frozen enum { ANTHROPIC_FLAVORED_MODEL, ORPHANED_REASONING_EFFORT,
// UNREADABLE, NOT_CODEX, AGENTS_DIR_MISSING }
// checkCodexModelPosture(runtime?, projectRoot?) => {
// ok, violations: [{ agent, file, reason, value? }], checked, agents_dir,
// agent_runtime, reason?
// }
//
// Row numbers below (# N) map 1:1 to 50-test-matrix.md. Rows 12, 13, 14, 15, 16, 25
// are the negative proofs the matrix calls out as the ones that actually discriminate
// a correct implementation from a naive whole-file `/model\s*=/` scan; each of those
// test names states the specific implementation mistake it catches.
const POSTURE_FIXTURES_DIR = path.join(__dirname, 'fixtures', 'adversarial', 'toml');
function writeAgentToml(agentsDir, agentName, content) {
fs.mkdirSync(agentsDir, { recursive: true });
fs.writeFileSync(path.join(agentsDir, `${agentName}.toml`), content);
}
// Loads a hand-authored fixture (tests/fixtures/adversarial/toml/, #2371 provenance)
// as raw bytes so CRLF / BOM content is copied byte-for-byte, not re-encoded through
// a JS string round-trip that could normalize either.
function copyFixtureToml(agentsDir, agentName, fixtureFile) {
fs.mkdirSync(agentsDir, { recursive: true });
const raw = fs.readFileSync(path.join(POSTURE_FIXTURES_DIR, fixtureFile));
fs.writeFileSync(path.join(agentsDir, `${agentName}.toml`), raw);
}
// Row 18a's CRLF fixture is derived at test runtime, never read from a committed file.
// `.gitattributes:2` is `* text=auto eol=lf`, repo-wide and deliberate, so a `\r\n`
// fixture committed to disk is normalized to LF on every commit and every checkout —
// a whole-file CRLF fixture proves nothing about CRLF handling once git has touched it.
// Authoring the LF content inline and converting it here keeps the CRLF-ness under the
// test's control instead of git's. (The BOM fixture is unaffected by eol=lf — a BOM is
// not a line ending — so it stays a committed file.)
function toCrlf(lfContent) {
return lfContent.replace(/\n/g, '\r\n');
}
// Fault injection for row 21 — monkeypatches node:fs's readFileSync and restores it
// in a `finally` INSIDE this helper (never in a test body, and never chmod 0o000,
// which root bypasses under Docker/CI and would give the test zero real coverage).
function withInjectedReadFailure(targetPath, injectedError, fn) {
const realReadFileSync = fs.readFileSync;
fs.readFileSync = function poisonedReadFileSync(target, ...args) {
const targetStr = typeof target === 'string' ? target : String(target);
if (targetStr === targetPath || path.resolve(targetStr) === path.resolve(targetPath)) {
throw injectedError;
}
return realReadFileSync.apply(fs, [target, ...args]);
};
try {
return fn();
} finally {
fs.readFileSync = realReadFileSync;
}
}
describe('checkCodexModelPosture', () => {
let tmpDir;
let agentsDir;
beforeEach(() => {
tmpDir = createTempDir('msd-posture-check-');
agentsDir = path.join(tmpDir, 'agents');
process.env['MSD_AGENTS_DIR'] = agentsDir;
});
afterEach(() => {
cleanup(tmpDir);
});
// # 1 — runtime is not codex: a no-op, not a failure, and it must not even read
// the filesystem to get there (checkAgentsInstalled's job is presence; this
// function's job starts only once the runtime is actually codex).
test('row 1: non-codex runtime (claude) is a no-op — NOT_CODEX, no violations, no fs.readFileSync call', (t) => {
const reads = [];
t.mock.method(fs, 'readFileSync', (...args) => {
reads.push(args[0]);
throw new Error('unreachable: readFileSync must not be called for a non-codex runtime');
});
const result = agentInstallCheck.checkCodexModelPosture('claude', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
assert.strictEqual(result.reason, agentInstallCheck.POSTURE_REASON.NOT_CODEX);
assert.deepStrictEqual(reads, [], 'non-codex runtime must short-circuit before any file read');
});
// # 2 — codex runtime, agents dir absent: distinct from row-3 empty-dir and
// distinct from a violation. Presence is checkAgentsInstalled's job.
test('row 2: codex + agents dir absent — AGENTS_DIR_MISSING, not a violation', () => {
// agentsDir intentionally not created
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
assert.strictEqual(result.reason, agentInstallCheck.POSTURE_REASON.AGENTS_DIR_MISSING);
});
// # 3 — codex runtime, agents dir exists but is empty.
test('row 3: codex + empty agents dir — ok:true, checked:[]', () => {
fs.mkdirSync(agentsDir, { recursive: true });
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
assert.deepStrictEqual(result.checked, []);
});
// # 4 — one clean .toml (no model, no effort key at all).
test('row 4: clean .toml (no model, no effort) — ok:true, no violations', () => {
writeAgentToml(
agentsDir,
'msd-clean',
'name = "msd-clean"\ndescription = "a clean agent"\ndeveloper_instructions = \'\'\'\nDo the work.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
});
// # 5 — the base case: a bare Claude tier alias pinned as `model`.
test('row 5: model = "sonnet" — ANTHROPIC_FLAVORED_MODEL naming the agent and the value', () => {
writeAgentToml(
agentsDir,
'msd-planner',
'name = "msd-planner"\nmodel = "sonnet"\ndeveloper_instructions = \'\'\'\nPlan.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, false);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].agent, 'msd-planner');
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
assert.strictEqual(result.violations[0].value, 'sonnet');
assert.ok(result.violations[0].file.endsWith('msd-planner.toml'));
});
// # 6 — table-driven over the full bare-alias set, sourced from model-catalog's
// CLAUDE_AGENT_ALIASES (not hardcoded) so this can't silently drift from the
// predicate the design says the posture check consumes.
for (const alias of CLAUDE_AGENT_ALIASES) {
test(`row 6: bare Claude alias "${alias}" — ANTHROPIC_FLAVORED_MODEL`, () => {
writeAgentToml(
agentsDir,
'msd-alias-agent',
`name = "msd-alias-agent"\nmodel = "${alias}"\ndeveloper_instructions = '''\nWork.\n'''\n`,
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
assert.strictEqual(result.violations[0].value, alias);
});
}
// # 7 — table-driven over full Claude model ids across provider namespacings,
// plus a case-insensitivity check.
for (const modelId of ['claude-opus-4-5', 'anthropic/claude-x', 'us.anthropic.claude-x', 'CLAUDE-X']) {
test(`row 7: Claude model id "${modelId}" — ANTHROPIC_FLAVORED_MODEL`, () => {
writeAgentToml(
agentsDir,
'msd-id-agent',
`name = "msd-id-agent"\nmodel = "${modelId}"\ndeveloper_instructions = '''\nWork.\n'''\n`,
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
});
}
// # 8 — the rule is Anthropic-flavored, never an allowlist: a real Codex/OpenAI id
// must never be flagged, however unfamiliar it looks.
test('row 8: model = "gpt-5.6-sol" — no violation (not an allowlist)', () => {
writeAgentToml(
agentsDir,
'msd-gpt-agent',
'name = "msd-gpt-agent"\nmodel = "gpt-5.6-sol"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
});
// # 9 — must not go stale on a hypothetical future OpenAI release id.
test('row 9: model = "some-future-model-id" — no violation', () => {
writeAgentToml(
agentsDir,
'msd-future-agent',
'name = "msd-future-agent"\nmodel = "some-future-model-id"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
});
// # 10 — #838 coupling: a static reasoning-effort with no model pin means Codex
// is inheriting the session model while MSD's effort pin silently disagrees.
test('row 10: model_reasoning_effort with no model — ORPHANED_REASONING_EFFORT', () => {
writeAgentToml(
agentsDir,
'msd-orphan-agent',
'name = "msd-orphan-agent"\nmodel_reasoning_effort = "high"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, false);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ORPHANED_REASONING_EFFORT);
assert.strictEqual(result.violations[0].agent, 'msd-orphan-agent');
});
// # 11 — the legal pinned pair: model + matching reasoning effort is intentional.
test('row 11: model + model_reasoning_effort, model legal — no violation', () => {
writeAgentToml(
agentsDir,
'msd-pinned-agent',
'name = "msd-pinned-agent"\nmodel = "gpt-5-codex"\nmodel_reasoning_effort = "high"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
});
// # 12 — NEGATIVE PROOF. Catches: over-generalizing the #838 model/effort
// coupling rule to service_tier/model_verbosity too. Those are #774's
// cost/verbosity knobs and are decoupled from `model` by design — an
// implementation that treats "any knob present without model" as orphaned
// fails this row.
test('row 12 (negative proof): service_tier + model_verbosity, no model — no violation', () => {
writeAgentToml(
agentsDir,
'msd-light-agent',
'name = "msd-light-agent"\nservice_tier = "flex"\nmodel_verbosity = "low"\ndeveloper_instructions = \'\'\'\nWork fast.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
});
// # 13 — NEGATIVE PROOF. Catches: implementing the check as a whitelist over the
// whole TOML document (flagging any key MSD doesn't itself emit) instead of a
// predicate on exactly the two fields the posture owns (`model`,
// `model_reasoning_effort`). A hand-added `approval_policy` is legitimate and
// must never be flagged.
test('row 13 (negative proof): extra hand-added key (approval_policy) — no violation', () => {
writeAgentToml(
agentsDir,
'msd-custom-agent',
'name = "msd-custom-agent"\napproval_policy = "on-request"\ndeveloper_instructions = \'\'\'\nFollow policy.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
});
// # 14 — NEGATIVE PROOF, the headline trap. Catches: scanning the WHOLE file with
// a line-oriented `/^model\s*=/m` instead of only the header slice (the lines
// before `developer_instructions = '''`). The fixture below contains a literal,
// unindented `model = "sonnet"` line INSIDE the developer_instructions block —
// verified (see PR description / dispatch notes) to trip a naive whole-file
// regex scan (`/^model\s*=/m.test(wholeFile) === true`) while the correct
// header-slice scan sees nothing (`/^model\s*=/m.test(headerOnly) === false`).
// If this test passed against a whole-file scanner it would prove nothing; it
// must fail against one.
test('row 14 (negative proof, headline): model = inside developer_instructions block — no violation', () => {
copyFixtureToml(agentsDir, 'msd-planner', 'model-in-developer-instructions.toml');
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
});
// # 15 — NEGATIVE PROOF. Catches: a header-slice scan that doesn't skip comment
// lines, so a commented-out `# model = "sonnet"` still counts as a live pin.
test('row 15 (negative proof): commented-out model pin — no violation', () => {
copyFixtureToml(agentsDir, 'msd-reviewer', 'commented-model-pin.toml');
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
});
// # 16 — NEGATIVE PROOF. Catches: probing for the key by substring/prefix
// (e.g. a bare `/model/` test) instead of anchoring on the full key name, so
// `model_verbosity` gets misidentified as a `model` pin.
test('row 16 (negative proof): model_verbosity only (key-prefix collision) — no violation', () => {
copyFixtureToml(agentsDir, 'msd-analyst', 'model-verbosity-prefix-collision.toml');
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
});
// # 17 — boundary: whitespace around a REAL key must still be recognized as a pin.
test('row 17: indented / inner-spaced model pin — IS a violation', () => {
copyFixtureToml(agentsDir, 'msd-tester', 'whitespace-indented-model-pin.toml');
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, false);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
assert.strictEqual(result.violations[0].value, 'sonnet');
});
// # 18 — cross-platform: CRLF and BOM must parse identically to plain LF.
test('row 18a: CRLF file with a pinned model — parsed identically to LF (still a violation)', () => {
const lfContent =
'name = "msd-scribe"\ndescription = "Writes changelog entries from merged PRs"\n' +
'model = "sonnet"\ndeveloper_instructions = \'\'\'\n' +
'Write a changelog entry summarizing the merged pull request.\n\'\'\'\n';
writeAgentToml(agentsDir, 'msd-scribe', toCrlf(lfContent));
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
assert.strictEqual(result.violations[0].value, 'sonnet');
});
test('row 18b: BOM-prefixed file with a pinned model — parsed identically to a BOM-free file', () => {
copyFixtureToml(agentsDir, 'msd-archivist', 'bom-pinned-model.toml');
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
assert.strictEqual(result.violations[0].value, 'sonnet');
});
// # 19 — independence: multiple agents, some violating; deterministic order;
// clean agents are simply absent from violations (not present-but-empty).
test('row 19: several agents, some violating — one entry per offender, deterministic order, clean absent', () => {
writeAgentToml(
agentsDir,
'msd-alpha',
'name = "msd-alpha"\ndeveloper_instructions = \'\'\'\nClean.\n\'\'\'\n',
);
writeAgentToml(
agentsDir,
'msd-bravo',
'name = "msd-bravo"\nmodel = "opus"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n',
);
writeAgentToml(
agentsDir,
'msd-charlie',
'name = "msd-charlie"\nmodel_reasoning_effort = "medium"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, false);
assert.strictEqual(result.violations.length, 2);
assert.strictEqual(result.violations[0].agent, 'msd-bravo');
assert.strictEqual(result.violations[1].agent, 'msd-charlie');
assert.ok(
!result.violations.some((v) => v.agent === 'msd-alpha'),
'clean agent must not appear in violations at all',
);
assert.strictEqual(result.checked.length, 3);
});
// ─── Reviewer-found false negatives (quoted keys; block-marker truncation) ──
//
// Both defects are false negatives — checkCodexModelPosture reported ok:true
// when a real pin was present. Each test below is red against the
// implementation this PR replaces; see the PR description for exactly which
// assertion fails against each.
// Defect 1: a quoted TOML key (`"model" = ...`) is legal TOML and was invisible
// to a key regex that required a bare identifier.
test('quoted key "model" = "sonnet" (double-quoted) — still ANTHROPIC_FLAVORED_MODEL', () => {
writeAgentToml(
agentsDir,
'msd-quoted-double',
'name = "msd-quoted-double"\n"model" = "sonnet"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, false);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
assert.strictEqual(result.violations[0].value, 'sonnet');
});
test("quoted key 'model' = \"sonnet\" (single-quoted) — still ANTHROPIC_FLAVORED_MODEL", () => {
writeAgentToml(
agentsDir,
'msd-quoted-single',
'name = "msd-quoted-single"\n\'model\' = "sonnet"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, false);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
assert.strictEqual(result.violations[0].value, 'sonnet');
});
// Defect 2a: the block marker used to be found by an unanchored whole-content
// search, so a `description` value that merely quotes the marker text earlier
// in the file truncated the header before a real, later `model` pin.
test('a description value quoting the marker text does not hide a real model pin before it', () => {
writeAgentToml(
agentsDir,
'msd-decoy-marker',
'name = "msd-decoy-marker"\n' +
'description = "mentions developer_instructions = \'\'\' as an example string"\n' +
'model = "sonnet"\n' +
'developer_instructions = \'\'\'\nWork.\n\'\'\'\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, false);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
assert.strictEqual(result.violations[0].value, 'sonnet');
});
// Defect 2b: the old implementation only ever scanned the slice BEFORE the
// marker, so a hand-reordered file with `model` placed AFTER the
// developer_instructions block (still legal TOML) was never scanned at all.
test('a model pin placed AFTER the developer_instructions block is still flagged', () => {
writeAgentToml(
agentsDir,
'msd-reordered',
'name = "msd-reordered"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\nmodel = "sonnet"\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, false);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
assert.strictEqual(result.violations[0].value, 'sonnet');
});
// Defect-2 boundary: no developer_instructions block at all — every line must
// be scanned. NOTE: a bare-key version of this fixture is already green
// against the pre-fix implementation (its no-marker fallback already scanned
// the whole file), so this uses a quoted key to keep the assertion genuinely
// red pre-fix (via defect 1) while proving the no-block path is fully scanned.
test('a file with no developer_instructions block at all is fully scanned', () => {
writeAgentToml(
agentsDir,
'msd-noblock',
'name = "msd-noblock"\ndescription = "no prompt block on this agent"\n"model" = "sonnet"\n',
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, false);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
assert.strictEqual(result.violations[0].value, 'sonnet');
});
// # 21 — filesystem failure: an unreadable .toml is reported, not thrown, and
// does not abort checking the rest of the agents. Injected via fs.readFileSync
// monkeypatch/restore (see withInjectedReadFailure) rather than chmod 0o000,
// which root bypasses under Docker/CI.
test('row 21: unreadable .toml (EACCES) — UNREADABLE violation naming the file, other agents still checked, no throw', () => {
writeAgentToml(agentsDir, 'msd-bad', 'name = "msd-bad"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n');
writeAgentToml(agentsDir, 'msd-good', 'name = "msd-good"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n');
const badPath = path.join(agentsDir, 'msd-bad.toml');
const injected = Object.assign(new Error('injected EACCES'), { code: 'EACCES' });
const result = withInjectedReadFailure(badPath, injected, () =>
agentInstallCheck.checkCodexModelPosture('codex', tmpDir),
);
assert.strictEqual(result.ok, false);
const unreadable = result.violations.find((v) => v.agent === 'msd-bad');
assert.ok(unreadable, 'unreadable file must produce a named violation, not a silent skip');
assert.strictEqual(unreadable.reason, agentInstallCheck.POSTURE_REASON.UNREADABLE);
assert.ok(unreadable.file.endsWith('msd-bad.toml'));
assert.strictEqual(result.checked.length, 2, 'the unreadable file must still be counted as checked');
assert.ok(
!result.violations.some((v) => v.agent === 'msd-good'),
'the still-readable sibling must be checked and found clean',
);
});
// Security review (#3242, MEDIUM): readdirSync + readFileSync followed symlinks,
// so a symlink in the agents directory pointing at an arbitrary file could have
// that file's content echoed into a violation's `value` field. Fixed by lstat-
// filtering to regular files only, matching cmdEffortSync's existing symlink
// guard in commands.cts. Symlinks are silently excluded (not reported), same
// as cmdEffortSync — see agent-install-check.cts inline comment for why.
test('symlink pointing at a file containing model = "sonnet" is never read — no violation names that value', (t) => {
const targetPath = path.join(tmpDir, 'outside-target.toml');
fs.writeFileSync(targetPath, 'model = "sonnet"\n');
fs.mkdirSync(agentsDir, { recursive: true });
const symlinkPath = path.join(agentsDir, 'msd-linked.toml');
try {
fs.symlinkSync(targetPath, symlinkPath, 'file');
} catch (error) {
if (error && ['EPERM', 'EACCES', 'ENOTSUP'].includes(error.code)) {
t.skip('symlink creation is not available on this platform');
return;
}
throw error;
}
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
assert.deepStrictEqual(result.checked, [], 'the symlinked entry must not appear in checked');
});
test('broken symlink in agents dir does not crash the scan — other agents still checked', (t) => {
fs.mkdirSync(agentsDir, { recursive: true });
const brokenTarget = path.join(tmpDir, 'does-not-exist.toml');
const brokenSymlink = path.join(agentsDir, 'msd-broken.toml');
try {
fs.symlinkSync(brokenTarget, brokenSymlink, 'file');
} catch (error) {
if (error && ['EPERM', 'EACCES', 'ENOTSUP'].includes(error.code)) {
t.skip('symlink creation is not available on this platform');
return;
}
throw error;
}
writeAgentToml(agentsDir, 'msd-good', 'name = "msd-good"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n');
let result;
assert.doesNotThrow(() => {
result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
});
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
assert.deepStrictEqual(result.checked, ['msd-good'], 'the broken symlink must be excluded, the other agent still checked');
});
test('a regular .toml file is still scanned normally (guard against over-filtering)', () => {
writeAgentToml(agentsDir, 'msd-planner', 'name = "msd-planner"\nmodel = "sonnet"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n');
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, false);
assert.deepStrictEqual(result.checked, ['msd-planner']);
assert.strictEqual(result.violations.length, 1);
assert.strictEqual(result.violations[0].reason, agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL);
assert.strictEqual(result.violations[0].value, 'sonnet');
});
// # 22 — boundary: empty / whitespace-only .toml pins nothing.
test('row 22: empty / whitespace-only .toml — no violation', () => {
writeAgentToml(agentsDir, 'msd-blank', ' \n\n\t\n');
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
});
// # 23 — hostile: an oversized (secret-shaped) model value must be truncated at
// 64 chars, matching bin/install.js's _warnCodexModelOverrideDropped truncation
// (`slice(0, 64) + '…'`) so an oversized/secret-shaped value cannot reach logs
// in full.
test('row 23: oversized model value — truncated at 64 chars, matching the installer cap', () => {
const oversized = `claude-${'x'.repeat(80)}`; // 87 chars, Anthropic-flavored (contains "claude")
writeAgentToml(
agentsDir,
'msd-oversized-agent',
`name = "msd-oversized-agent"\nmodel = "${oversized}"\ndeveloper_instructions = '''\nWork.\n'''\n`,
);
const result = agentInstallCheck.checkCodexModelPosture('codex', tmpDir);
assert.strictEqual(result.violations.length, 1);
const { value } = result.violations[0];
assert.ok(value.length <= 65, `expected value capped at 64 chars (+ ellipsis), got length ${value.length}`);
assert.notStrictEqual(value, oversized, 'the full oversized value must not reach the violation untruncated');
assert.strictEqual(value, `${oversized.slice(0, 64)}…`);
});
// # 25 — NEGATIVE PROOF. Catches: gating the runtime no-op AFTER already
// scanning the agents directory (e.g. deciding what to report only at the end),
// instead of short-circuiting before any file is read. A stray .toml that WOULD
// trip a violation if inspected must never be inspected for a non-codex runtime.
test('row 25 (negative proof): non-codex runtime with a stray violating .toml present — still NOT_CODEX, file never inspected', (t) => {
writeAgentToml(
agentsDir,
'msd-stray',
'name = "msd-stray"\nmodel = "sonnet"\ndeveloper_instructions = \'\'\'\nWork.\n\'\'\'\n',
);
const reads = [];
t.mock.method(fs, 'readFileSync', (...args) => {
reads.push(args[0]);
throw new Error('unreachable: readFileSync must not be called for a non-codex runtime');
});
const result = agentInstallCheck.checkCodexModelPosture('opencode', tmpDir);
assert.strictEqual(result.ok, true);
assert.deepStrictEqual(result.violations, []);
assert.strictEqual(result.reason, agentInstallCheck.POSTURE_REASON.NOT_CODEX);
assert.deepStrictEqual(reads, [], 'the stray violating file must never be read for a non-codex runtime');
});
});
// # 24 — enum lock: adding a POSTURE_REASON value is a deliberate three-way
// coordinated change (enum, emitting site, this test), not a silent drift. Locks
// the exact key set, matching the repo's established shape for reason enums
// (see verify-reapply-patches.cjs's REASON).
describe('POSTURE_REASON enum', () => {
test('row 24: Object.keys(POSTURE_REASON).sort() is locked', () => {
assert.deepStrictEqual(
Object.keys(agentInstallCheck.POSTURE_REASON).sort(),
[
'AGENTS_DIR_MISSING',
'ANTHROPIC_FLAVORED_MODEL',
'NOT_CODEX',
'ORPHANED_REASONING_EFFORT',
'UNREADABLE',
].sort(),
);
});
test('POSTURE_REASON values are the frozen snake_case wire form, not prose', () => {
assert.strictEqual(agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL, 'anthropic_flavored_model');
assert.strictEqual(agentInstallCheck.POSTURE_REASON.ORPHANED_REASONING_EFFORT, 'orphaned_reasoning_effort');
assert.strictEqual(agentInstallCheck.POSTURE_REASON.UNREADABLE, 'unreadable');
assert.strictEqual(agentInstallCheck.POSTURE_REASON.NOT_CODEX, 'not_codex');
assert.strictEqual(agentInstallCheck.POSTURE_REASON.AGENTS_DIR_MISSING, 'agents_dir_missing');
assert.ok(Object.isFrozen(agentInstallCheck.POSTURE_REASON));
});
});
// # 20 — keystone wiring: a library that works but is never called from the
// user-reachable command surface is the keystone-unwired failure the coverage
// gate exists to catch. Drives cmdValidateAgents directly (the same seam
// tests/verify.test.cjs already uses for cmdValidateHealth) and asserts through
// the command's structured JSON output — captured by monkeypatching
// node:fs.writeSync, the exact seam tests/io.test.cjs already establishes for
// io.cjs's output(), which writes via writeAllSync(1, ...) → fs.writeSync, NOT
// console.log — rather than calling checkCodexModelPosture a second time.
describe('cmdValidateAgents surfaces the Codex posture result (#3242 row 20)', () => {
let tmpDir;
let agentsDir;
beforeEach(() => {
tmpDir = createTempDir('msd-posture-wiring-');
agentsDir = path.join(tmpDir, 'agents');
process.env['MSD_AGENTS_DIR'] = agentsDir;
process.env['MSD_RUNTIME'] = 'codex';
});
afterEach(() => {
cleanup(tmpDir);
});
test('row 20: validate agents output carries the posture result for a violating install', () => {
const { cmdValidateAgents } = require(
path.join(__dirname, '..', 'msd-core', 'bin', 'lib', 'verify.cjs'),
);
writeAgentToml(
agentsDir,
EXPECTED_AGENTS[0],
`name = "${EXPECTED_AGENTS[0]}"\nmodel = "sonnet"\ndeveloper_instructions = '''\nWork.\n'''\n`,
);
const written = captureFdSync(1, () => cmdValidateAgents(tmpDir, false));
const parsed = JSON.parse(written);
assert.ok(
parsed.codex_posture,
`expected cmdValidateAgents output to carry a codex_posture key, got keys: ${Object.keys(parsed).join(', ')}`,
);
assert.strictEqual(parsed.codex_posture.ok, false);
assert.strictEqual(parsed.codex_posture.violations.length, 1);
assert.strictEqual(parsed.codex_posture.violations[0].agent, EXPECTED_AGENTS[0]);
assert.strictEqual(
parsed.codex_posture.violations[0].reason,
agentInstallCheck.POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL,
);
});
});
// ─── #2872 (ADR-2866 Phase 3): checkAgentsInstalled unchanged by the ──────
// manifest-reader de-duplication (50-test-matrix.md section 5, rows A1-A5)
//
// installer-migrations.cts's readInstallManifest now records manifestVersion
// /runtime/scope, but checkAgentsInstalled only ever consumed `.files`
// before AND after the substitution (agent-install-check.cts:191-194
// swapped an inlined try/readFileSync/JSON.parse for the shared reader,
// zero new branches) — see 40-design.md's "Rejected" #4 for why this
// function is not rewired onto the two-scope resolver instead. These rows
// prove the substitution changed nothing observable here.
describe('checkAgentsInstalled — unchanged by the manifest-reader substitution (#2872 A1-A5)', () => {
describe('A1-A4: v1/v2 manifest parity', () => {
let tmpDir;
let agentsDir;
beforeEach(() => {
tmpDir = createTempDir('msd-agent-check-schema-');
agentsDir = path.join(tmpDir, 'agents');
process.env['MSD_AGENTS_DIR'] = agentsDir;
fs.mkdirSync(agentsDir, { recursive: true });
for (const agent of EXPECTED_AGENTS) {
fs.writeFileSync(path.join(agentsDir, `${agent}.md`), `# ${agent}\n`);
}
});
afterEach(() => {
cleanup(tmpDir);
});
// A1 — a v1 manifest (no manifestVersion/runtime/scope key at all)
// beside the agents dir: result identical to pre-#2872 behavior.
test('A1: checkAgentsInstalled is unchanged for a v1 manifest', () => {
const manifestFiles = {};
for (const agent of EXPECTED_AGENTS) manifestFiles[`agents/${agent}.md`] = 'somehash';
fs.writeFileSync(
path.join(tmpDir, 'msd-file-manifest.json'),
JSON.stringify({
version: '1.49.0',
timestamp: '2026-05-10T00:00:00.000Z',
mode: 'full',
files: manifestFiles,
}),
);
const result = agentInstallCheck.checkAgentsInstalled();
assert.strictEqual(result.agents_installed, true);
assert.deepStrictEqual(result.missing_agents, []);
assert.deepStrictEqual(result.incomplete_agents, []);
});
// A2 — a v2 manifest (carrying manifestVersion/runtime/scope) produces
// the exact same result: the new fields change nothing here.
test('A2: checkAgentsInstalled is unchanged for a v2 manifest', () => {
const manifestFiles = {};
for (const agent of EXPECTED_AGENTS) manifestFiles[`agents/${agent}.md`] = 'somehash';
fs.writeFileSync(
path.join(tmpDir, 'msd-file-manifest.json'),
JSON.stringify({
manifestVersion: 2,
version: '1.60.0',
timestamp: '2026-08-01T00:00:00.000Z',
mode: 'full',
runtime: 'claude',
scope: 'global',
files: manifestFiles,
}),
);
const result = agentInstallCheck.checkAgentsInstalled();
assert.strictEqual(result.agents_installed, true);
assert.deepStrictEqual(result.missing_agents, []);
assert.deepStrictEqual(result.incomplete_agents, []);
});
// A3 — no manifest at all (readInstallManifest's absent-file branch):
// the completeness check is skipped, no throw — today's behavior.
// Presence (the .md files above) still makes the install look complete.
test('A3: an unreadable/absent manifest still skips the completeness check', () => {
let result;
assert.doesNotThrow(() => {
result = agentInstallCheck.checkAgentsInstalled();
});
assert.deepStrictEqual(result.incomplete_agents, []);
assert.strictEqual(result.agents_installed, true);
});
// A4 — manifest present, one agent's .toml tracked but absent on disk:
// still reported as incomplete, same as before the substitution.
test('A4: still reports an incomplete agent', () => {
const targetAgent = EXPECTED_AGENTS[0];
const manifestFiles = {};
for (const agent of EXPECTED_AGENTS) manifestFiles[`agents/${agent}.md`] = 'somehash';
manifestFiles[`agents/${targetAgent}.toml`] = 'somehash';
fs.writeFileSync(
path.join(tmpDir, 'msd-file-manifest.json'),
JSON.stringify({
manifestVersion: 2,
version: '1.60.0',
timestamp: '2026-08-01T00:00:00.000Z',
mode: 'full',
runtime: 'claude',
scope: 'global',
files: manifestFiles,
}),
);
const result = agentInstallCheck.checkAgentsInstalled();
assert.ok(
result.incomplete_agents.includes(targetAgent),
`expected ${targetAgent} in incomplete_agents, got: ${JSON.stringify(result.incomplete_agents)}`,
);
assert.strictEqual(result.agents_installed, false);
});
});
// A5 — getAgentsDir's local-install probe is deliberately NOT rewired to
// readInstallManifest: it stays an lstat-based (symlink-unaware) presence
// check (agent-install-check.cts:123, "Not touched" in 40-design.md's
// blast-radius table), so a manifest path that is itself a SYMLINK is
// still ignored, exactly as before the substitution.
test('A5: getAgentsDir still ignores a symlinked manifest', (t) => {
const projectRoot = createTempDir('msd-local-codex-schema-');
const globalHome = createTempDir('msd-global-codex-schema-');
const localConfigDir = path.join(projectRoot, '.codex');
const localAgentsDir = path.join(localConfigDir, 'agents');
const realManifestTarget = path.join(projectRoot, 'real-manifest.json');
t.after(() => cleanup(projectRoot));
t.after(() => cleanup(globalHome));
fs.mkdirSync(localAgentsDir, { recursive: true });
for (const agent of EXPECTED_AGENTS) {
fs.writeFileSync(path.join(localAgentsDir, `${agent}.toml`), `name = "${agent}"\n`);
}
fs.writeFileSync(realManifestTarget, JSON.stringify({ files: {} }));
const manifestPath = path.join(localConfigDir, 'msd-file-manifest.json');
createCompleteAgents(path.join(globalHome, 'agents'));
process.env['CODEX_HOME'] = globalHome;
try {
fs.symlinkSync(realManifestTarget, manifestPath, 'file');
} catch (error) {
if (error && ['EPERM', 'EACCES', 'ENOTSUP'].includes(error.code)) {
t.skip('symlink creation is not available on this platform');
return;
}
throw error;
}
// getAgentsDir's own probe (`fs.lstatSync(manifestPath).isFile()`) sees
// the manifest path as a symlink, not a regular file — isFile() is false
// for the link itself even though its target is a regular file — so the
// local install is NOT selected; the global fallback wins instead.
assert.strictEqual(agentInstallCheck.getAgentsDir('codex', projectRoot), path.join(globalHome, 'agents'));
assert.notStrictEqual(agentInstallCheck.getAgentsDir('codex', projectRoot), localAgentsDir);
});
});
// ─── 4. Sandbox-mode drift (#3897 rung 3, ADR-3473 §8.3 criterion 3) ──────────
//
// Spec: .msd/phase/feat-3897-adr3473-83-rungs/{40-design,50-test-matrix}.md, S8/T28.
//
// `checkAgentsInstalled` above (and `checkCodexModelPosture`, the established
// sibling pattern for a codex-only posture check — #3242, "A new sibling
// export... presence is checkAgentsInstalled's job; this function's job starts
// only once the runtime is confirmed codex and only inspects posture") checks
// FILE PRESENCE and MANIFEST COMPLETENESS only. Neither inspects whether an
// installed .toml's `sandbox_mode` line agrees with what that role's tool
// contract says it should be — a TOML that disagrees passes `validate agents`
// today. `checkCodexSandboxPosture` is this test's anticipated name for the new
// sibling check (mirroring `checkCodexModelPosture`'s exact shape); the exact
// export name is the implementer's call, but the export MUST exist for this
// row's acceptance criterion (§8.3 criterion 3) to be met.
//
// RED today: the export does not exist at all.
describe('checkCodexSandboxPosture (#3897 rung 3 — not yet implemented, RED until it lands)', () => {
test('T28 validateAgentsFailsOnSandboxDrift_3897: a TOML whose sandbox_mode disagrees with the derived expectation must fail, naming role/expected/found', (t) => {
const globalHome = createTempDir('msd-sandbox-drift-');
t.after(() => cleanup(globalHome));
const agentsDir = path.join(globalHome, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
// msd-executor declares tools: Read, Write, Edit (agents/msd-executor.md) —
// it MUST derive workspace-write. Install a drifted TOML claiming
// read-only instead, matching the shape generateCodexAgentToml emits.
fs.writeFileSync(
path.join(agentsDir, 'msd-executor.toml'),
'name = "msd-executor"\ndescription = "Executes plans"\nsandbox_mode = "read-only"\n' +
"developer_instructions = '''\nExecute.\n'''\n",
);
for (const agent of EXPECTED_AGENTS) {
if (agent === 'msd-executor') continue;
fs.writeFileSync(path.join(agentsDir, `${agent}.toml`), `name = "${agent}"\n`);
}
process.env['CODEX_HOME'] = globalHome;
assert.equal(
typeof agentInstallCheck.checkCodexSandboxPosture,
'function',
'agent-install-check.cjs must export a sandbox-posture check (#3897 rung 3, ADR-3473 §8.3 criterion 3) — it does not exist yet, so a drifted sandbox_mode currently passes validate agents silently',
);
const result = agentInstallCheck.checkCodexSandboxPosture('codex');
assert.equal(
result.ok,
false,
'a TOML whose sandbox_mode disagrees with the expected derived value must fail the check',
);
const violation = result.violations.find((v) => v.agent === 'msd-executor');
assert.ok(violation, 'the violation must name the drifted agent');
assert.equal(violation.expected, 'workspace-write', 'the violation must name the EXPECTED sandbox_mode');
assert.equal(violation.found, 'read-only', 'the violation must name the FOUND (installed) sandbox_mode');
});
// #3897 rung 4 (isolated correctness review, MINOR finding 2): `found` used
// to be read via a naive whole-file regex, unlike the block-aware scanner
// the sibling checkCodexModelPosture uses. Reviewer's fixture: no header
// sandbox_mode pin at all, but a `sandbox_mode = "..."`-shaped line INSIDE
// the developer_instructions block (prose a role's own prompt might
// legitimately discuss). The naive regex misread that prose as a live
// value and manufactured a FALSE violation.
test('a sandbox_mode-shaped line INSIDE developer_instructions is never read as a live value (no false violation)', (t) => {
const globalHome = createTempDir('msd-sandbox-block-aware-');
t.after(() => cleanup(globalHome));
const agentsDir = path.join(globalHome, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
// msd-nyquist-auditor's real canonical tools: declares Write/Edit, so the
// derived expectation is workspace-write. Install a TOML that correctly
// has NO header sandbox_mode pin, but whose developer_instructions block
// contains a line that is shaped exactly like a live sandbox_mode pin.
fs.writeFileSync(
path.join(agentsDir, 'msd-nyquist-auditor.toml'),
'name = "msd-nyquist-auditor"\ndescription = "Fills Nyquist validation gaps"\n' +
"developer_instructions = '''\n" +
'When configuring the sandbox, use:\n' +
'sandbox_mode = "workspace-write"\n' +
"'''\n",
);
for (const agent of EXPECTED_AGENTS) {
if (agent === 'msd-nyquist-auditor') continue;
fs.writeFileSync(path.join(agentsDir, `${agent}.toml`), `name = "${agent}"\n`);
}
process.env['CODEX_HOME'] = globalHome;
const result = agentInstallCheck.checkCodexSandboxPosture('codex');
const violation = result.violations.find((v) => v.agent === 'msd-nyquist-auditor');
assert.equal(
violation,
undefined,
'a sandbox_mode-shaped line inside developer_instructions must never be read as the installed value — it must not manufacture a violation for an agent with no real header pin',
);
});
// #3897 rung 4 (isolated correctness review, MINOR finding 3):
// truncatePostureValue exists at agent-install-check.cts precisely for
// this, and the model-posture sibling applies it — the sandbox check did
// not, so an oversized (or secret-shaped) sandbox_mode value could reach
// `validate agents --raw` output at full length.
test('an oversized sandbox_mode value is truncated in the emitted violation (CLI JSON)', (t) => {
const globalHome = createTempDir('msd-sandbox-truncate-');
t.after(() => cleanup(globalHome));
const agentsDir = path.join(globalHome, 'agents');
fs.mkdirSync(agentsDir, { recursive: true });
const oversized = 'x'.repeat(300);
fs.writeFileSync(
path.join(agentsDir, 'msd-executor.toml'),
`name = "msd-executor"\ndescription = "Executes plans"\nsandbox_mode = "${oversized}"\n` +
"developer_instructions = '''\nExecute.\n'''\n",
);
for (const agent of EXPECTED_AGENTS) {
if (agent === 'msd-executor') continue;
fs.writeFileSync(path.join(agentsDir, `${agent}.toml`), `name = "${agent}"\n`);
}
process.env['CODEX_HOME'] = globalHome;
const result = agentInstallCheck.checkCodexSandboxPosture('codex');
const violation = result.violations.find((v) => v.agent === 'msd-executor');
assert.ok(violation, 'the violation must name the drifted agent');
assert.ok(
violation.found.length <= 65,
`violation.found must be truncated (<=64 chars + ellipsis), got length ${violation.found.length}`,
);
assert.ok(violation.found.endsWith('…'), 'a truncated value must end with the ellipsis marker');
// The full-length value must never reach the emitted JSON at all.
const serialized = JSON.stringify(result);
assert.ok(!serialized.includes(oversized), 'the untruncated 300-char value must never appear in the emitted CLI JSON');
});
});
// T29 (validate agents still fails when a file is missing, S9) is intentionally
// NOT duplicated here — it is already covered by the existing "missing dir"
// coverage documented in this file's own header comment ("missing dir →
// agents_installed:false, missing_agents = all expected") and exercised above.
// It must stay green; nothing in this rung touches that path.