fix(ci): trust frozen plugin prerelease fixtures (#124446)

* fix(ci): trust frozen plugin prerelease fixtures

* fix(ci): apply scoped plugin test omissions
This commit is contained in:
Peter Steinberger
2026-08-15 23:47:25 -07:00
committed by GitHub
parent 7d05f7c5b9
commit 255ca8d96b
4 changed files with 331 additions and 9 deletions
+22 -3
View File
@@ -84,6 +84,11 @@ on:
- qa-live
- npm-telegram
- performance
plugin_prerelease_node_exclude_patterns_json:
description: Exact Plugin Prerelease Node test paths omitted only for frozen-target validation
required: false
default: "[]"
type: string
reuse_evidence:
description: Reuse matching green product validation for the same target or a changelog-only Release SHA
required: false
@@ -216,7 +221,9 @@ jobs:
RERUN_GROUP: ${{ inputs.rerun_group }}
LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
CROSS_OS_SUITE_FILTER: ${{ inputs.cross_os_suite_filter }}
PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON: ${{ inputs.plugin_prerelease_node_exclude_patterns_json }}
run: |
plugin_prerelease_node_exclusions="$(jq -c . <<< "$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON")"
{
echo "## Full release validation"
echo
@@ -246,6 +253,7 @@ jobs:
fi
if [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "plugin-prerelease" ]]; then
echo "- Plugin prerelease: \`Plugin Prerelease\` with \`target_ref=${TARGET_SHA}\`"
echo "- Plugin prerelease Node exclusions: \`${plugin_prerelease_node_exclusions}\`"
else
echo "- Plugin prerelease: skipped by rerun group"
fi
@@ -342,6 +350,7 @@ jobs:
NPM_TELEGRAM_SCENARIO: ${{ inputs.npm_telegram_scenario }}
SKIP_PACKAGE_TELEGRAM_E2E: ${{ inputs.skip_package_telegram_e2e }}
ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog || (inputs.target_context_ref == '' && (inputs.ref == 'main' || inputs.ref == 'refs/heads/main')) }}
PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON: ${{ inputs.plugin_prerelease_node_exclude_patterns_json }}
run: |
set -euo pipefail
# Lane-selection inputs must match the prior run's manifest exactly;
@@ -360,6 +369,7 @@ jobs:
--arg npmTelegramScenario "$NPM_TELEGRAM_SCENARIO" \
--arg skipPackageTelegramE2e "$SKIP_PACKAGE_TELEGRAM_E2E" \
--arg allowUnreleasedChangelog "$ALLOW_UNRELEASED_CHANGELOG" \
--arg pluginPrereleaseNodeExcludePatternsJson "$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON" \
'{
provider: $provider,
mode: $mode,
@@ -373,7 +383,8 @@ jobs:
npmTelegramProviderMode: $npmTelegramProviderMode,
npmTelegramScenario: $npmTelegramScenario,
skipPackageTelegramE2e: $skipPackageTelegramE2e,
allowUnreleasedChangelog: $allowUnreleasedChangelog
allowUnreleasedChangelog: $allowUnreleasedChangelog,
pluginPrereleaseNodeExcludePatternsJson: $pluginPrereleaseNodeExcludePatternsJson
}')"
bash workflow/scripts/github/find-reusable-release-validation.sh \
--target-sha "$TARGET_SHA" \
@@ -848,15 +859,19 @@ jobs:
dispatch_and_wait ci.yml "$dispatch_run_name" "${args[@]}"
;;
plugin-prerelease)
plugin_prerelease_node_exclusions="$(
jq -c . <<< "$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON"
)"
{
echo "### Plugin prerelease"
echo
echo "- Target ref: \`${TARGET_REF}\`"
echo "- Target SHA: \`${TARGET_SHA}\`"
echo "- Frozen-target Node test omissions: \`${plugin_prerelease_node_exclusions}\`"
} >> "$GITHUB_STEP_SUMMARY"
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-plugin-prerelease"
dispatch_run_name="Plugin Prerelease ${dispatch_id}"
args=(-f target_ref="$TARGET_SHA" -f expected_sha="$TARGET_SHA" -f full_release_validation=true -f dispatch_id="$dispatch_id")
args=(-f target_ref="$TARGET_SHA" -f expected_sha="$TARGET_SHA" -f full_release_validation=true -f dispatch_id="$dispatch_id" -f node_test_exclude_patterns_json="$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON")
if [[ -n "${CANDIDATE_ARTIFACT_JSON// }" ]]; then
args+=(-f candidate_artifact_json="$CANDIDATE_ARTIFACT_JSON")
fi
@@ -1006,6 +1021,7 @@ jobs:
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
PARENT_WORKFLOW_SHA: ${{ github.sha }}
CANDIDATE_ARTIFACT_JSON: ${{ needs.prepare_release_candidate.outputs.candidate_artifact_json }}
PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON: ${{ inputs.plugin_prerelease_node_exclude_patterns_json }}
FAIL_FAST: ${{ inputs.fail_fast }}
run: *full_release_child_dispatch
@@ -1667,6 +1683,7 @@ jobs:
NPM_TELEGRAM_SCENARIO: ${{ inputs.npm_telegram_scenario }}
SKIP_PACKAGE_TELEGRAM_E2E: ${{ inputs.skip_package_telegram_e2e }}
ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog || (inputs.target_context_ref == '' && (inputs.ref == 'main' || inputs.ref == 'refs/heads/main')) }}
PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON: ${{ inputs.plugin_prerelease_node_exclude_patterns_json }}
run: |
set -euo pipefail
manifest_dir="${RUNNER_TEMP}/full-release-validation"
@@ -1743,6 +1760,7 @@ jobs:
--arg npmTelegramScenario "$NPM_TELEGRAM_SCENARIO" \
--arg skipPackageTelegramE2e "$SKIP_PACKAGE_TELEGRAM_E2E" \
--arg allowUnreleasedChangelog "$ALLOW_UNRELEASED_CHANGELOG" \
--arg pluginPrereleaseNodeExcludePatternsJson "$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON" \
'{
version: 3,
workflowName: $workflowName,
@@ -1770,7 +1788,8 @@ jobs:
npmTelegramProviderMode: $npmTelegramProviderMode,
npmTelegramScenario: $npmTelegramScenario,
skipPackageTelegramE2e: $skipPackageTelegramE2e,
allowUnreleasedChangelog: $allowUnreleasedChangelog
allowUnreleasedChangelog: $allowUnreleasedChangelog,
pluginPrereleaseNodeExcludePatternsJson: $pluginPrereleaseNodeExcludePatternsJson
},
controls: {
stableSoakRequired: ($releaseProfile == "stable" or $releaseProfile == "full"),
+108 -1
View File
@@ -20,6 +20,11 @@ on:
required: false
default: false
type: boolean
node_test_exclude_patterns_json:
description: Full Release Validation-only exact plugin test paths omitted for a frozen target
required: false
default: "[]"
type: string
dispatch_id:
description: Optional parent workflow dispatch identifier
required: false
@@ -53,11 +58,73 @@ jobs:
plugin_prerelease_static_matrix: ${{ steps.manifest.outputs.plugin_prerelease_static_matrix }}
run_plugin_prerelease_node: ${{ steps.manifest.outputs.run_plugin_prerelease_node }}
plugin_prerelease_node_matrix: ${{ steps.manifest.outputs.plugin_prerelease_node_matrix }}
node_test_exclude_patterns_json: ${{ steps.node_test_exclusions.outputs.patterns_json }}
run_plugin_prerelease_extensions: ${{ steps.manifest.outputs.run_plugin_prerelease_extensions }}
plugin_prerelease_extension_matrix: ${{ steps.manifest.outputs.plugin_prerelease_extension_matrix }}
run_plugin_prerelease_docker: ${{ steps.manifest.outputs.run_plugin_prerelease_docker }}
plugin_prerelease_docker_lanes: ${{ steps.manifest.outputs.plugin_prerelease_docker_lanes }}
steps:
- name: Validate frozen-target Node exclusions
id: node_test_exclusions
env:
FULL_RELEASE_VALIDATION: ${{ inputs.full_release_validation && 'true' || 'false' }}
NODE_TEST_EXCLUDE_PATTERNS_JSON: ${{ inputs.node_test_exclude_patterns_json }}
shell: bash
run: |
set -euo pipefail
node --input-type=module <<'EOF'
import { appendFileSync } from "node:fs";
const rawPatterns = process.env.NODE_TEST_EXCLUDE_PATTERNS_JSON ?? "";
let patterns;
try {
patterns = JSON.parse(rawPatterns);
} catch (error) {
console.error(`node_test_exclude_patterns_json must be valid JSON: ${error.message}`);
process.exit(1);
}
if (!Array.isArray(patterns)) {
console.error("node_test_exclude_patterns_json must be a JSON array");
process.exit(1);
}
const invalidPattern = patterns.find((pattern) => {
if (typeof pattern !== "string" || pattern.length === 0 || pattern !== pattern.trim()) {
return true;
}
const segments = pattern.split("/");
return (
!pattern.startsWith("src/plugins/") ||
!pattern.endsWith(".test.ts") ||
pattern.includes("\\") ||
pattern.includes("..") ||
segments.some((segment) => segment.length === 0 || segment === ".") ||
!/^[A-Za-z0-9._/-]+$/u.test(pattern)
);
});
if (invalidPattern !== undefined) {
console.error(
`Invalid frozen-target Node test omission: ${JSON.stringify(invalidPattern)}`,
);
process.exit(1);
}
if (new Set(patterns).size !== patterns.length) {
console.error("node_test_exclude_patterns_json must not contain duplicate paths");
process.exit(1);
}
const fullReleaseValidation = process.env.FULL_RELEASE_VALIDATION === "true";
if (!fullReleaseValidation && rawPatterns !== "[]") {
console.error(
"node_test_exclude_patterns_json may differ from [] only when full_release_validation=true",
);
process.exit(1);
}
appendFileSync(
process.env.GITHUB_OUTPUT,
`patterns_json=${JSON.stringify(patterns)}\n`,
"utf8",
);
EOF
- name: Checkout target
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
@@ -305,6 +372,32 @@ jobs:
persist-credentials: false
submodules: false
- name: Checkout trusted npm security inventory
if: inputs.full_release_validation
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ github.sha }}
path: .plugin-prerelease-trusted
fetch-depth: 1
fetch-tags: false
persist-credentials: false
sparse-checkout: src/plugins/npm-install-security-scan.release.test.ts
sparse-checkout-cone-mode: false
submodules: false
- name: Install trusted npm security inventory
if: inputs.full_release_validation
shell: bash
run: |
set -euo pipefail
trusted_checkout=.plugin-prerelease-trusted
trap 'rm -rf -- "$trusted_checkout"' EXIT
install -m 0644 \
"$trusted_checkout/src/plugins/npm-install-security-scan.release.test.ts" \
src/plugins/npm-install-security-scan.release.test.ts
rm -rf -- "$trusted_checkout"
trap - EXIT
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
@@ -316,6 +409,7 @@ jobs:
- name: Run release-only plugin Node shard
env:
NODE_OPTIONS: --max-old-space-size=8192
NODE_TEST_EXCLUDE_PATTERNS_JSON: ${{ needs.preflight.outputs.node_test_exclude_patterns_json }}
OPENCLAW_NODE_TEST_CONFIGS_JSON: ${{ toJson(matrix.configs) }}
OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON: ${{ toJson(matrix.includePatterns) }}
OPENCLAW_VITEST_SHARD_NAME: ${{ matrix.shard_name }}
@@ -336,6 +430,16 @@ jobs:
const includePatterns = JSON.parse(
process.env.OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON ?? "null",
);
const excludePatterns = JSON.parse(
process.env.NODE_TEST_EXCLUDE_PATTERNS_JSON ?? "[]",
);
if (
!Array.isArray(excludePatterns) ||
excludePatterns.some((pattern) => typeof pattern !== "string")
) {
console.error("Invalid frozen-target Node exclusions");
process.exit(1);
}
const childEnv = { ...process.env };
if (Array.isArray(includePatterns) && includePatterns.length > 0) {
const includeFile = join(
@@ -346,7 +450,10 @@ jobs:
childEnv.OPENCLAW_VITEST_INCLUDE_FILE = includeFile;
}
const result = spawnSync("pnpm", ["test", "--", ...configs], {
const excludeArgs = excludePatterns.map(
(pattern) => `--exclude=${pattern.slice("src/plugins/".length)}`,
);
const result = spawnSync("pnpm", ["test", "--", ...configs, "--", ...excludeArgs], {
env: childEnv,
stdio: "inherit",
});