Fix release asset validation workflow gates

This commit is contained in:
rcourtman 2026-05-02 00:36:54 +01:00
parent c8e24f06d7
commit 011d288cb4
6 changed files with 60 additions and 6 deletions

View file

@ -773,6 +773,21 @@ jobs:
RELEASE_URL=$(echo "$RELEASE_JSON" | jq -r '.html_url')
fi
RELEASE_JSON=$(gh api "repos/${{ github.repository }}/releases/${RELEASE_ID}")
ACTUAL_RELEASE_TAG=$(echo "$RELEASE_JSON" | jq -r '.tag_name // empty')
ACTUAL_TARGET_COMMITISH=$(echo "$RELEASE_JSON" | jq -r '.target_commitish // empty')
RELEASE_URL=$(echo "$RELEASE_JSON" | jq -r '.html_url')
if [ "$ACTUAL_RELEASE_TAG" != "$TAG" ]; then
echo "::error::Draft release ${RELEASE_ID} is bound to tag ${ACTUAL_RELEASE_TAG}, expected ${TAG}."
exit 1
fi
if [ "$ACTUAL_TARGET_COMMITISH" != "$HEAD_SHA" ]; then
echo "::error::Draft release ${RELEASE_ID} target_commitish is ${ACTUAL_TARGET_COMMITISH}, expected ${HEAD_SHA}."
exit 1
fi
rm -f "$NOTES_FILE"
echo "release_url=${RELEASE_URL}" >> $GITHUB_OUTPUT
@ -939,10 +954,11 @@ jobs:
needs:
- prepare
- create_release
if: ${{ needs.prepare.outputs.historical_asset_backfill_only != 'true' }}
if: ${{ always() && needs.prepare.result == 'success' && needs.create_release.result == 'success' && needs.prepare.outputs.historical_asset_backfill_only != 'true' }}
permissions:
contents: write
issues: write
statuses: write
uses: ./.github/workflows/validate-release-assets.yml
secrets: inherit
with:

View file

@ -57,6 +57,7 @@ jobs:
permissions:
contents: write
issues: write
statuses: write
steps:
- name: Checkout repository
@ -242,7 +243,7 @@ jobs:
- name: Set commit status - Success
if: steps.context.outputs.should_run == 'true' && steps.validate.outputs.validation_passed == 'true'
run: |
curl -X POST \
curl --fail-with-body --silent --show-error -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/statuses/${{ steps.context.outputs.target_commitish }}" \
@ -313,7 +314,7 @@ jobs:
- name: Set commit status - Failure
if: steps.context.outputs.should_run == 'true' && (failure() || steps.validate.outputs.validation_passed == 'false')
run: |
curl -X POST \
curl --fail-with-body --silent --show-error -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/statuses/${{ steps.context.outputs.target_commitish }}" \

View file

