From 22f183f8cbb9ee59b5ee2b37675c148cecec235b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 12 Jul 2026 12:52:31 +0100 Subject: [PATCH] ci: isolate legacy QA smoke invocations (#105340) * ci: isolate legacy QA smoke invocations * docs(agents): record PR review recommendation --- .github/workflows/ci.yml | 30 +++++++++++-------------- AGENTS.md | 1 + test/scripts/ci-workflow-guards.test.ts | 4 +++- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22ea987ab03c..44ed26636c46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1208,7 +1208,7 @@ jobs: } else if (typeof smokePlan.createQaSmokeCiMatrix === "function") { // Legacy planners select the entire profile and can mix long-lived // execution kinds. Reuse the current bounded smoke contract and - // isolate each kind so frozen targets cannot pin a profile part. + // isolate each scenario so one invocation cannot pin a profile part. const compatibilityScenarioIds = new Set([ "control-ui-chat-flow-playwright", "crestodian-ring-zero-setup", @@ -1236,28 +1236,24 @@ jobs: const legacyRuns = smokePlan .createQaSmokeCiMatrix() .include.filter((_, index) => index % 2 === partIndex); - runs = legacyRuns.flatMap((run) => { - const scenarioIdsByKind = new Map(); - for (const scenarioId of run.scenario_ids) { + runs = legacyRuns.flatMap((run) => + run.scenario_ids.flatMap((scenarioId) => { if (!compatibilityScenarioIds.has(scenarioId)) { - continue; + return []; } const kind = scenarioKindById.get(scenarioId); if (!kind) { throw new Error(`legacy QA smoke scenario not found: ${scenarioId}`); } - const scenarioIds = scenarioIdsByKind.get(kind) ?? []; - scenarioIds.push(scenarioId); - scenarioIdsByKind.set(kind, scenarioIds); - } - return [...scenarioIdsByKind.entries()] - .toSorted(([left], [right]) => left.localeCompare(right)) - .map(([kind, scenarioIds]) => ({ - ...run, - slug: `${run.slug}-${kind}`, - scenario_ids: scenarioIds, - })); - }); + return [ + { + ...run, + slug: `${run.slug}-${kind}-${scenarioId}`, + scenario_ids: [scenarioId], + }, + ]; + }), + ); } else { throw new Error("QA smoke plan does not expose a supported CI planner."); } diff --git a/AGENTS.md b/AGENTS.md index 9ab3065ec34b..5aefc595d98a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -195,6 +195,7 @@ Skills own workflows; root owns hard policy and routing. - CI polling: exact SHA, relevant checks only, minimal fields. Skip routine noise (`Auto response`, `Labeler`, docs agents, performance/stale). Logs only after failure/completion or concrete need. - Trusted-workflow release-branch CI: pass `target_ref` + `release_candidate_ref`; never `release_gate` (requires workflow head == target). - Agent PR landing to `main`: use only the repo-native `scripts/pr` wrapper: run `scripts/pr review-init `, follow its emitted checkout/guard guidance, initialize and complete review artifacts with `scripts/pr review-artifacts-init `, validate them with `scripts/pr review-validate-artifacts `, then run `OPENCLAW_TESTBOX=1 scripts/pr prepare-run ` and `scripts/pr merge-run `. The Testbox flag is mandatory for agents so prepare verifies hosted CI/Testbox on the current head or reuses a patch-identical pre-rebase run green within 24 hours instead of running full gates locally. For owner-approved reviewed fork code without hosted Testbox, use `OPENCLAW_PR_GATES_REMOTE=testbox` instead. Do not rebase only because `main` advanced; merge drift is advisory unless strict drift is explicitly enabled, while GitHub still blocks conflicts. Do not idle on `auto-response` or `check-docs`. +- `scripts/pr` review JSON: land-ready recommendation `READY FOR /prepare-pr`, `issueValidation.status=valid`; never `APPROVE`. ## Code diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index 41e36667a541..e1afebafdc9f 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -2775,7 +2775,9 @@ describe("ci workflow guards", () => { expect(smokeRunStep.run).toContain("createQaSmokeCiPart"); expect(smokeRunStep.run).toContain("createQaSmokeCiMatrix"); expect(smokeRunStep.run).toContain("readQaScenarioPack"); - expect(smokeRunStep.run).toContain("scenarioIdsByKind"); + expect(smokeRunStep.run).toContain("isolate each scenario"); + expect(smokeRunStep.run).toContain("scenario_ids: [scenarioId]"); + expect(smokeRunStep.run).not.toContain("scenarioIdsByKind"); const compatibilityScenarioBlock = smokeRunStep.run.match( /const compatibilityScenarioIds = new Set\(\[([\s\S]*?)\]\);/u, )?.[1];