fix(release-gate): skip duplicate CI work and stop live LLM blowups

Shared paths like db.mjs were pulling PAGE/DATA live agent suites into every
hotfix. Keep those cases for actual page-data changes, resume passed suites on
the same artifact, and fail fast on Docker/port issues instead of rerunning 100+
scenarios.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-14 22:15:16 +08:00
parent 293ac69a76
commit 2f240e7500
14 changed files with 487 additions and 26 deletions
+54 -5
View File
@@ -6,7 +6,14 @@ import { assertPortalRuntimePath, hashArtifact, inspectPortalRuntime } from './a
import { loadScenarioCatalog } from './catalog.mjs';
import { AUTOMATION_SUITES, validateAutomationSuites } from './coverage.mjs';
import { selectImpactScenarios } from './impact.mjs';
import { preflightImpactSuites } from './preflight.mjs';
import { loadActiveRegressionCorpus } from './regression-corpus.mjs';
import {
REPOSITORY_CHECK_IDS,
carryForwardScenario,
findImpactResumeReport,
shouldRerunImpactSuite,
} from './resume.mjs';
import {
buildIncrementalReport,
findCarryForwardBaseline,
@@ -419,24 +426,61 @@ export async function executeImpactReleaseGate(options) {
const suites = AUTOMATION_SUITES.filter(
(suite) => suite.scenarios.some((scenarioId) => selectedIds.has(scenarioId)),
);
const resume = await findImpactResumeReport({
reportRoot: options.reportRoot,
artifactSha256: artifact.sha256,
commitSha,
});
let changedPathsSincePrevious = [];
if (resume?.commitSha && resume.commitSha !== commitSha) {
try {
changedPathsSincePrevious = await listChangedPathsBetween(resume.commitSha, commitSha);
} catch {
changedPathsSincePrevious = ['release-gate/coverage.mjs'];
}
}
const suitesToRun = suites.filter((suite) => shouldRerunImpactSuite({
suite,
selectedIds,
previousReport: resume?.report ?? null,
artifactSha256: artifact.sha256,
commitSha,
changedPathsSincePrevious,
}));
const skippedSuites = suites.filter((suite) => !suitesToRun.includes(suite));
const previousById = new Map((resume?.report?.scenarios ?? []).map((scenario) => [scenario.id, scenario]));
for (const suite of skippedSuites) {
for (const scenarioId of suite.scenarios) {
if (!selectedIds.has(scenarioId) || REPOSITORY_CHECK_IDS.has(scenarioId)) continue;
const previous = previousById.get(scenarioId);
if (!previous) continue;
const current = byId.get(scenarioId);
Object.assign(current, carryForwardScenario(previous));
}
}
if (suitesToRun.length > 0) {
await preflightImpactSuites(suitesToRun);
}
const executions = await runSuitesWithConcurrency(
suites,
suitesToRun,
options.suiteConcurrency,
(suite) => runSuite(suite, outputDir, options.timeoutMs),
);
for (let index = 0; index < suites.length; index += 1) {
const suite = suites[index];
for (let index = 0; index < suitesToRun.length; index += 1) {
const suite = suitesToRun[index];
const execution = executions[index];
for (const scenarioId of suite.scenarios) {
if (!selectedIds.has(scenarioId)) continue;
const scenario = byId.get(scenarioId);
scenario.status = execution.code === 0 && !execution.timedOut ? 'passed' : 'failed';
scenario.reason = scenario.status === 'passed' ? null : 'automation_suite_failed';
scenario.evidence.push(
scenario.evidence = [
`suite=${suite.id}`,
`log=${execution.logPath}`,
...suite.cases[scenarioId].map((assertedCase) => `asserted_case=${assertedCase}`),
);
'carried_forward=false',
];
}
}
@@ -457,6 +501,11 @@ export async function executeImpactReleaseGate(options) {
startedAt,
completedAt,
});
report.resume = {
baseline_commit: resume?.commitSha ?? null,
carried_suites: skippedSuites.map((suite) => suite.id),
reran_suites: suitesToRun.map((suite) => suite.id),
};
await writeGateReport(report, outputDir);
return { report, outputDir, selection };
}