@ -36,6 +36,7 @@ server-side update execution surfaces.
13. `.github/workflows/publish-helm-chart.yml`
14. `.github/workflows/release-dry-run.yml`
15. `.github/workflows/update-demo-server.yml`
16. `.github/workflows/validate-release-assets.yml`
16. `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`
17. `docs/RELEASE_NOTES.md`
18. `docs/releases/`
@ -113,7 +114,7 @@ server-side update execution surfaces.
## Extension Points
1. Add or change deployment-type detection, update planning, or apply behavior through `internal/updates/`
2. Add or change release-build metadata injection, Docker build-context allowlists, release artifact assembly, governed promotion metadata resolution, the canonical version file, operator-facing release packet content, prerelease feedback intake wording, historical published-release integrity backfill, download endpoint checksum/signature header proof, or the canonical in-repo v6 upgrade guide through `scripts/build-release.sh`, `scripts/release_asset_common.sh`, `scripts/backfill-release-assets.sh`, `scripts/release_ldflags.sh`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/release_control/render_release_body.py`, `scripts/release_control/resolve_release_promotion.py`, `scripts/release_control/record_rc_to_ga_rehearsal.py`, `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`, `scripts/release_control/release_promotion_policy_support.py`, `.dockerignore`, `Dockerfile`, `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`, `docs/RELEASE_NOTES.md`, `docs/releases/`, `docs/UPGRADE_v6.md`, `docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md`, `docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md`, `docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md`, `scripts/validate-release.sh`, `scripts/validate-published-release.sh`, the operator dispatch helpers `scripts/trigger-release.sh` and `scripts/trigger-release-dry-run.sh`, and the governed release workflows `.github/workflows/backfill-release-assets.yml`, `.github/workflows/create-release.yml`, `.github/workflows/deploy-demo-server.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, and `.github/workflows/update-demo-server.yml`
2. Add or change release-build metadata injection, Docker build-context allowlists, release artifact assembly, governed promotion metadata resolution, the canonical version file, operator-facing release packet content, prerelease feedback intake wording, historical published-release integrity backfill, release asset validation status publication, download endpoint checksum/signature header proof, or the canonical in-repo v6 upgrade guide through `scripts/build-release.sh`, `scripts/release_asset_common.sh`, `scripts/backfill-release-assets.sh`, `scripts/release_ldflags.sh`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/release_control/render_release_body.py`, `scripts/release_control/resolve_release_promotion.py`, `scripts/release_control/record_rc_to_ga_rehearsal.py`, `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`, `scripts/release_control/release_promotion_policy_support.py`, `.dockerignore`, `Dockerfile`, `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`, `docs/RELEASE_NOTES.md`, `docs/releases/`, `docs/UPGRADE_v6.md`, `docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md`, `docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md`, `docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md`, `scripts/validate-release.sh`, `scripts/validate-published-release.sh`, the operator dispatch helpers `scripts/trigger-release.sh` and `scripts/trigger-release-dry-run.sh`, and the governed release workflows `.github/workflows/backfill-release-assets.yml`, `.github/workflows/create-release.yml`, `.github/workflows/deploy-demo-server.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, `.github/workflows/update-demo-server.yml`, and `.github/workflows/validate-release-assets.yml`
3. Add or change shell installer, Docker bootstrap installer, Windows installer, container-agent installer, repo-root compose defaults, or auto-update script behavior through `scripts/install.sh`, `scripts/install-docker.sh`, `scripts/install.ps1`, `scripts/install-container-agent.sh`, `docker-compose.yml`, and `scripts/pulse-auto-update.sh`
4. Add or change server update transport through `internal/api/updates.go` and `frontend-modern/src/api/updates.ts`
5. Add or change local dev-runtime orchestration, managed ownership, browser-runtime proof wiring, frontend/backend coherence diagnostics, canonical developer entry wrappers, dependency manifest floors, frontend build chunking, or dev-runtime helper control surfaces through `scripts/hot-dev.sh`, `scripts/hot-dev-bg.sh`, `scripts/dev-deploy-agent.sh`, `Makefile`, `package.json`, `package-lock.json`, `frontend-modern/package.json`, `frontend-modern/package-lock.json`, `frontend-modern/vite.config.ts`, `go.mod`, `go.sum`, `scripts/dev-check.sh`, `scripts/toggle-mock.sh`, `scripts/clean-mock-alerts.sh`, `scripts/dev-launchd-setup.sh`, `scripts/dev-launchd-wrapper.sh`, `scripts/run_demo_public_browser_smoke.sh`, `scripts/demo_public_browser_smoke.cjs`, `scripts/com.pulse.hot-dev.plist.template`, `tests/integration/scripts/managed-dev-runtime.mjs`, `tests/integration/playwright.config.ts`, `tests/integration/tests/helpers.ts`, `tests/integration/tests/runtime-defaults.ts`, `tests/integration/README.md`, and `tests/integration/QUICK_START.md`

View file

@ -2655,6 +2655,7 @@
".github/workflows/publish-helm-chart.yml",
".github/workflows/release-dry-run.yml",
".github/workflows/update-demo-server.yml",
".github/workflows/validate-release-assets.yml",
"cmd/pulse-control-plane/main.go",
"docker-compose.yml",
"Dockerfile",
@ -2784,6 +2785,7 @@
".github/workflows/publish-helm-chart.yml",
".github/workflows/release-dry-run.yml",
".github/workflows/update-demo-server.yml",
".github/workflows/validate-release-assets.yml",
"docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md",
"docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md",
"docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md",

View file

