diff --git a/.github/workflows/openclaw-stable-main-closeout.yml b/.github/workflows/openclaw-stable-main-closeout.yml index cd0d154cc5fe..2951c3e8943b 100644 --- a/.github/workflows/openclaw-stable-main-closeout.yml +++ b/.github/workflows/openclaw-stable-main-closeout.yml @@ -244,6 +244,11 @@ jobs: exit 1 fi if [[ -z "$ROLLBACK_DRILL_ID" || -z "$ROLLBACK_DRILL_DATE" ]]; then + if [[ "$EVENT_NAME" == "push" ]]; then + echo "::warning::Stable closeout skipped: rollback drill repository variables are missing; manual dispatch remains required to complete closeout." + echo "should_closeout=false" >> "$GITHUB_OUTPUT" + exit 0 + fi echo "Stable closeout requires repository variables RELEASE_ROLLBACK_DRILL_ID and RELEASE_ROLLBACK_DRILL_DATE, or explicit manual overrides." >&2 exit 1 fi diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index 9769ae9e98b6..7f1a08a25819 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -191,10 +191,11 @@ release state. closeout requires both assets and a matching checksum. A partial manifest replays its recorded `main` SHA and rollback drill to regenerate identical bytes, then attaches the missing checksum; an invalid pair, or a checksum - without a manifest, stays blocking. A missing or more-than-90-day-old drill - record blocks a new evidence-backed closeout; private recovery commands - remain in the maintainer-only runbook. Use manual dispatch only to repair or - replay an evidence-backed stable closeout. + without a manifest, stays blocking. A push-triggered run without rollback + drill repository variables skips without completing closeout; a missing or + more-than-90-day-old drill record still blocks manual evidence-backed + closeout. Private recovery commands remain in the maintainer-only runbook. + Use manual dispatch only to repair or replay an evidence-backed stable closeout. A legacy fallback correction tag may reuse base-package evidence only when the correction tag resolves to the same source commit as the base stable tag. A correction with different source must publish and verify its own package diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 89684e74097a..5cbdcac7121a 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -120,6 +120,12 @@ describe("package acceptance workflow", () => { const existingCloseoutEvidenceMatchIndex = workflow.indexOf( 'if [[ -n "$existing_closeout_full_release_validation_run_id" &&', ); + const rollbackDrillGateIndex = workflow.indexOf( + 'if [[ -z "$ROLLBACK_DRILL_ID" || -z "$ROLLBACK_DRILL_DATE" ]]; then', + ); + const rollbackDrillPushSkipIndex = workflow.indexOf( + "Stable closeout skipped: rollback drill repository variables are missing", + ); expect(workflow).toContain('evidence_checksum_asset="${evidence_asset}.sha256"'); expect(workflow).toContain('--pattern "$evidence_checksum_asset"'); @@ -168,6 +174,9 @@ describe("package acceptance workflow", () => { expect(workflow).toContain( "Stable closeout manifest for $tag does not match immutable postpublish evidence; refusing to accept it.", ); + expect(workflow).toContain( + "Stable closeout requires repository variables RELEASE_ROLLBACK_DRILL_ID and RELEASE_ROLLBACK_DRILL_DATE, or explicit manual overrides.", + ); expect(workflow).toContain( "REPAIR_PARTIAL_CLOSEOUT: ${{ needs.resolve.outputs.repair_partial_closeout }}", ); @@ -186,6 +195,8 @@ describe("package acceptance workflow", () => { expect(partialRepairIndex).toBeGreaterThan(-1); expect(partialRepairIndex).toBeLessThan(releaseVersionGateIndex); expect(evidenceDownloadIndex).toBeGreaterThan(releaseVersionGateIndex); + expect(rollbackDrillGateIndex).toBeGreaterThan(existingCloseoutEvidenceMatchIndex); + expect(rollbackDrillPushSkipIndex).toBeGreaterThan(rollbackDrillGateIndex); }); it("keeps pnpm version selection sourced from packageManager", () => {