From 3de10373b2d5b62b8c4529e4502a2277e823b8ed Mon Sep 17 00:00:00 2001 From: Dezoff-max <199903504+Dezoff-max@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:57:24 +0500 Subject: [PATCH] Make release publishing idempotent --- .github/workflows/release.yml | 20 +++++++---- contract.md | 65 ++++++++++++++++------------------- progress.md | 10 ++++++ project-analysis.md | 2 +- roadmap.md | 14 ++++++++ trace.md | 7 ++++ 6 files changed, 75 insertions(+), 43 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 602a762..6e77654 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,13 +61,21 @@ jobs: - name: Package Release app run: ./script/package_release.sh - - name: Create GitHub Release + - name: Publish GitHub Release env: GH_TOKEN: ${{ github.token }} run: | tag_name="${GITHUB_REF_NAME:-manual-release}" - gh release create "$tag_name" \ - dist/*.zip \ - dist/*.sha256 \ - --title "CleanMac $tag_name" \ - --notes "Unsigned CleanMac build. Download the zip and verify it with the attached sha256 file." + if gh release view "$tag_name" >/dev/null 2>&1; then + echo "Release $tag_name already exists; replacing matching assets." + gh release upload "$tag_name" \ + dist/*.zip \ + dist/*.sha256 \ + --clobber + else + gh release create "$tag_name" \ + dist/*.zip \ + dist/*.sha256 \ + --title "CleanMac $tag_name" \ + --notes "Unsigned CleanMac build. Download the zip and verify it with the attached sha256 file." + fi diff --git a/contract.md b/contract.md index 24ac1c6..b9b9335 100644 --- a/contract.md +++ b/contract.md @@ -2,59 +2,52 @@ ## Task -- ID: TASK-041 -- Title: CleanMac v0.3.0 release +- ID: TASK-042 +- Title: Idempotent GitHub Release publishing - Mode: continue ## Planner Notes -- Why this task now: the feature branch has a green CI run and contains several user-visible additions since v0.2.1. -- Expected value: a reproducible v0.3.0 package and GitHub release that match the current app and make the new functionality available together. -- Main risk: publishing an artifact from an unmerged or differently versioned commit, attaching stale ZIP files, or implying Apple notarization when only ad-hoc signing is available. -- Release assumption: the new feature set warrants minor version 0.3.0 with build number 4; GitHub release copy remains English. +- Why this task now: the `v0.3.0` tag workflow packaged successfully but failed when it tried to create a release that had already been published manually. +- Expected value: future tag runs can safely create a missing release or refresh matching assets on an existing release without sending a false build-failure notification. +- Main risk: overwriting release notes or introducing a second release while handling the existing-release path. +- Safety choice: preserve existing release title/notes and replace only matching ZIP/checksum assets with `gh release upload --clobber`. ## Builder Scope - Allowed files: - - `CleanMac.xcodeproj/project.pbxproj` - - generated `dist/CleanMac.app`, ZIP, and SHA-256 assets; - - GitHub PR/release metadata; - - Loop documentation files + - `.github/workflows/release.yml`; + - Loop documentation files. - Allowed commands: - - read-only source, Git, bundle, signing, and release inspection; - - `swift test --package-path CleanMacCore`; - - Debug `xcodebuild`; - - `./script/build_and_run.sh --verify`; - - `./script/package_release.sh`; - - PR update/merge and GitHub Release publishing explicitly approved by the user; - - `git diff --check`. + - read-only Actions run/log/release inspection; + - YAML and embedded shell syntax checks; + - execute the approved existing-release branch against `v0.3.0` with the already verified local assets; + - standard Git/PR/CI checks and merge after green status. - Out of scope: - - new product behavior, cleanup execution, permission changes, Developer ID signing, notarization, dependencies, or architecture changes. -- Dependencies allowed: no external dependencies; system CryptoKit only -- Destructive actions allowed: replace generated ignored `dist/` artifacts only; no user-data cleanup + - moving or recreating `v0.3.0`, changing app code/assets, changing release notes, adding secrets, signing, notarization, or deleting Actions history. +- Dependencies allowed: none +- Destructive actions allowed: replace same-named `v0.3.0` release assets only; no user files ## Evaluator Checklist - Done criteria: - - Debug and Release bundles both report version 0.3.0 and build 4. - - The feature PR is green and merged into `main` before tagging. - - The final arm64 ZIP is built from the tagged main commit, passes strict signature verification after fresh extraction, and has a matching SHA-256 file. - - GitHub Release v0.3.0 is public, latest, not prerelease, uses English notes, and contains exactly the verified ZIP and SHA-256 assets. - - The distribution note clearly says the build is ad-hoc signed and not Apple-notarized. + - Missing releases still use `gh release create` with the existing title and notes. + - Existing releases use `gh release upload --clobber` and keep their metadata. + - The exact existing-release shell path succeeds for `v0.3.0` and leaves exactly the verified ZIP and checksum assets. + - Workflow YAML, embedded shell, PR CI, and final GitHub release metadata checks pass. - Required verification: - - `swift test --package-path CleanMacCore`; - - `./script/build_and_run.sh --verify`; - - `./script/package_release.sh`; - - extracted bundle version/build/architecture/signature inspection; - - local and downloaded SHA-256 verification; - - GitHub CI and release metadata inspection; - - `git diff --check`. + - Ruby YAML parse; + - extracted publish-step `bash -n`; + - controlled existing-release execution for `v0.3.0`; + - clean downloaded SHA-256 verification; + - `git diff --check`; + - green GitHub PR checks. - Manual checks: - - Confirm the About metadata and packaged bundle both show 0.3.0 (4). - - Confirm Gatekeeper limitations are documented without recommending security bypasses. + - Confirm `v0.3.0` remains public/latest with its English release notes unchanged. + - Confirm the historical failed run remains only as immutable history and is not presented as a broken app build. ## Result - Status: complete -- Verification result: 40 SwiftPM tests, local Debug build/launch, green PR CI, main merge, Release packaging, strict fresh-extraction signature verification, 0.3.0 (4) metadata, arm64 architecture, portable SHA-256 verification, and downloaded GitHub asset verification all passed. -- Notes: public latest release `v0.3.0` targets merge commit `515f591`, contains the verified ZIP and portable checksum, and is transparently labeled ad-hoc signed and not notarized because no Developer ID identity is installed. +- Verification result: workflow YAML parsed; embedded shell passed `bash -n`; the stubbed missing-release path called `gh release create`; the exact existing-release path successfully refreshed `v0.3.0` assets through `--clobber`; English metadata remained in place; the clean downloaded checksum passed; final diff and PR CI passed. +- Notes: the historical run cannot be made green by rerunning because reruns use the workflow stored at the tagged commit; this task fixes future executions without rewriting the published tag. diff --git a/progress.md b/progress.md index 20709e5..1022074 100644 --- a/progress.md +++ b/progress.md @@ -380,3 +380,13 @@ Append-only history. Do not erase previous entries. - Next step: Install the packaged app in `/Applications`; Developer ID signing and Apple notarization remain the next distribution upgrade when credentials are available. - Bottleneck: This Mac has no Developer ID identity, so Gatekeeper can reject the downloaded ad-hoc build even though its integrity and signature are valid. - Handoff: `dist/` contains the current 0.3.0 app and verified release assets. No security setting, Login Item, privacy permission, cleanup selection, application, or user file was changed. + +## 2026-07-12 - TASK-042 - Idempotent GitHub Release publishing + +- What changed: Replaced the unconditional `gh release create` step with an idempotent publish step. Missing tag releases keep the existing creation behavior; existing releases preserve their title/notes and replace matching ZIP/checksum assets through `gh release upload --clobber`. +- Files touched: `.github/workflows/release.yml`, `contract.md`, `project-analysis.md`, `roadmap.md`, `progress.md`, `trace.md`. +- Checks run: GitHub failure-log inspection; Ruby YAML parse; extracted publish-step `bash -n`; stubbed missing-release branch; exact existing-release branch against `v0.3.0`; clean downloaded `shasum -a 256 -c`; release metadata/asset inspection; `git diff --check`; GitHub PR CI. +- Result: Passed. The original red run was a duplicate-publication error after a successful package, not an app build failure. `v0.3.0` remains public/latest with its English notes and exactly the verified ZIP and portable checksum assets. +- Next step: Use the normal tag workflow for the next version; no manual pre-creation is required, but the workflow now safely tolerates it. +- Bottleneck: The historical `v0.3.0` failure cannot be turned green without rewriting the tag because GitHub reruns the workflow definition stored at that tagged commit. +- Handoff: No release tag, note, app binary, security setting, secret, or user file was changed; only same-named verified release assets were refreshed. diff --git a/project-analysis.md b/project-analysis.md index 92e1b48..72f754f 100644 --- a/project-analysis.md +++ b/project-analysis.md @@ -11,7 +11,7 @@ CleanMac is a macOS menu bar and windowed system cleanup utility. The project wa - `script/build_and_run.sh`: local Debug build and launch entrypoint. - `script/package_release.sh`: local Release `.app`, `.zip`, and `.sha256` packaging. - `.github/workflows/ci.yml`: GitHub CI for core tests, Debug app build, and unsigned Release artifact. -- `.github/workflows/release.yml`: tag-driven GitHub Release workflow for unsigned/ad-hoc zip and sha256 assets, with optional Developer ID signing/notarization when private secrets are configured. +- `.github/workflows/release.yml`: idempotent tag-driven GitHub Release workflow for unsigned/ad-hoc zip and sha256 assets, with optional Developer ID signing/notarization when private secrets are configured; it creates a missing release or replaces matching assets on an existing release without overwriting its notes. - `docs/` and `Design/`: icon and project assets. ## Tech Stack diff --git a/roadmap.md b/roadmap.md index a11b44c..0e2acbb 100644 --- a/roadmap.md +++ b/roadmap.md @@ -1,5 +1,19 @@ # Roadmap +- [x] ID: TASK-042 + Title: Idempotent GitHub Release publishing + Goal: Prevent a successful tag package from reporting failure when the corresponding GitHub Release already exists. + What to do: Detect the release first, create it only when missing, otherwise replace same-named assets with `gh release upload --clobber` while preserving notes. + Files: `.github/workflows/release.yml`, Loop docs + Definition of done: missing and existing release paths are explicit; the existing v0.3.0 path succeeds without duplicate releases or metadata loss; PR CI is green. + Verification: YAML parse; embedded shell syntax; controlled v0.3.0 asset refresh; clean downloaded checksum verification; GitHub PR checks + Priority: high + Impact: medium + Risk: low + Effort: small + Confidence: high + Score: medium impact / low risk / small + - [x] ID: TASK-041 Title: CleanMac v0.3.0 release Goal: Ship the verified disk analysis, onboarding, system dashboard, developer cleanup, and duplicate finder as the next feature release. diff --git a/trace.md b/trace.md index 420c1bb..5bb00c4 100644 --- a/trace.md +++ b/trace.md @@ -113,3 +113,10 @@ Append-only trace of failures, restarts, and judgment divergences. - Cause: `package_release.sh` passed the absolute `ZIP_PATH` directly to `shasum`. - Fix: generate the checksum from inside `dist` using only the ZIP basename, replace the GitHub checksum asset, and repeat verification from a clean temporary download directory. - Status: resolved; the published checksum now references `CleanMac-515f591-unsigned.zip`, and clean-directory verification checks the downloaded ZIP successfully. + +## 2026-07-12 - TASK-042 - Existing release made tag workflow fail + +- Symptom: the `v0.3.0` Release workflow packaged the app successfully but the final job failed with `a release with the same tag name already exists: v0.3.0`. +- Cause: the workflow always called `gh release create`, while `v0.3.0` had already been published and verified through the approved local release flow. +- Fix: query the tag first; create only when missing, otherwise upload the same ZIP/checksum names with `--clobber` while preserving the current title and English notes. +- Status: resolved for future executions; the exact existing-release branch refreshed both `v0.3.0` assets successfully and clean-download SHA-256 verification passed. The historical failed run remains unchanged because reruns use the workflow stored at the tagged commit.