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.
This commit is contained in:
@@ -15,16 +15,16 @@ const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||
const fc = require('./helpers/fast-check-setup.cjs');
|
||||
const { ensureScriptsOut } = require('./helpers/exit-code-artifact-flags.cjs');
|
||||
|
||||
// Paths to the compiled product seam (src/cli-exit.cts → gsd-core/bin/lib/cli-exit.cjs)
|
||||
// Paths to the compiled product seam (src/cli-exit.cts → msd-core/bin/lib/cli-exit.cjs)
|
||||
// used for json-error mode regression tests which require io.cjs integration.
|
||||
const BUILT_CLI_EXIT_PATH = path.resolve(__dirname, '../gsd-core/bin/lib/cli-exit.cjs');
|
||||
const IO_PATH = path.resolve(__dirname, '../gsd-core/bin/lib/io.cjs');
|
||||
const BUILT_CLI_EXIT_PATH = path.resolve(__dirname, '../msd-core/bin/lib/cli-exit.cjs');
|
||||
const IO_PATH = path.resolve(__dirname, '../msd-core/bin/lib/io.cjs');
|
||||
const SCRIPTS_CLI_EXIT_PATH = path.resolve(__dirname, '../scripts/lib/cli-exit.cjs');
|
||||
const EXIT_CODE_REGISTRY_PATH = path.resolve(__dirname, '../gsd-core/bin/lib/exit-code-registry.cjs');
|
||||
const EXIT_CODE_REGISTRY_PATH = path.resolve(__dirname, '../msd-core/bin/lib/exit-code-registry.cjs');
|
||||
|
||||
// #3911 (ADR-3889 Phase 7): the THIRD emitted copy, for hooks/ consumers that
|
||||
// must terminate through terminateNow without depending on any build
|
||||
// artifact (gsd-core/bin/lib is gitignored tsc output, absent on a raw
|
||||
// artifact (msd-core/bin/lib is gitignored tsc output, absent on a raw
|
||||
// plugin-marketplace or git-clone install).
|
||||
const HOOKS_CLI_EXIT_PATH = path.resolve(__dirname, '../hooks/lib/cli-exit.js');
|
||||
const HOOKS_EXIT_CODE_REGISTRY_PATH = path.resolve(__dirname, '../hooks/lib/exit-code-registry.js');
|
||||
@@ -231,7 +231,7 @@ describe('runMain', () => {
|
||||
* Fix: src/cli-exit.cts non-ExitError catch branch now checks getJsonErrorMode()
|
||||
* and emits the same structured envelope as error() when active.
|
||||
*
|
||||
* Tests run against the compiled product seam (gsd-core/bin/lib/cli-exit.cjs)
|
||||
* Tests run against the compiled product seam (msd-core/bin/lib/cli-exit.cjs)
|
||||
* via subprocess so that io.cjs module-level state is isolated per spawn.
|
||||
*/
|
||||
describe('regressions', () => {
|
||||
@@ -317,7 +317,7 @@ describe('regressions', () => {
|
||||
const exitResult = spawnJsonErrorRun({
|
||||
jsonMode: true,
|
||||
errorType: 'ExitError',
|
||||
message: 'Usage: gsd-tools <command> [args]',
|
||||
message: 'Usage: msd-tools <command> [args]',
|
||||
});
|
||||
assert.strictEqual(exitResult.status, 1, 'ExitError exits with its code');
|
||||
const exitStderr = exitResult.stderr.trim();
|
||||
@@ -443,7 +443,7 @@ describe('regressions', () => {
|
||||
test('scripts copy keeps ExitError plain-text under json mode', () => {
|
||||
const result = spawnScriptsRun({
|
||||
jsonMode: true,
|
||||
throwExpr: `new ExitError(1, 'Usage: gsd-tools <command> [args]')`,
|
||||
throwExpr: `new ExitError(1, 'Usage: msd-tools <command> [args]')`,
|
||||
});
|
||||
assert.strictEqual(result.status, 1, 'ExitError exits with its own code');
|
||||
const trimmed = result.stderr.trim();
|
||||
@@ -628,7 +628,7 @@ describe('regressions', () => {
|
||||
assert.strictEqual(seen.frozen, true);
|
||||
// #3884 (ADR-3473 §8.4) legitimately added two new codes —
|
||||
// PICK_FIELD_ABSENT and PICK_OUTPUT_NOT_JSON — for the `--pick`
|
||||
// absence contract (see .gsd/phase/feat-3884-failure-is-a-value/40-design.md
|
||||
// absence contract (see .msd/phase/feat-3884-failure-is-a-value/40-design.md
|
||||
// rows B6/B11). 23 -> 25 is an intentional, documented growth of the
|
||||
// enum, not drift; bump the golden count rather than treat this as a
|
||||
// Hyrum violation.
|
||||
@@ -640,16 +640,16 @@ describe('regressions', () => {
|
||||
});
|
||||
|
||||
// ── Matrix rows 22-23: the unbuilt-clone constraint ──────────────────────
|
||||
test('the scripts copy loads with no gsd-core tree in scope at all', (t) => {
|
||||
test('the scripts copy loads with no msd-core tree in scope at all', (t) => {
|
||||
// The generated file is COMMITTED and 64+ scripts/ consumers require it,
|
||||
// including scripts/check-env.cjs which runs before any build. It must
|
||||
// therefore not reach into gsd-core/bin/lib/, which is gitignored tsc
|
||||
// therefore not reach into msd-core/bin/lib/, which is gitignored tsc
|
||||
// output absent on a fresh clone.
|
||||
//
|
||||
// Proven by copying the file into an isolated temp directory that has no
|
||||
// gsd-core sibling and no node_modules — a require of the built tree is
|
||||
// msd-core sibling and no node_modules — a require of the built tree is
|
||||
// MODULE_NOT_FOUND there. Deliberately NOT done by renaming the real
|
||||
// gsd-core/bin/lib: test files run in parallel, so mutating a shared
|
||||
// msd-core/bin/lib: test files run in parallel, so mutating a shared
|
||||
// production directory would break every sibling suite mid-run.
|
||||
//
|
||||
// This is the sole guard of the "depends on node: builtins only"
|
||||
@@ -664,7 +664,7 @@ describe('regressions', () => {
|
||||
// registry require; that failure mode is exercised on its own by the
|
||||
// dedicated #3906 standalone-load test below, which is the one that
|
||||
// asserts the CORRECT two-file set loads clean.
|
||||
const dir = createTempDir('gsd-3904-standalone-');
|
||||
const dir = createTempDir('msd-3904-standalone-');
|
||||
t.after(() => cleanup(dir));
|
||||
const copied = path.join(dir, 'cli-exit.cjs');
|
||||
fs.copyFileSync(SCRIPTS_CLI_EXIT_PATH, copied);
|
||||
@@ -686,10 +686,10 @@ describe('regressions', () => {
|
||||
});
|
||||
|
||||
test('the build sentinel is still emitted', () => {
|
||||
// gsd-core/bin/ensure-runtime-build.cjs keys isBuilt() on this exact filename.
|
||||
// msd-core/bin/ensure-runtime-build.cjs keys isBuilt() on this exact filename.
|
||||
assert.ok(
|
||||
fs.statSync(BUILT_CLI_EXIT_PATH).isFile(),
|
||||
'gsd-core/bin/lib/cli-exit.cjs must remain tsc output — it is the build sentinel',
|
||||
'msd-core/bin/lib/cli-exit.cjs must remain tsc output — it is the build sentinel',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -816,26 +816,26 @@ describe('#3906: resolveContractVersion', () => {
|
||||
assert.equal(resolveContractVersion({ argv: ['node', 'x', '--exit-contract=v2'], env: {} }), 'v2');
|
||||
});
|
||||
|
||||
test('GSD_EXIT_CONTRACT=v2 env -> v2', () => {
|
||||
assert.equal(resolveContractVersion({ argv: ['node', 'x'], env: { GSD_EXIT_CONTRACT: 'v2' } }), 'v2');
|
||||
test('MSD_EXIT_CONTRACT=v2 env -> v2', () => {
|
||||
assert.equal(resolveContractVersion({ argv: ['node', 'x'], env: { MSD_EXIT_CONTRACT: 'v2' } }), 'v2');
|
||||
});
|
||||
|
||||
test('flag v1 beats env v2', () => {
|
||||
assert.equal(
|
||||
resolveContractVersion({ argv: ['node', 'x', '--exit-contract=v1'], env: { GSD_EXIT_CONTRACT: 'v2' } }),
|
||||
resolveContractVersion({ argv: ['node', 'x', '--exit-contract=v1'], env: { MSD_EXIT_CONTRACT: 'v2' } }),
|
||||
'v1',
|
||||
);
|
||||
});
|
||||
|
||||
test('flag v2 beats env v1 (both directions)', () => {
|
||||
assert.equal(
|
||||
resolveContractVersion({ argv: ['node', 'x', '--exit-contract=v2'], env: { GSD_EXIT_CONTRACT: 'v1' } }),
|
||||
resolveContractVersion({ argv: ['node', 'x', '--exit-contract=v2'], env: { MSD_EXIT_CONTRACT: 'v1' } }),
|
||||
'v2',
|
||||
);
|
||||
});
|
||||
|
||||
test('an empty GSD_EXIT_CONTRACT reads as unset, not as an explicit selection', () => {
|
||||
assert.equal(resolveContractVersion({ argv: ['node', 'x'], env: { GSD_EXIT_CONTRACT: '' } }), 'v1');
|
||||
test('an empty MSD_EXIT_CONTRACT reads as unset, not as an explicit selection', () => {
|
||||
assert.equal(resolveContractVersion({ argv: ['node', 'x'], env: { MSD_EXIT_CONTRACT: '' } }), 'v1');
|
||||
});
|
||||
|
||||
for (const bad of ['v3', 'garbage', '--exit-contract=']) {
|
||||
@@ -845,13 +845,13 @@ describe('#3906: resolveContractVersion', () => {
|
||||
});
|
||||
}
|
||||
|
||||
test('GSD_EXIT_CONTRACT=v3 (env garbage) is rejected the same way', () => {
|
||||
assert.throws(() => resolveContractVersion({ argv: ['node', 'x'], env: { GSD_EXIT_CONTRACT: 'v3' } }));
|
||||
test('MSD_EXIT_CONTRACT=v3 (env garbage) is rejected the same way', () => {
|
||||
assert.throws(() => resolveContractVersion({ argv: ['node', 'x'], env: { MSD_EXIT_CONTRACT: 'v3' } }));
|
||||
});
|
||||
|
||||
test('casing is decided: uppercase V2 is rejected, not silently accepted', () => {
|
||||
assert.throws(() => resolveContractVersion({ argv: ['node', 'x', '--exit-contract=V2'], env: {} }));
|
||||
assert.throws(() => resolveContractVersion({ argv: ['node', 'x'], env: { GSD_EXIT_CONTRACT: 'V2' } }));
|
||||
assert.throws(() => resolveContractVersion({ argv: ['node', 'x'], env: { MSD_EXIT_CONTRACT: 'V2' } }));
|
||||
});
|
||||
|
||||
test('resolveContractVersion persists into the shared cell read by getContractVersion', () => {
|
||||
@@ -864,13 +864,13 @@ describe('#3906: resolveContractVersion', () => {
|
||||
|
||||
describe('#3906: parity — runMain and terminateNow project identically (mandatory per ADR-3889 §3)', () => {
|
||||
// #3906 follow-up: this MUST drive the version through the REAL ambient
|
||||
// mechanism (GSD_EXIT_CONTRACT in the child's env, never touched by the
|
||||
// mechanism (MSD_EXIT_CONTRACT in the child's env, never touched by the
|
||||
// script body itself), not by calling resolveContractVersion() explicitly
|
||||
// inside the child. A test that pre-seeds the shared cell before invoking
|
||||
// either terminator can pass even if getContractVersion() never actually
|
||||
// wires the ambient process in at all — which is exactly the defect this
|
||||
// matrix exists to catch (both terminators reading the SAME un-wired
|
||||
// default 'v1' would still "agree", 16/16, while GSD_EXIT_CONTRACT was
|
||||
// default 'v1' would still "agree", 16/16, while MSD_EXIT_CONTRACT was
|
||||
// silently ignored). Neither script below calls resolveContractVersion or
|
||||
// passes --exit-contract; the version reaches the process ONLY via env.
|
||||
function runMainExit(outcome, version) {
|
||||
@@ -881,7 +881,7 @@ describe('#3906: parity — runMain and terminateNow project identically (mandat
|
||||
].join('\n');
|
||||
return toLegacyResult(runNode(['-e', script], {
|
||||
timeoutMs: PROBE_TIMEOUT_MS,
|
||||
env: { ...process.env, GSD_EXIT_CONTRACT: version },
|
||||
env: { ...process.env, MSD_EXIT_CONTRACT: version },
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -892,7 +892,7 @@ describe('#3906: parity — runMain and terminateNow project identically (mandat
|
||||
].join('\n');
|
||||
return toLegacyResult(runNode(['-e', script], {
|
||||
timeoutMs: PROBE_TIMEOUT_MS,
|
||||
env: { ...process.env, GSD_EXIT_CONTRACT: version },
|
||||
env: { ...process.env, MSD_EXIT_CONTRACT: version },
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -951,13 +951,13 @@ describe('#3906: parity — runMain and terminateNow project identically (mandat
|
||||
}
|
||||
|
||||
// #3906 follow-up: a matrix where every row merely agrees between the two
|
||||
// terminators is satisfiable by a build that ignores GSD_EXIT_CONTRACT
|
||||
// terminators is satisfiable by a build that ignores MSD_EXIT_CONTRACT
|
||||
// entirely (both terminators would then agree on the un-wired v1 default
|
||||
// for every row, 16/16, and the matrix above would still read green). This
|
||||
// block is the non-vacuousness proof the brief demands: it asserts the ONE
|
||||
// row that MUST be version-sensitive actually differs by version, and
|
||||
// spot-checks a control row that must NOT.
|
||||
test('non-vacuousness: DEGRADED is version-sensitive via ambient GSD_EXIT_CONTRACT', () => {
|
||||
test('non-vacuousness: DEGRADED is version-sensitive via ambient MSD_EXIT_CONTRACT', () => {
|
||||
const runMainV1 = runMainExit('DEGRADED', 'v1');
|
||||
const runMainV2 = runMainExit('DEGRADED', 'v2');
|
||||
const terminateNowV1 = terminateNowExit('DEGRADED', 'v1');
|
||||
@@ -979,7 +979,7 @@ describe('#3906: parity — runMain and terminateNow project identically (mandat
|
||||
);
|
||||
});
|
||||
|
||||
test('control: a non-DEGRADED outcome (FAIL) stays version-invariant via ambient GSD_EXIT_CONTRACT', () => {
|
||||
test('control: a non-DEGRADED outcome (FAIL) stays version-invariant via ambient MSD_EXIT_CONTRACT', () => {
|
||||
const runMainV1 = runMainExit('FAIL', 'v1');
|
||||
const runMainV2 = runMainExit('FAIL', 'v2');
|
||||
assert.equal(runMainV1.status, 1);
|
||||
@@ -989,28 +989,28 @@ describe('#3906: parity — runMain and terminateNow project identically (mandat
|
||||
|
||||
// ─── #3906 acceptance criterion, made executable ────────────────────────────
|
||||
//
|
||||
// "user can run the gsd-tools command with --exit-contract=v2 (or
|
||||
// GSD_EXIT_CONTRACT=v2 in the environment) and observe the v2 registry
|
||||
// "user can run the msd-tools command with --exit-contract=v2 (or
|
||||
// MSD_EXIT_CONTRACT=v2 in the environment) and observe the v2 registry
|
||||
// projection on the exit status; absent both, the same command yields the v1
|
||||
// integers." Nothing in the module wired the ambient process to the
|
||||
// terminators before this: getContractVersion() only ever read the shared
|
||||
// cell, and nothing populated that cell absent an explicit
|
||||
// resolveContractVersion()/setContractVersion() call — so a bare
|
||||
// `GSD_EXIT_CONTRACT=v2 node -e "...terminateNow('DEGRADED')..."` exited 0,
|
||||
// `MSD_EXIT_CONTRACT=v2 node -e "...terminateNow('DEGRADED')..."` exited 0,
|
||||
// not 80. These tests spawn a fresh child per case (a fresh process has an
|
||||
// empty cell) and touch NOTHING but the documented public surface.
|
||||
describe('#3906: ambient GSD_EXIT_CONTRACT/--exit-contract wiring (acceptance criterion)', () => {
|
||||
test('terminateNow: GSD_EXIT_CONTRACT=v2 -> DEGRADED exits 80', () => {
|
||||
describe('#3906: ambient MSD_EXIT_CONTRACT/--exit-contract wiring (acceptance criterion)', () => {
|
||||
test('terminateNow: MSD_EXIT_CONTRACT=v2 -> DEGRADED exits 80', () => {
|
||||
const r = toLegacyResult(runNode(['-e', [
|
||||
`const c = require(${JSON.stringify(BUILT_CLI_EXIT_PATH)});`,
|
||||
`c.terminateNow('DEGRADED', {});`,
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_EXIT_CONTRACT: 'v2' } }));
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, MSD_EXIT_CONTRACT: 'v2' } }));
|
||||
assert.equal(r.status, 80, `stderr: ${r.stderr}`);
|
||||
});
|
||||
|
||||
test('terminateNow: no env, no flag -> DEGRADED exits 0 (v1 default unchanged)', () => {
|
||||
const env = { ...process.env };
|
||||
delete env.GSD_EXIT_CONTRACT;
|
||||
delete env.MSD_EXIT_CONTRACT;
|
||||
const r = toLegacyResult(runNode(['-e', [
|
||||
`const c = require(${JSON.stringify(BUILT_CLI_EXIT_PATH)});`,
|
||||
`c.terminateNow('DEGRADED', {});`,
|
||||
@@ -1018,18 +1018,18 @@ describe('#3906: ambient GSD_EXIT_CONTRACT/--exit-contract wiring (acceptance cr
|
||||
assert.equal(r.status, 0, `stderr: ${r.stderr}`);
|
||||
});
|
||||
|
||||
test('runMain: GSD_EXIT_CONTRACT=v2 -> DEGRADED exits 80', () => {
|
||||
test('runMain: MSD_EXIT_CONTRACT=v2 -> DEGRADED exits 80', () => {
|
||||
const r = toLegacyResult(runNode(['-e', [
|
||||
`const c = require(${JSON.stringify(BUILT_CLI_EXIT_PATH)});`,
|
||||
`c.runMain(() => 'DEGRADED');`,
|
||||
`setImmediate(() => {});`,
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_EXIT_CONTRACT: 'v2' } }));
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, MSD_EXIT_CONTRACT: 'v2' } }));
|
||||
assert.equal(r.status, 80, `stderr: ${r.stderr}`);
|
||||
});
|
||||
|
||||
test('runMain: no env, no flag -> DEGRADED exits 0 (v1 default unchanged)', () => {
|
||||
const env = { ...process.env };
|
||||
delete env.GSD_EXIT_CONTRACT;
|
||||
delete env.MSD_EXIT_CONTRACT;
|
||||
const r = toLegacyResult(runNode(['-e', [
|
||||
`const c = require(${JSON.stringify(BUILT_CLI_EXIT_PATH)});`,
|
||||
`c.runMain(() => 'DEGRADED');`,
|
||||
@@ -1044,7 +1044,7 @@ describe('#3906: ambient GSD_EXIT_CONTRACT/--exit-contract wiring (acceptance cr
|
||||
// real CLI entrypoint is invoked as `node <file> --exit-contract=v2`, so
|
||||
// the flag path is proven with a real temp script file, not `-e`.
|
||||
test('flag path: node <file> --exit-contract=v2 -> terminateNow DEGRADED exits 80', (t) => {
|
||||
const dir = createTempDir('gsd-3906-flag-');
|
||||
const dir = createTempDir('msd-3906-flag-');
|
||||
t.after(() => cleanup(dir));
|
||||
const scriptPath = path.join(dir, 'terminate-degraded.cjs');
|
||||
fs.writeFileSync(scriptPath, [
|
||||
@@ -1053,13 +1053,13 @@ describe('#3906: ambient GSD_EXIT_CONTRACT/--exit-contract wiring (acceptance cr
|
||||
'',
|
||||
].join('\n'));
|
||||
const env = { ...process.env };
|
||||
delete env.GSD_EXIT_CONTRACT;
|
||||
delete env.MSD_EXIT_CONTRACT;
|
||||
const r = toLegacyResult(runNode([scriptPath, '--exit-contract=v2'], { timeoutMs: PROBE_TIMEOUT_MS, env }));
|
||||
assert.equal(r.status, 80, `stderr: ${r.stderr}`);
|
||||
});
|
||||
|
||||
test('flag path: node <file> with no flag -> terminateNow DEGRADED exits 0', (t) => {
|
||||
const dir = createTempDir('gsd-3906-flag-');
|
||||
const dir = createTempDir('msd-3906-flag-');
|
||||
t.after(() => cleanup(dir));
|
||||
const scriptPath = path.join(dir, 'terminate-degraded.cjs');
|
||||
fs.writeFileSync(scriptPath, [
|
||||
@@ -1068,7 +1068,7 @@ describe('#3906: ambient GSD_EXIT_CONTRACT/--exit-contract wiring (acceptance cr
|
||||
'',
|
||||
].join('\n'));
|
||||
const env = { ...process.env };
|
||||
delete env.GSD_EXIT_CONTRACT;
|
||||
delete env.MSD_EXIT_CONTRACT;
|
||||
const r = toLegacyResult(runNode([scriptPath], { timeoutMs: PROBE_TIMEOUT_MS, env }));
|
||||
assert.equal(r.status, 0, `stderr: ${r.stderr}`);
|
||||
});
|
||||
@@ -1077,25 +1077,25 @@ describe('#3906: ambient GSD_EXIT_CONTRACT/--exit-contract wiring (acceptance cr
|
||||
// is the module's own sanctioned way to write an explicit version into the
|
||||
// shared cell, and it is what this precedence rests on: getContractVersion's
|
||||
// lazy ambient-resolution path must be skipped entirely once the cell is
|
||||
// already populated, so a later read of GSD_EXIT_CONTRACT must NOT unseat it.
|
||||
test('an explicit resolveContractVersion() call beats a later ambient GSD_EXIT_CONTRACT read (precedence)', () => {
|
||||
// already populated, so a later read of MSD_EXIT_CONTRACT must NOT unseat it.
|
||||
test('an explicit resolveContractVersion() call beats a later ambient MSD_EXIT_CONTRACT read (precedence)', () => {
|
||||
const r = toLegacyResult(runNode(['-e', [
|
||||
`const c = require(${JSON.stringify(BUILT_CLI_EXIT_PATH)});`,
|
||||
`c.resolveContractVersion({ argv: ['node','x','--exit-contract=v1'], env: {} });`,
|
||||
`c.terminateNow('DEGRADED', {});`,
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_EXIT_CONTRACT: 'v2' } }));
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, MSD_EXIT_CONTRACT: 'v2' } }));
|
||||
assert.equal(
|
||||
r.status, 0,
|
||||
`an explicit resolveContractVersion('v1') call must beat a later ambient GSD_EXIT_CONTRACT=v2 read; stderr: ${r.stderr}`,
|
||||
`an explicit resolveContractVersion('v1') call must beat a later ambient MSD_EXIT_CONTRACT=v2 read; stderr: ${r.stderr}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('GSD_EXIT_CONTRACT=v3 (invalid) still THROWS on the lazy ambient path, never silently v1 (runMain: generic-throw arm, exit 1)', () => {
|
||||
test('MSD_EXIT_CONTRACT=v3 (invalid) still THROWS on the lazy ambient path, never silently v1 (runMain: generic-throw arm, exit 1)', () => {
|
||||
const r = toLegacyResult(runNode(['-e', [
|
||||
`const c = require(${JSON.stringify(BUILT_CLI_EXIT_PATH)});`,
|
||||
`c.runMain(() => 'DEGRADED');`,
|
||||
`setImmediate(() => {});`,
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_EXIT_CONTRACT: 'v3' } }));
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, MSD_EXIT_CONTRACT: 'v3' } }));
|
||||
assert.equal(r.status, 1, `expected the generic-throw exit code 1; stderr: ${r.stderr}`);
|
||||
assert.ok(
|
||||
/unrecognized exit-contract version/.test(r.stderr),
|
||||
@@ -1108,11 +1108,11 @@ describe('#3906: ambient GSD_EXIT_CONTRACT/--exit-contract wiring (acceptance cr
|
||||
// throw at all (an enforcement-hook caller's own outer catch could turn
|
||||
// that into a fail-open exit 0). It must terminate deterministically with
|
||||
// INTERNAL (70) and diagnose on stderr instead.
|
||||
test('terminateNow: GSD_EXIT_CONTRACT=v3 (invalid) exits 70 (INTERNAL), not silently v1 and not a propagated throw', () => {
|
||||
test('terminateNow: MSD_EXIT_CONTRACT=v3 (invalid) exits 70 (INTERNAL), not silently v1 and not a propagated throw', () => {
|
||||
const r = toLegacyResult(runNode(['-e', [
|
||||
`const c = require(${JSON.stringify(BUILT_CLI_EXIT_PATH)});`,
|
||||
`c.terminateNow('DEGRADED', {});`,
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_EXIT_CONTRACT: 'v3' } }));
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, MSD_EXIT_CONTRACT: 'v3' } }));
|
||||
assert.equal(r.status, 70, `expected INTERNAL (70); stderr: ${r.stderr}`);
|
||||
assert.ok(
|
||||
/unrecognized exit-contract version/.test(r.stderr),
|
||||
@@ -1124,21 +1124,21 @@ describe('#3906: ambient GSD_EXIT_CONTRACT/--exit-contract wiring (acceptance cr
|
||||
// ─── #3912 regression: `--exit-contract=<v>` in LEADING argv position ──────
|
||||
//
|
||||
// resolveContractVersion() scans argv non-destructively (findExitContractFlag,
|
||||
// gsd-core/bin/lib/cli-exit.cjs). Nothing previously spliced the flag out of
|
||||
// the `gsd-tools` CLI dispatcher's own argv before it fell through to command
|
||||
// msd-core/bin/lib/cli-exit.cjs). Nothing previously spliced the flag out of
|
||||
// the `msd-tools` CLI dispatcher's own argv before it fell through to command
|
||||
// dispatch — the `--json-errors` block did this splice for itself, but
|
||||
// `--exit-contract=<v>` never got the same treatment. The dispatcher treats
|
||||
// argv[0] as the command name, so:
|
||||
// `gsd-tools --exit-contract=v2 state validate --strict` (leading) died
|
||||
// `msd-tools --exit-contract=v2 state validate --strict` (leading) died
|
||||
// with "Unknown command: --exit-contract=v2" (exit 64) — the flag was never
|
||||
// consumed and squatted on the command-name slot.
|
||||
// `gsd-tools state-snapshot --exit-contract=v2` (trailing) worked, because
|
||||
// `msd-tools state-snapshot --exit-contract=v2` (trailing) worked, because
|
||||
// the flag landed after the command name and never collided with dispatch.
|
||||
describe('#3912: gsd-tools dispatcher splices --exit-contract=<v> regardless of argv position', () => {
|
||||
const GSD_TOOLS_BIN = path.resolve(__dirname, '../gsd-core/bin/gsd-tools.cjs');
|
||||
describe('#3912: msd-tools dispatcher splices --exit-contract=<v> regardless of argv position', () => {
|
||||
const MSD_TOOLS_BIN = path.resolve(__dirname, '../msd-core/bin/msd-tools.cjs');
|
||||
|
||||
function run(args, options = {}) {
|
||||
return toLegacyResult(runNode([GSD_TOOLS_BIN, ...args], { timeoutMs: PROBE_TIMEOUT_MS, ...options }));
|
||||
return toLegacyResult(runNode([MSD_TOOLS_BIN, ...args], { timeoutMs: PROBE_TIMEOUT_MS, ...options }));
|
||||
}
|
||||
|
||||
// Fixture: a temp dir with a `.planning/` directory but no `STATE.md`, so
|
||||
@@ -1514,11 +1514,11 @@ describe('#3906: terminateNow', () => {
|
||||
assert.equal(r.status, 70, `expected INTERNAL (70); stderr: ${r.stderr}`);
|
||||
});
|
||||
|
||||
test('FAIL under an invalid ambient GSD_EXIT_CONTRACT=v3 exits 70, not a Node crash and not a silent v1', () => {
|
||||
test('FAIL under an invalid ambient MSD_EXIT_CONTRACT=v3 exits 70, not a Node crash and not a silent v1', () => {
|
||||
const r = toLegacyResult(runNode(['-e', [
|
||||
`const c = require(${JSON.stringify(BUILT_CLI_EXIT_PATH)});`,
|
||||
`c.terminateNow('FAIL', {});`,
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_EXIT_CONTRACT: 'v3' } }));
|
||||
].join('\n')], { timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, MSD_EXIT_CONTRACT: 'v3' } }));
|
||||
assert.equal(r.status, 70, `expected INTERNAL (70), not a Node crash / silent v1; stderr: ${r.stderr}`);
|
||||
assert.notEqual(r.status, 1, 'FAIL under a valid v1 resolution would exit 1 — 70 proves the invalid version was not silently accepted as v1');
|
||||
});
|
||||
@@ -1666,8 +1666,8 @@ describe('#3906: cross-copy — the built copy and the scripts copy agree', () =
|
||||
}
|
||||
});
|
||||
|
||||
test('scripts/lib/cli-exit.cjs + its sibling exit-code-registry.cjs load standalone, no gsd-core sibling', (t) => {
|
||||
const dir = createTempDir('gsd-3906-standalone-');
|
||||
test('scripts/lib/cli-exit.cjs + its sibling exit-code-registry.cjs load standalone, no msd-core sibling', (t) => {
|
||||
const dir = createTempDir('msd-3906-standalone-');
|
||||
t.after(() => cleanup(dir));
|
||||
const copiedExit = path.join(dir, 'cli-exit.cjs');
|
||||
const copiedRegistry = path.join(dir, 'exit-code-registry.cjs');
|
||||
@@ -1675,7 +1675,7 @@ describe('#3906: cross-copy — the built copy and the scripts copy agree', () =
|
||||
fs.copyFileSync(path.resolve(__dirname, '../scripts/lib/exit-code-registry.cjs'), copiedRegistry);
|
||||
|
||||
// This directory contains ONLY these two files — no node_modules, no
|
||||
// gsd-core sibling. A successful load here is the behavioral proof (per
|
||||
// msd-core sibling. A successful load here is the behavioral proof (per
|
||||
// CONTRIBUTING.md's ban on source-grepping a generated .cjs for its
|
||||
// require() specifiers) that this module's only dependencies are
|
||||
// node: builtins plus ./exit-code-registry.cjs: anything else would
|
||||
@@ -1719,16 +1719,16 @@ describe('#3906: cross-copy — the built copy and the scripts copy agree', () =
|
||||
//
|
||||
// The WHOLE POINT of hooks/lib/cli-exit.js is that a shipped enforcement hook
|
||||
// can terminate through terminateNow WITHOUT depending on any build
|
||||
// artifact. gsd-core/bin/lib is gitignored tsc output and is ABSENT on a raw
|
||||
// artifact. msd-core/bin/lib is gitignored tsc output and is ABSENT on a raw
|
||||
// plugin-marketplace or git-clone install. These tests prove that property
|
||||
// hermetically: by copying ONLY hooks/lib/cli-exit.js and its sibling
|
||||
// hooks/lib/exit-code-registry.js into a fresh, otherwise-empty tmpdir (no
|
||||
// gsd-core sibling, no node_modules) and requiring the copy from a CHILD
|
||||
// msd-core sibling, no node_modules) and requiring the copy from a CHILD
|
||||
// process rooted there.
|
||||
describe('#3911: hooks/lib/cli-exit.js loads and terminates with no build present (A4, load-bearing)', () => {
|
||||
/** Copy both hooks/lib artifacts into a fresh, otherwise-empty tmpdir. */
|
||||
function makeStandaloneHooksCopy(t) {
|
||||
const dir = createTempDir('gsd-3911-hooks-standalone-');
|
||||
const dir = createTempDir('msd-3911-hooks-standalone-');
|
||||
t.after(() => cleanup(dir));
|
||||
const copiedExit = path.join(dir, 'cli-exit.js');
|
||||
const copiedRegistry = path.join(dir, 'exit-code-registry.js');
|
||||
@@ -1738,7 +1738,7 @@ describe('#3911: hooks/lib/cli-exit.js loads and terminates with no build presen
|
||||
}
|
||||
|
||||
// This test must FAIL if hooks/lib/cli-exit.js ever gains a require
|
||||
// reaching outside hooks/lib/ (e.g. back into gsd-core/bin/lib, or a
|
||||
// reaching outside hooks/lib/ (e.g. back into msd-core/bin/lib, or a
|
||||
// node_modules package): the tmpdir contains NOTHING else, so any such
|
||||
// require resolves to MODULE_NOT_FOUND and the child crashes before
|
||||
// terminateNow ever runs, failing every assertion below.
|
||||
@@ -1845,7 +1845,7 @@ describe('#3911: the --check guards for the new hooks/lib artifacts can actually
|
||||
const CHECK_TIMEOUT_MS = 90000;
|
||||
|
||||
test('gen-hooks-cli-exit.cjs --check fails on a corrupted TMPDIR copy of hooks/lib/cli-exit.js, names the file, and clears on restore', (t) => {
|
||||
const dir = createTempDir('gsd-3911-hooks-cli-exit-check-');
|
||||
const dir = createTempDir('msd-3911-hooks-cli-exit-check-');
|
||||
t.after(() => cleanup(dir));
|
||||
const copiedOut = path.join(dir, 'cli-exit.js');
|
||||
const original = fs.readFileSync(HOOKS_CLI_EXIT_PATH);
|
||||
@@ -1875,7 +1875,7 @@ describe('#3911: the --check guards for the new hooks/lib artifacts can actually
|
||||
});
|
||||
|
||||
test('gen-exit-code-registry.cjs --check fails on a corrupted TMPDIR copy of hooks/lib/exit-code-registry.js, names the file, and clears on restore', (t) => {
|
||||
const dir = createTempDir('gsd-3911-exit-code-registry-check-');
|
||||
const dir = createTempDir('msd-3911-exit-code-registry-check-');
|
||||
t.after(() => cleanup(dir));
|
||||
|
||||
// Copy all FIVE generated artifacts into the tmpdir so --check compares
|
||||
@@ -1975,7 +1975,7 @@ describe('#3912: pending-outcome cell (runMain precedence + parity)', () => {
|
||||
// Under v2, DEGRADED alone would project to 80; PASS must win with 0.
|
||||
const r = toLegacyResult(runNode(['-e', script], {
|
||||
timeoutMs: PROBE_TIMEOUT_MS,
|
||||
env: { ...process.env, GSD_EXIT_CONTRACT: 'v2' },
|
||||
env: { ...process.env, MSD_EXIT_CONTRACT: 'v2' },
|
||||
}));
|
||||
assert.equal(r.status, 0, `explicit PASS return must win over the pending DEGRADED cell; stderr: ${r.stderr}`);
|
||||
});
|
||||
@@ -1989,7 +1989,7 @@ describe('#3912: pending-outcome cell (runMain precedence + parity)', () => {
|
||||
].join('\n');
|
||||
const r = toLegacyResult(runNode(['-e', script], {
|
||||
timeoutMs: PROBE_TIMEOUT_MS,
|
||||
env: { ...process.env, GSD_EXIT_CONTRACT: 'v2' },
|
||||
env: { ...process.env, MSD_EXIT_CONTRACT: 'v2' },
|
||||
}));
|
||||
assert.equal(r.status, 42, `explicit numeric return must win over the pending DEGRADED cell; stderr: ${r.stderr}`);
|
||||
});
|
||||
@@ -2012,11 +2012,11 @@ describe('#3912: pending-outcome cell (runMain precedence + parity)', () => {
|
||||
`setImmediate(() => {});`,
|
||||
].join('\n');
|
||||
const v1 = toLegacyResult(runNode(['-e', script], {
|
||||
timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_EXIT_CONTRACT: 'v1' },
|
||||
timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, MSD_EXIT_CONTRACT: 'v1' },
|
||||
}));
|
||||
assert.equal(v1.status, 0, `stderr: ${v1.stderr}`);
|
||||
const v2 = toLegacyResult(runNode(['-e', script], {
|
||||
timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_EXIT_CONTRACT: 'v2' },
|
||||
timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, MSD_EXIT_CONTRACT: 'v2' },
|
||||
}));
|
||||
assert.equal(v2.status, 80, `stderr: ${v2.stderr}`);
|
||||
});
|
||||
@@ -2038,11 +2038,11 @@ describe('#3912: pending-outcome cell (runMain precedence + parity)', () => {
|
||||
`setImmediate(() => {});`,
|
||||
].join('\n');
|
||||
const v1 = toLegacyResult(runNode(['-e', script], {
|
||||
timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_EXIT_CONTRACT: 'v1' },
|
||||
timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, MSD_EXIT_CONTRACT: 'v1' },
|
||||
}));
|
||||
assert.equal(v1.status, 1, `an already-set non-zero exitCode must not be clobbered down to DEGRADED's v1 projection (0); stderr: ${v1.stderr}`);
|
||||
const v2 = toLegacyResult(runNode(['-e', script], {
|
||||
timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_EXIT_CONTRACT: 'v2' },
|
||||
timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, MSD_EXIT_CONTRACT: 'v2' },
|
||||
}));
|
||||
assert.equal(v2.status, 1, `an already-set non-zero exitCode must not be clobbered by DEGRADED's v2 projection (80) either; stderr: ${v2.stderr}`);
|
||||
});
|
||||
@@ -2050,7 +2050,7 @@ describe('#3912: pending-outcome cell (runMain precedence + parity)', () => {
|
||||
// C6: extends the existing three-copy parity coverage (json-error-mode cell,
|
||||
// tested above at "both copies of the exit module share one json-error-mode
|
||||
// cell") to the pending-outcome cell, across all THREE emitted copies —
|
||||
// built (gsd-core/bin/lib), scripts/lib, and hooks/lib — not just two.
|
||||
// built (msd-core/bin/lib), scripts/lib, and hooks/lib — not just two.
|
||||
test('C6: all three emitted copies of cli-exit share ONE pending-outcome cell', () => {
|
||||
const r = toLegacyResult(runNode(['-e', [
|
||||
`const built = require(${JSON.stringify(BUILT_CLI_EXIT_PATH)});`,
|
||||
@@ -2081,7 +2081,7 @@ describe('#3912: pending-outcome cell (runMain precedence + parity)', () => {
|
||||
`c.runMain(() => undefined);`,
|
||||
`setImmediate(() => {});`,
|
||||
].join('\n')], {
|
||||
timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_EXIT_CONTRACT: version },
|
||||
timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, MSD_EXIT_CONTRACT: version },
|
||||
}));
|
||||
const expected = version === 'v1' ? 0 : 80;
|
||||
assert.equal(
|
||||
|
||||
Reference in New Issue
Block a user