diff --git a/scripts/live-config-guard.cjs b/scripts/live-config-guard.cjs index 74a83c433..5794718bf 100644 --- a/scripts/live-config-guard.cjs +++ b/scripts/live-config-guard.cjs @@ -41,6 +41,17 @@ * KNOWN GAP — a leak into a file GSD does not own (e.g. mutating the host's own * `.claude.json`) is outside this guard by construction. Closing it would require * watching shared files, which is the false-positive trap above. + * + * SEVERITY — reports by default, fails only under GSD_STRICT_LIVE_CONFIG_GUARD=1. + * Not timidity: on its first CI run this guard found PRE-EXISTING leaks on the + * Windows lane (`C:\Users\runneradmin\.claude\gsd-core` and + * `skills\gsd-dev-preferences`), because os.homedir() reads USERPROFILE there and + * ~190 test sites sandbox HOME alone. Those are real and worth fixing, but they + * are a different defect class from the one #2665 closes, and a brand-new gate + * that immediately reds an unrelated lane gets bypassed or reverted rather than + * obeyed. This repo already has the pattern: the local/no-source-grep ESLint rule + * shipped at `warn` and was promoted to `error` after its cleanup sweep (ADR 452). + * Promote this the same way once the USERPROFILE sweep lands. */ const fs = require('fs'); @@ -209,7 +220,7 @@ function diffLiveConfig(before, after) { function formatViolations(violations) { const lines = [ '', - 'run-tests: HERMETICITY FAILURE — the suite wrote into a LIVE config directory.', + 'run-tests: HERMETICITY WARNING — the suite wrote into a LIVE config directory.', '', 'A test resolved a runtime config dir from the ambient environment instead of a', 'sandbox. The usual cause is an IN-PROCESS install() call: tests/helpers.cjs', @@ -225,7 +236,10 @@ function formatViolations(violations) { lines.push(` ${label}: ${v.path}`); } lines.push(''); - lines.push('Set GSD_SKIP_LIVE_CONFIG_GUARD=1 to bypass (intentionally loud).'); + lines.push( + 'Reporting only. Set GSD_STRICT_LIVE_CONFIG_GUARD=1 to make this fail the run, ' + + 'or GSD_SKIP_LIVE_CONFIG_GUARD=1 to skip the check entirely.', + ); lines.push(''); return lines.join('\n'); } diff --git a/scripts/run-tests.cjs b/scripts/run-tests.cjs index e2eef2ec8..1633c7d78 100644 --- a/scripts/run-tests.cjs +++ b/scripts/run-tests.cjs @@ -1055,7 +1055,11 @@ function main() { const violations = diffLiveConfig(liveConfigBefore, snapshotLiveConfig(liveConfigRoots)); if (violations.length > 0) { console.error(formatViolations(violations)); - if (firstFailureExit === 0) firstFailureExit = 1; + // Reports by default; fails only under opt-in strict mode. See the + // SEVERITY note in scripts/live-config-guard.cjs for why. + if (process.env.GSD_STRICT_LIVE_CONFIG_GUARD === '1' && firstFailureExit === 0) { + firstFailureExit = 1; + } } } diff --git a/tests/live-config-guard.test.cjs b/tests/live-config-guard.test.cjs index b942342b6..0a757795a 100644 --- a/tests/live-config-guard.test.cjs +++ b/tests/live-config-guard.test.cjs @@ -152,9 +152,10 @@ describe('#2665: live-config hermeticity guard', () => { test('the report names the path and the remedy', () => { const out = formatViolations([{ path: '/live/.claude/gsd-core', kind: 'created' }]); - assert.match(out, /HERMETICITY FAILURE/); + assert.match(out, /HERMETICITY WARNING/); assert.match(out, /\/live\/\.claude\/gsd-core/); assert.match(out, /scrubConfigLocationEnv/); assert.match(out, /GSD_SKIP_LIVE_CONFIG_GUARD/); + assert.match(out, /GSD_STRICT_LIVE_CONFIG_GUARD/); }); });