From 9d2c49f3e5e260146f5a7e2f4153fe2c5e8cf84f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 2 Aug 2026 00:49:58 +0100 Subject: [PATCH] Make private Pro publication reruns recover promotion-only failures The paid-runtime R2 prefix was derived from the wall-clock date, and the enterprise build was always dispatched as a fresh rebuild. Rerunning the failed publish job after a transient promotion failure therefore hit the enterprise R2 overwrite guard (same run id, existing prefix) and the Definitive Release Verdict could never go green by rerun, as on v6.2.0-rc.6. The prefix date now comes from the run's createdAt, which is stable across rerun attempts, and the build dispatch passes reuse_existing_packet=true so pulse-enterprise validates the packet the earlier attempt uploaded, skips the rebuild, and only the promotion re-executes against it. The deployment-installability contract now records rerun recoverability as part of the promotion boundary. --- .github/workflows/create-release.yml | 24 +++++++++++++++++-- .../subsystems/deployment-installability.md | 10 ++++++++ .../installtests/build_release_assets_test.go | 4 +++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 4fc63138b..1a2b12bac 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -1390,7 +1390,11 @@ jobs: # Docker publication workflow to pass, then dispatches the pulse-pro live # promotion workflow and waits for the signed packet to update the license # broker. A failure here fails the release pipeline instead of leaving paid - # customers on a stale private manifest. + # customers on a stale private manifest. Rerunning this job after a + # promotion-only failure is safe: the R2 prefix is derived from run-stable + # values and the build is dispatched with reuse_existing_packet=true, so the + # enterprise side validates the packet the earlier attempt uploaded, skips + # the rebuild, and only the promotion re-executes. publish_private_pro_runtime: needs: - prepare @@ -1475,7 +1479,22 @@ jobs: allow_ga_publish=true fi - r2_prefix="${TAG}-pro-$(date -u '+%Y%m%d')-${GITHUB_RUN_ID}" + # The R2 prefix must be identical across rerun attempts of this run: + # a rerun after a promotion-only failure has to reuse the packet the + # earlier attempt already uploaded instead of tripping the enterprise + # R2 overwrite guard. Run creation date and run id are stable across + # attempts; wall-clock date is not. + run_created_date="$( + gh run view "${GITHUB_RUN_ID}" \ + --repo "${GITHUB_REPOSITORY}" \ + --json createdAt \ + --jq '.createdAt' | cut -c1-10 | tr -d '-' + )" + if [[ ! "${run_created_date}" =~ ^[0-9]{8}$ ]]; then + echo "::error::Could not derive the release run creation date for the R2 prefix." + exit 1 + fi + r2_prefix="${TAG}-pro-${run_created_date}-${GITHUB_RUN_ID}" build_started_at="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" echo "Dispatching private Pro build for ${TAG} with R2 prefix ${r2_prefix}." gh workflow run build-pro-release.yml \ @@ -1488,6 +1507,7 @@ jobs: -f publish_docker_image=true \ -f docker_image=license.pulserelay.pro/pulse-pro \ -f r2_prefix="${r2_prefix}" \ + -f reuse_existing_packet=true \ -f allow_stable_ga_publish="${allow_ga_publish}" wait_for_workflow rcourtman/pulse-enterprise "Build Pro Release" main "${build_started_at}" "private Pro build" 7200 diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index b5b9f486c..ff3f2e26c 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -861,6 +861,16 @@ upgrade, update, release, or artifact-selection behavior. fail the public release workflow; future private Pro publication must not depend on an operator noticing a manual checklist step after the public RC has shipped. + A promotion-only failure must be recoverable by rerunning the public + release run's failed jobs: the paid-runtime R2 prefix is derived from + run-stable values (the run's creation date and run id, never the + wall-clock date at attempt time), and the private build dispatch sets + `reuse_existing_packet=true` so `Build Pro Release` validates a complete + signed packet already present at that prefix, skips the rebuild, and lets + the promotion re-execute against the packet the earlier attempt uploaded. + A rebuilt packet from identical inputs is waste and lineage churn; a + non-empty prefix that fails packet validation must fail the private build + instead of being overwritten. A support-only private Pro prerelease image is a narrower exception for customer verification of an already-fixed defect. It may dispatch the private `Build Pro Release` workflow with `publish_docker_image=true`, diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index aff33e96c..a88d5eec2 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -1904,7 +1904,8 @@ func TestCreateReleasePublishesPrivateProRuntime(t *testing.T) { `github.event.inputs.draft_only != 'true'`, `startsWith(needs.prepare.outputs.version, '6.')`, `GH_TOKEN: ${{ secrets.WORKFLOW_PAT }}`, - `r2_prefix="${TAG}-pro-$(date -u '+%Y%m%d')-${GITHUB_RUN_ID}"`, + `--json createdAt`, + `r2_prefix="${TAG}-pro-${run_created_date}-${GITHUB_RUN_ID}"`, `gh workflow run build-pro-release.yml`, `--repo rcourtman/pulse-enterprise`, `-f pulse_ref="${TAG}"`, @@ -1914,6 +1915,7 @@ func TestCreateReleasePublishesPrivateProRuntime(t *testing.T) { `-f publish_docker_image=true`, `-f docker_image=license.pulserelay.pro/pulse-pro`, `-f r2_prefix="${r2_prefix}"`, + `-f reuse_existing_packet=true`, `-f allow_stable_ga_publish="${allow_ga_publish}"`, `wait_for_workflow rcourtman/pulse-enterprise "Build Pro Release" main "${build_started_at}" "private Pro build"`, `gh workflow run promote-paid-runtime-release.yml`,