From 652b99b71b820cb24206128e7b4ad66f65e5bfdb Mon Sep 17 00:00:00 2001 From: 0xdhx Date: Mon, 3 Aug 2026 03:09:17 -0500 Subject: [PATCH] test(#2665): reversion guards for all three round-4 fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit None of the round-4 fixes had a test that fails on reversion: re-shipping the test-instrumentation chain passes #2858 (everything ships, so every require resolves), dropping the strict env from test.yml demotes the guard to report-only with nothing red, and the skillsHome derivation tests are enumeration-relative over declarations that are all empty today. One guard each, every one negative-controlled against its reverted fix (fails pre-fix, passes post-fix): - packaging-shipped-scripts-require-only-shipped.test.cjs asserts the four chain files are absent from the npm pack file list (reuses the tarball set the #2858 gate already resolves — no second npm pack). - live-config-guard.test.cjs asserts all three test jobs wire GSD_STRICT_LIVE_CONFIG_GUARD, matching the WHOLE expression anchored — a prefix match accepted both a Windows-silently-strict tail and a malformed one. - helpers-process-isolation.test.cjs cold-requires helpers.cjs in a child with sentinel skillsHome env vars injected into both enumerations, so the walk itself is under test rather than today's empty declarations. --- tests/helpers-process-isolation.test.cjs | 54 +++++++++++++++++++ tests/live-config-guard.test.cjs | 37 +++++++++++++ ...pped-scripts-require-only-shipped.test.cjs | 21 ++++++++ 3 files changed, 112 insertions(+) diff --git a/tests/helpers-process-isolation.test.cjs b/tests/helpers-process-isolation.test.cjs index d18e4f2b9..d6554813f 100644 --- a/tests/helpers-process-isolation.test.cjs +++ b/tests/helpers-process-isolation.test.cjs @@ -235,3 +235,57 @@ describe('#2665: TEST_ENV_BASE config-location coverage', () => { }); }); }); + +describe('#2665 round 4: the skillsHome walk is reversion-sensitive', () => { + // The coverage tests above are enumeration-relative, and skillsHome.env is + // empty everywhere today — so reverting the skillsHome rungs from the + // derivation leaves every one of them green (measured by this round's + // pre-push adversarial review). This test closes that: it cold-requires + // helpers.cjs in a child process after injecting sentinel skillsHome env + // vars into BOTH enumerations (registry and non-registry), so the walk + // itself is what is under test, not today's empty declarations. + test('sentinel skillsHome vars flow into TEST_ENV_BASE on both rungs', () => { + const { execFileSync } = require('node:child_process'); + const regPath = require.resolve('../gsd-core/bin/lib/capability-registry.cjs'); + const rhPath = require.resolve('../gsd-core/bin/lib/runtime-homes.cjs'); + const helpersPath = require.resolve('./helpers.cjs'); + + const script = ` + 'use strict'; + const reg = require(${JSON.stringify(regPath)}); + const rh = require(${JSON.stringify(rhPath)}); + // Rung 1 (registry): give one runtime a skillsHome env var. kilo already + // declares skillsHome (env: []); push a sentinel into whichever runtime + // declares it, or graft one onto the first runtime if none does. + const declaring = Object.values(reg.runtimes).find( + (r) => r?.runtime?.configHome?.skillsHome, + ) ?? Object.values(reg.runtimes)[0]; + if (!declaring.runtime.configHome.skillsHome) { + declaring.runtime.configHome.skillsHome = { kind: 'dot-home', name: '.x', env: [] }; + } + declaring.runtime.configHome.skillsHome.env = ['GSD_TEST_SENTINEL_REGISTRY_SKILLS']; + // Rung 2 (non-registry): graft a skillsHome onto the first descriptor. + rh.NON_REGISTRY_CONFIG_HOME_DESCRIPTORS[0].skillsHome = { + kind: 'dot-home', name: '.x', env: ['GSD_TEST_SENTINEL_NONREG_SKILLS'], + }; + const { TEST_ENV_BASE } = require(${JSON.stringify(helpersPath)}); + const missing = [ + 'GSD_TEST_SENTINEL_REGISTRY_SKILLS', + 'GSD_TEST_SENTINEL_NONREG_SKILLS', + ].filter((k) => TEST_ENV_BASE[k] !== ''); + if (missing.length > 0) { + console.error('skillsHome walk missed: ' + missing.join(', ')); + process.exit(1); + } + process.exit(0); + `; + + const out = execFileSync(process.execPath, ['-e', script], { + cwd: __dirname, + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + timeout: 30_000, + }); + void out; // exit 0 is the assertion; execFileSync throws on nonzero + }); +}); diff --git a/tests/live-config-guard.test.cjs b/tests/live-config-guard.test.cjs index 41ebccca0..a12c46006 100644 --- a/tests/live-config-guard.test.cjs +++ b/tests/live-config-guard.test.cjs @@ -410,3 +410,40 @@ describe('#2665: scan-budget truncation', () => { } }); }); + +describe('#2665 round 4: CI wires the guard to strict mode', () => { + // The reversion this guards: dropping GSD_STRICT_LIVE_CONFIG_GUARD from + // test.yml silently demotes the guard back to report-only, and a future + // leak of exactly the class #2665 closes prints a warning and CI stays + // green. Windows lanes are deliberately report-only until the documented + // pre-existing USERPROFILE leak class is swept (SEVERITY note in + // scripts/live-config-guard.cjs) — so the assertion is per-OS, not global. + test('all three test jobs set GSD_STRICT_LIVE_CONFIG_GUARD (Windows carved out)', () => { + const yaml = require('js-yaml'); + const wf = yaml.load( + fs.readFileSync( + path.join(__dirname, '..', '.github', 'workflows', 'test.yml'), + 'utf8', + ), + ); + + for (const job of ['test', 'test-full']) { + const v = String(wf.jobs?.[job]?.env?.GSD_STRICT_LIVE_CONFIG_GUARD ?? ''); + assert.match( + v, + // The WHOLE expression, anchored — a prefix match accepted both + // `&& '1' || '1'` (Windows silently strict) and a malformed tail + // (found by this round's pre-push adversarial review). + /^\$\{\{\s*matrix\.os\s*!=\s*'windows-latest'\s*&&\s*'1'\s*\|\|\s*''\s*\}\}$/, + `jobs.${job}.env.GSD_STRICT_LIVE_CONFIG_GUARD must be strict on ` + + `non-Windows lanes and empty on windows-latest; got: ${JSON.stringify(v)}`, + ); + } + + assert.strictEqual( + String(wf.jobs?.['test-inert']?.env?.GSD_STRICT_LIVE_CONFIG_GUARD ?? ''), + '1', + 'jobs.test-inert (ubuntu-only) must set GSD_STRICT_LIVE_CONFIG_GUARD: 1', + ); + }); +}); diff --git a/tests/packaging-shipped-scripts-require-only-shipped.test.cjs b/tests/packaging-shipped-scripts-require-only-shipped.test.cjs index 8d3142c31..774a3d4df 100644 --- a/tests/packaging-shipped-scripts-require-only-shipped.test.cjs +++ b/tests/packaging-shipped-scripts-require-only-shipped.test.cjs @@ -135,6 +135,27 @@ describe('#2858 — shipped scripts require only shipped paths', () => { .sort(); }); + test('#2665: the test-instrumentation chain does not ship', () => { + // These four are one closed require chain of test instrumentation + // (run-tests -> live-config-guard, affected-tests-lib -> run-tests, + // run-affected-tests -> affected-tests-lib). Excluding a strict subset + // re-trips the shipped-requires-only-shipped gate above on whichever links + // still ship, so the exclusion set and this assertion cover the chain. + const TEST_INSTRUMENTATION = [ + 'scripts/live-config-guard.cjs', + 'scripts/run-tests.cjs', + 'scripts/affected-tests-lib.cjs', + 'scripts/run-affected-tests.cjs', + ]; + for (const f of TEST_INSTRUMENTATION) { + assert.ok( + !shippedFiles.has(f), + `${f} is test instrumentation and must not ship — restore its ` + + "package.json files[] '!'-exclusion (and keep the whole chain excluded)", + ); + } + }); + test('every shipped scripts/*.{cjs,js} is require-able from a shipped-only tree', () => { assert.ok(shippedScripts.length > 0, 'expected at least one shipped script');