From 698886caf3e3aab91a1ac3f0c6752a8ae1108a4e Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 27 Aug 2026 18:40:56 +0800 Subject: [PATCH] fix(ci): run FRV contract tests for workflow edits (#130668) * test(ci): route FRV workflow contract tests * test(ci): route remaining FRV workflow contracts * fix(ci): route FRV workflow contract test * fix(ci): discover workflow contract tests --- scripts/test-projects.test-support.mts | 34 +++++-- test/scripts/test-projects.test.ts | 135 ++++++++++++++++++++++++- 2 files changed, 161 insertions(+), 8 deletions(-) diff --git a/scripts/test-projects.test-support.mts b/scripts/test-projects.test-support.mts index b71a7bb740fb..dbfde136680f 100644 --- a/scripts/test-projects.test-support.mts +++ b/scripts/test-projects.test-support.mts @@ -2281,7 +2281,18 @@ const SEMANTIC_TOOLING_TARGET_PATTERNS: Array<[RegExp, string[]]> = [ ], [ /^\.github\/workflows\/full-release-validation\.yml$/u, - ["src/dockerfile.test.ts", packageAcceptance, pluginPrerelease], + [ + "src/dockerfile.test.ts", + "full-release-validation-state", + "full-release-validation-at-sha", + "find-reusable-release-validation", + "openclaw-npm-extended-stable-full-validation-workflow", + "release-no-push-workflow", + "release-ci-summary", + packageAcceptance, + pluginPrerelease, + "check-workflows", + ], ], [ /^\.github\/workflows\/openclaw-release-checks\.yml$/u, @@ -2911,11 +2922,15 @@ function resolveSemanticToolingTargets(changedPath: string) { ); } +function isGithubWorkflowOrActionYaml(changedPath: string) { + return ( + /^\.github\/workflows\/[^/]+\.ya?ml$/u.test(changedPath) || + /^\.github\/actions\/.+\.ya?ml$/u.test(changedPath) + ); +} + function resolveGithubYamlGuardTargets(changedPath: string) { - if (/^\.github\/workflows\/[^/]+\.ya?ml$/u.test(changedPath)) { - return GITHUB_YAML_PINNING_GUARD_TEST_TARGETS; - } - if (/^\.github\/actions\/.+\.ya?ml$/u.test(changedPath)) { + if (isGithubWorkflowOrActionYaml(changedPath)) { return GITHUB_YAML_PINNING_GUARD_TEST_TARGETS; } return null; @@ -2948,13 +2963,16 @@ function resolveToolingTestTargets(changedPath: string, cwd = process.cwd()) { !changedPath.startsWith("scripts/") && changedPath.endsWith(".d.mts") ? changedPath.replace(/\.d\.mts$/u, ".mjs") : changedPath; + const githubYaml = isGithubWorkflowOrActionYaml(implementationPath); const exactOwners = EXACT_TOOLING_TARGETS.get(implementationPath); - if (exactOwners) { + if (exactOwners && !githubYaml) { return resolveToolingTestOwnerTargets(...exactOwners); } + const exactTargets = exactOwners ? resolveToolingTestOwnerTargets(...exactOwners) : []; const semanticTargets = resolveSemanticToolingTargets(implementationPath); const facts = getChangedPathFacts(changedPath); const hasToolingOwner = + exactTargets.length > 0 || semanticTargets.length > 0 || facts.surface === "rootTooling" || changedPath === "Dockerfile" || @@ -2992,6 +3010,7 @@ function resolveToolingTestTargets(changedPath: string, cwd = process.cwd()) { const githubYamlGuardTargets = resolveGithubYamlGuardTargets(implementationPath); const conventionalTargets = resolveConventionalToolingTestTargets(implementationPath, cwd); const hasDirectOwner = Boolean( + exactTargets.length || explicitTargets?.length || githubYamlGuardTargets?.length || semanticTargets.length || @@ -3007,10 +3026,11 @@ function resolveToolingTestTargets(changedPath: string, cwd = process.cwd()) { : []; const importGraphTargets = importGraphResult ?? []; const referenceTargets = - semanticTargets.length === 0 && (githubYamlGuardTargets || !hasDirectOwner) + githubYaml || (semanticTargets.length === 0 && !hasDirectOwner) ? resolveDirectToolingReferenceTests(implementationPath, cwd) : []; const targets = [ + ...exactTargets, ...(explicitTargets ?? []), ...semanticTargets, ...(conventionalTargets ?? []), diff --git a/test/scripts/test-projects.test.ts b/test/scripts/test-projects.test.ts index acab686e03e7..2d9a2076d6f6 100644 --- a/test/scripts/test-projects.test.ts +++ b/test/scripts/test-projects.test.ts @@ -491,10 +491,114 @@ describe("scripts/test-projects changed-target routing", () => { "test/scripts/plugin-contract-test-plan.test.ts", "test/scripts/plugin-prerelease-test-plan.test.ts", "test/scripts/verify-pr-hosted-gates.test.ts", + "src/scripts/ci-changed-scope.control-ui.test.ts", + "src/scripts/ci-changed-scope.test.ts", + "test/scripts/authorized-beta-focused-evidence.test.ts", + "test/scripts/changed-path-facts.test.ts", + "test/scripts/ci-changed-node-test-plan.test.ts", + "test/scripts/openclaw-npm-resume-run.test.ts", + "test/scripts/package-acceptance-workflow.test.ts", + "test/scripts/run-additional-boundary-checks.test.ts", ], ); }); + it("keeps full release validation workflow edits on FRV contract tests", () => { + expectChangedTargets( + [".github/workflows/full-release-validation.yml"], + [ + "src/dockerfile.test.ts", + "test/scripts/full-release-validation-state.test.ts", + "test/scripts/full-release-validation-at-sha.test.ts", + "test/scripts/find-reusable-release-validation.test.ts", + "test/scripts/openclaw-npm-extended-stable-full-validation-workflow.test.ts", + "test/scripts/release-no-push-workflow.test.ts", + "test/scripts/release-ci-summary.test.ts", + "test/scripts/package-acceptance-workflow.test.ts", + "test/scripts/plugin-prerelease-test-plan.test.ts", + "test/scripts/check-workflows.test.ts", + "test/scripts/ci-workflow-guards.test.ts", + "test/scripts/frv-proof-broker.test.ts", + "test/scripts/openclaw-performance-workflow.test.ts", + "test/scripts/release-plan-producer.test.ts", + "test/scripts/validate-full-release-validation-evidence.test.ts", + ], + ); + }); + + it("unions semantic workflow owners with bounded direct references", () => { + withTinyGitRepo( + { + ".github/workflows/full-release-validation.yml": "name: FRV\n", + "test/scripts/full-release-validation-state.test.ts": + 'const workflow = ".github/workflows/full-release-validation.yml";\n', + "test/scripts/unknown-frv-contract.test.ts": + 'const workflow = ".github/workflows/full-release-validation.yml";\n', + "test/scripts/unknown-frv-contract.live.test.ts": + 'const workflow = ".github/workflows/full-release-validation.yml";\n', + "test/scripts/test-projects.test.ts": + 'const workflow = ".github/workflows/full-release-validation.yml";\n', + "test/scripts/workflow-substring.test.ts": + 'const workflow = ".github/workflows/full-release-validation.yml.bak";\n', + }, + (cwd) => { + const targets = resolveChangedTestTargetPlan( + [".github/workflows/full-release-validation.yml"], + { cwd }, + ).targets; + + expect(targets).toContain("test/scripts/unknown-frv-contract.test.ts"); + expect( + targets.filter( + (target) => target === "test/scripts/full-release-validation-state.test.ts", + ), + ).toHaveLength(1); + expect(targets).not.toContain("test/scripts/unknown-frv-contract.live.test.ts"); + expect(targets).not.toContain("test/scripts/test-projects.test.ts"); + expect(targets).not.toContain("test/scripts/workflow-substring.test.ts"); + }, + ); + }); + + it.each([ + { + changedPath: ".github/workflows/plugin-npm-release.yml", + exactTarget: "test/scripts/plugin-npm-extended-stable-workflow.test.ts", + }, + { + changedPath: ".github/actions/setup-node-env/action.yml", + exactTarget: "test/scripts/install-trufflehog.test.ts", + }, + ])("unions exact owners and references for $changedPath", ({ changedPath, exactTarget }) => { + withTinyGitRepo( + { + [changedPath]: "name: fixture\n", + "test/scripts/direct-workflow-reference.test.ts": `const target = "${changedPath}";\n`, + }, + (cwd) => { + const targets = resolveChangedTestTargetPlan([changedPath], { cwd }).targets; + + expect(targets).toContain(exactTarget); + expect(targets).toContain("test/scripts/direct-workflow-reference.test.ts"); + expect(targets).toContain("test/scripts/ci-workflow-guards.test.ts"); + }, + ); + }); + + it("does not scan direct references for semantic non-YAML tooling", () => { + withTinyGitRepo( + { + "scripts/pr": "#!/bin/sh\n", + "test/scripts/direct-tooling-reference.test.ts": 'const target = "scripts/pr";\n', + }, + (cwd) => { + const targets = resolveChangedTestTargetPlan(["scripts/pr"], { cwd }).targets; + + expect(targets).not.toContain("test/scripts/direct-tooling-reference.test.ts"); + }, + ); + }); + it("keeps npm release workflow edits on the preflight cache guard", () => { expectChangedTargets( [".github/workflows/openclaw-npm-release.yml"], @@ -502,7 +606,11 @@ describe("scripts/test-projects changed-target routing", () => { "test/openclaw-npm-postpublish-verify.test.ts", "test/scripts/openclaw-npm-extended-stable-workflow.test.ts", "test/scripts/package-acceptance-workflow.test.ts", + "test/scripts/authorized-beta-focused-evidence.test.ts", "test/scripts/ci-workflow-guards.test.ts", + "test/scripts/openclaw-npm-resume-run.test.ts", + "test/scripts/package-source-preflight.test.ts", + "test/scripts/release-plan-producer.test.ts", ], ); }); @@ -514,7 +622,10 @@ describe("scripts/test-projects changed-target routing", () => { "src/dockerfile.test.ts", "test/scripts/docker-channel-promote.test.ts", "test/scripts/vercel-container-registry-publish.test.ts", + "test/scripts/authorized-beta-focused-evidence.test.ts", "test/scripts/ci-workflow-guards.test.ts", + "test/scripts/release-no-push-workflow.test.ts", + "test/scripts/release-plan-producer.test.ts", ], }, { @@ -522,6 +633,10 @@ describe("scripts/test-projects changed-target routing", () => { targets: [ "test/scripts/package-acceptance-workflow.test.ts", "test/scripts/vercel-container-registry-publish.test.ts", + "test/scripts/authorized-beta-focused-evidence.test.ts", + "test/scripts/release-candidate-checklist.test.ts", + "test/scripts/release-no-push-workflow.test.ts", + "test/scripts/release-plan-producer.test.ts", "test/scripts/ci-workflow-guards.test.ts", ], }, @@ -562,7 +677,15 @@ describe("scripts/test-projects changed-target routing", () => { ".github/workflows/real-behavior-proof.yml", ".github/workflows/stale.yml", ]) { - expectChangedTargets([workflowPath], ["test/scripts/ci-workflow-guards.test.ts"]); + expectChangedTargets( + [workflowPath], + workflowPath === ".github/workflows/labeler.yml" + ? [ + "test/scripts/ci-workflow-guards.test.ts", + "test/scripts/ci-changed-node-test-plan.test.ts", + ] + : ["test/scripts/ci-workflow-guards.test.ts"], + ); } }); @@ -585,6 +708,8 @@ describe("scripts/test-projects changed-target routing", () => { "test/scripts/package-acceptance-workflow.test.ts", "test/scripts/changed-lanes.test.ts", "test/scripts/install-trufflehog.test.ts", + "test/scripts/pr-prepare-gates.test.ts", + "test/scripts/testbox-lease-freshness.test.ts", ], ], [ @@ -699,7 +824,15 @@ describe("scripts/test-projects changed-target routing", () => { "test/scripts/openclaw-cross-os-release-checks.test.ts", "test/scripts/plugin-prerelease-test-plan.test.ts", "test/scripts/test-install-sh-docker.test.ts", + "test/scripts/authorized-beta-focused-evidence.test.ts", "test/scripts/ci-workflow-guards.test.ts", + "test/scripts/install-smoke-no-push-workflow.test.ts", + "test/scripts/openclaw-cross-os-release-workflow.test.ts", + "test/scripts/openclaw-npm-extended-stable-full-validation-workflow.test.ts", + "test/scripts/openclaw-release-telegram-qa-workflow.test.ts", + "test/scripts/package-source-preflight.test.ts", + "test/scripts/release-ci-summary.test.ts", + "test/scripts/release-no-push-workflow.test.ts", ], ); });