diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 47ca669f..2b5d87ff 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3245,23 +3245,58 @@ jobs: # upload on purpose: a failed upload must not leave the release emptier than it was. And only # here: a versioned v* release is history, github-release never deletes anything. Every # deletion is printed by name, so the job log is the audit trail. + # + # Asset by asset, never the release. The first form of this job (2026-05-07) deleted the whole + # `snapshot` release with its tag and recreated it; on 2026-05-10 the runs of several main + # pushes minutes apart deleted it under each other, and every job of each burst but the last + # failed in that step with its artifacts present. The job then went with the Central + # migration and came back as `view || create` + `upload --clobber` without any cleanup, which + # is how the backlog grew (workspace/policies/fat-jar-release-assets.md has the runs). Hence + # three guards: an empty upload is refused (every asset would count as stale; the download + # and upload steps above fail first, the guard keeps it so), an asset that is already gone + # counts as removed (two overlapping dispatches may both pick it), and a failed deletion is + # reported and the rest still tried -- the assets of this upload are on the release either + # way, so this is "attach first, then red" like the signature check. env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail keep="$(ls -1 snapshot-assets | sort)" - current="$(gh release view snapshot --repo ${{ github.repository }} --json assets --jq '.assets[].name' | sort)" + if [ -z "$keep" ]; then + echo "::error::refusing to prune: this run uploaded nothing, so every asset of the release would count as stale" + exit 1 + fi + list_assets() { + gh release view snapshot --repo ${{ github.repository }} --json assets --jq '.assets[].name' | sort + } + current="$(list_assets)" stale="$(comm -13 <(printf '%s\n' "$keep") <(printf '%s\n' "$current"))" if [ -z "$stale" ]; then echo "nothing to remove: the release holds exactly the $(printf '%s\n' "$keep" | wc -l) asset(s) of this upload" exit 0 fi echo "removing $(printf '%s\n' "$stale" | wc -l) asset(s) that are not part of this upload:" + failed=0 while IFS= read -r name; do [ -n "$name" ] || continue echo " deleting $name" - gh release delete-asset snapshot "$name" --repo ${{ github.repository }} --yes + if gh release delete-asset snapshot "$name" --repo ${{ github.repository }} --yes; then + continue + fi + if ! remaining="$(list_assets)"; then + echo "::error::could not delete $name, and could not re-read the release to tell whether it is gone" + failed=$((failed + 1)) + elif printf '%s\n' "$remaining" | grep -qxF -- "$name"; then + echo "::error::could not delete $name" + failed=$((failed + 1)) + else + echo " $name was already gone" + fi done <<< "$stale" + if [ "$failed" -ne 0 ]; then + echo "::error::$failed asset(s) could not be removed; the $(printf '%s\n' "$keep" | wc -l) asset(s) of this upload are on the release" + exit 1 + fi echo "kept the $(printf '%s\n' "$keep" | wc -l) asset(s) of this upload" - name: Fail if anything was attached unsigned # After the upload on purpose: the assets must exist even when the signature does not. diff --git a/CHANGELOG.md b/CHANGELOG.md index 55fb0054..e2ca28ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -500,7 +500,10 @@ from version 5.0.0 onward. Pre-fork releases (`1.x`–`4.2.0`) were authored by name -- `gh release upload --clobber` never removed anything, so the release held 208 assets, 109 of them from 5.0.x and 43 in shapes that no longer exist (`msvc-windows`, `all-*-jar-with-dependencies`), plus three SpotBugs plugin jars an old collect step attached in - 2026-05. Versioned releases are never pruned; the next snapshot run clears the backlog by itself + 2026-05. The prune refuses an empty upload, treats an asset that is already gone as removed and + fails only after trying every deletion (asset by asset on purpose: the job's first form deleted + and recreated the release, and failed in every burst of main pushes in May 2026). Versioned + releases are never pruned; the next snapshot run clears the backlog by itself (`workspace/policies/fat-jar-release-assets.md`). - **A Windows build on an AMX CPU (Sapphire, Emerald or Granite Rapids) crashed the JVM with `STATUS_HEAP_CORRUPTION` when a model was unloaded.** ggml's AMX weight buffer -- used by the diff --git a/CLAUDE.md b/CLAUDE.md index 87eec784..65a8acf6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1048,11 +1048,14 @@ two AARs with the `.aar.asc` Gradle writes next to them, their poms `sign-fatjars.sh`, no `environment:`; `verify-signing-key` still proves the key, and the publish jobs sign with it). Until 2026-10-12 the collect steps copied jars and bare AARs only: every pom was missing, `llama-platform` was absent entirely, the AARs went out unsigned and the `*.jar`-only -signature check never noticed. The check counts jars, AARs and poms now, and `github-snapshot` ends by -deleting every asset of the `snapshot` pre-release the run did not upload, each printed by name -(`gh release upload --clobber` never removed anything: 208 assets, 109 of them from 5.0.x); a versioned -release is never pruned (`workspace/policies/fat-jar-release-assets.md`, "The `snapshot` pre-release -holds exactly the current upload"). +signature check never noticed. The check counts jars, AARs and poms now, and `github-snapshot` ends +by deleting every asset of the `snapshot` pre-release the run did not upload, each printed by name +(`gh release upload --clobber` never removed anything: 208 assets, 109 of them from 5.0.x). The +prune refuses an empty upload, treats an asset that is already gone as removed and fails only after +trying every deletion; a versioned release is never pruned. Why it prunes asset by asset instead of +deleting and recreating the release -- the job's first form, which failed in every burst of main +pushes in May 2026 -- is recorded in `workspace/policies/fat-jar-release-assets.md`, "The `snapshot` +pre-release holds exactly the current upload". The common part of the three smoke scripts is `.github/smoke-lib.sh` (`smoke_classpath`, `smoke_set_modules`, `smoke_assert_loader_line`); the PowerShell script repeats it, since the Windows