ci(mutants): skip the nightly registry on a proven tree; install only the shard's environment

#620. Mutants were ~70 % of CI machine time.

1. mutation-gate.yml: a green full run (aggregator verified all six shards)
   leaves a cache marker keyed by material tree + workflow SHA. A scheduled
   night with the same tree and workflow, marker not older than 7 days, skips
   the shards and writes "reused from run N" to the run summary. Red runs
   leave no marker, so the next night runs again and still files the issue
   (#472). Manual dispatch always runs the full registry. Decision is a pure
   function in scripts/mutation-nightly-reuse.mjs.
2. changed_mutants: the shard plan (already computed before setup, #518) now
   names the environment of its guards (plan-browser=/plan-python=, from
   scripts/mutation-environment.mjs). Python + backend deps only for shards
   with pytest guards, Chromium only for shards whose guards reach
   Playwright; pip wheels cached. Every shard still runs and reports, so the
   six mutant jobs of the review proof are unchanged.

Item 3 of the issue (smoke guards -> node --test) is out of scope here.

Issue: #620
User-Visible: no
This commit is contained in:
Claude
2026-09-24 05:48:42 +00:00
committed by claude[bot]
parent 7bb55c2a41
commit 44ee23ee33
10 changed files with 739 additions and 11 deletions
+6
View File
@@ -23,6 +23,7 @@ import {
LEDGER_SCHEMA, readLedger, recordCaught, splitByLedger, witnessFingerprint,
} from './mutation-evidence.mjs';
import { attributeSetupFailure } from './mutation-attribution.mjs';
import { guardEnvironment, planEnvironment, planEnvironmentLines } from './mutation-environment.mjs';
import { MUTATION_OUTCOME, isProofOutcome } from './mutation-guard-outcome.mjs';
const repoRoot = fileURLToPath(new URL('..', import.meta.url));
@@ -215,6 +216,11 @@ export async function main(argv) {
// ДО установки окружения (npm ci, python, Chromium ≈ 3 минуты на шард).
if (argv.includes('--plan-only')) {
console.log(`plan=${toRun.length}`);
// #620: и какое окружение нужно гардам плана — шаг ставит только его.
const exists = (file) => existsSync(join(repoRoot, file));
const read = (file) => (exists(file) ? readFileSync(join(repoRoot, file), 'utf8') : '');
const need = planEnvironment(toRun, (guard) => guardEnvironment(guard, { read, exists }));
for (const line of planEnvironmentLines(need)) console.log(line);
reportPlanMetrics();
return 0;
}