@ -100,8 +100,13 @@ func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
if err != nil {
t.Fatalf("read create-release.yml: %v", err)
}
validationContent, err := os.ReadFile(repoFile(".github", "workflows", "validate-release-assets.yml"))
if err != nil {
t.Fatalf("read validate-release-assets.yml: %v", err)
}
workflow := string(content)
validationWorkflow := string(validationContent)
required := []string{
`historical_asset_backfill_only:`,
`description: 'Repair an already-published release packet in place without rebuilding binaries'`,
@ -131,10 +136,15 @@ func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
`git push origin "refs/tags/${TAG}" --force`,
`-F target_commitish="${HEAD_SHA}"`,
`historical_asset_backfill_only=${HISTORICAL_ASSET_BACKFILL_ONLY}`,
`if: ${{ needs.prepare.outputs.historical_asset_backfill_only != 'true' }}`,
`if: ${{ always() && needs.prepare.result == 'success' && needs.create_release.result == 'success' && needs.prepare.outputs.historical_asset_backfill_only != 'true' }}`,
`if: ${{ needs.prepare.outputs.historical_asset_backfill_only == 'true' }}`,
`permissions:`,
`issues: write`,
`statuses: write`,
`ACTUAL_RELEASE_TAG=$(echo "$RELEASE_JSON" | jq -r '.tag_name // empty')`,
`ACTUAL_TARGET_COMMITISH=$(echo "$RELEASE_JSON" | jq -r '.target_commitish // empty')`,
`Draft release ${RELEASE_ID} is bound to tag ${ACTUAL_RELEASE_TAG}, expected ${TAG}.`,
`Draft release ${RELEASE_ID} target_commitish is ${ACTUAL_TARGET_COMMITISH}, expected ${HEAD_SHA}.`,
`./scripts/backfill-release-assets.sh --tag "${{ needs.prepare.outputs.tag }}" --repo "${{ github.repository }}"`,
`./scripts/validate-published-release.sh "${{ needs.prepare.outputs.tag }}" "${{ github.repository }}"`,
}
@ -150,6 +160,17 @@ func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
if strings.Contains(workflow, `provenance: false`) {
t.Fatal("create-release.yml must not disable release-image provenance")
}
validationRequired := []string{
`statuses: write`,
`curl --fail-with-body --silent --show-error -X POST`,
`"context": "Release Asset Validation"`,
}
for _, needle := range validationRequired {
if !strings.Contains(validationWorkflow, needle) {
t.Fatalf("validate-release-assets.yml missing required status publication contract: %s", needle)
}
}
}
func TestBackfillReleaseWorkflowRepairsPublishedAssetsWithoutRebuilds(t *testing.T) {

View file

@ -327,6 +327,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
def test_release_workflow_enforces_rc_lineage_soak_and_v5_notice(self) -> None:
content = read(".github/workflows/create-release.yml")
validation_workflow = read(".github/workflows/validate-release-assets.yml")
helper = read("scripts/trigger-release.sh")
renderer = read("scripts/release_control/render_release_body.py")
policy = read("docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md")
@ -373,9 +374,21 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
self.assertIn('Retargeting existing draft tag ${TAG}', content)
self.assertIn('-F target_commitish="${HEAD_SHA}"', content)
self.assertIn('historical_asset_backfill_only=${HISTORICAL_ASSET_BACKFILL_ONLY}', content)
self.assertIn("if: ${{ needs.prepare.outputs.historical_asset_backfill_only != 'true' }}", content)
self.assertIn(
"if: ${{ always() && needs.prepare.result == 'success' && needs.create_release.result == 'success' && needs.prepare.outputs.historical_asset_backfill_only != 'true' }}",
content,
)
self.assertIn("if: ${{ needs.prepare.outputs.historical_asset_backfill_only == 'true' }}", content)
self.assertIn("issues: write", content)
self.assertIn("statuses: write", content)
self.assertIn("statuses: write", validation_workflow)
self.assertIn("curl --fail-with-body --silent --show-error -X POST", validation_workflow)
self.assertIn('"context": "Release Asset Validation"', validation_workflow)
self.assertIn('ACTUAL_RELEASE_TAG=$(echo "$RELEASE_JSON" | jq -r \'.tag_name // empty\')', content)
self.assertIn(
'ACTUAL_TARGET_COMMITISH=$(echo "$RELEASE_JSON" | jq -r \'.target_commitish // empty\')',
content,
)
self.assertIn('./scripts/backfill-release-assets.sh --tag "${{ needs.prepare.outputs.tag }}" --repo "${{ github.repository }}"', content)
self.assertIn('./scripts/validate-published-release.sh "${{ needs.prepare.outputs.tag }}" "${{ github.repository }}"', content)
self.assertIn("PULSE_UPDATE_SIGNING_KEY: ${{ secrets.PULSE_UPDATE_SIGNING_KEY }}", content)