v0.25.0 shipped an Arch host no up-to-date box can install — and the pipeline had no way to tell #109

Merged
enricobuehler merged 1 commits from worktree-arch-ffmpeg9-repackage into main 2026-08-08 08:39:33 +00:00
Owner

What users see

error: failed to prepare transaction (could not satisfy dependencies)
:: unable to satisfy dependency 'libavcodec.so=62-64' required by punktfunk-host
:: unable to satisfy dependency 'libavutil.so=60-64' required by punktfunk-host
:: unable to satisfy dependency 'libavfilter.so=11-64' required by punktfunk-host
:: unable to satisfy dependency 'libavdevice.so=62-64' required by punktfunk-host
:: unable to satisfy dependency 'libswscale.so=9-64' required by punktfunk-host

The published punktfunk-host 0.25.0-1 declares FFmpeg 8 sonames; current Arch ships
63/61/12/63/10. Confirmed straight from the registry DB, and reproduced verbatim on a real
ffmpeg-9 box.

It fails safely — pacman refuses, nothing bricks, which is exactly what PR #108 was for — but
it fails broadly: pacman prepares one transaction, so an unsatisfiable dependency of ours
stops the user's entire pacman -Syu, not just our package. Hence the field report reading
as "the package requires 8". punktfunk-canary has the same defect.

Why

A four-minute race, not a design error.

time (2026-08-08)
00:41 PR #108 merges: soname depends + FFmpeg 9 + ci/arch-ci.Dockerfile re-keyed
00:45 v0.25.0 tagged → arch.yml starts
00:56 / 01:02 canary / stable built — still in the FFmpeg-8 :latest
later the re-keyed FFmpeg-9 builder image lands

arch.yml and docker.yml have no ordering between them, and arch.yml deliberately runs no
-Syu ("the image's snapshot IS the build environment"). Only Arch was ever exposed: every
other format derives its dependency from the ELF at build time, so it cannot disagree with
itself this way.

Nothing in the pipeline could have caught it. The existing assert proves the libav dep is
versioned; it cannot prove the version exists.

The change

  • Preflight parity — compare the builder's libav provides against the live repos, -Syu
    the container if they differ. The image is a cache and may lag; on this one axis it may not.
    Syncs into a throwaway --dbpath, so the container never sits in the partial-upgrade state a
    bare pacman -Sy leaves. Also makes it moot whether :latest is current on any given day.
  • Publish gate — resolve every built package with pacman -U --print against a pristine
    --dbpath. An empty db means "nothing is installed", so every dependency must come from the
    repos exactly as on a user's box. Resolving against the builder's own installed set is
    precisely what hides this: a stale ffmpeg satisfies a stale bound. gamescope stays
    best-effort (dropped from the upload with a warning, never fatal).
  • workflow_dispatch(release_tag, pkgrel) — a published release cannot be repaired by
    re-running its tag: pkgrel would stay 1, which is invisible to a box that already recorded
    the broken build, and the workflow file at a tag can never carry inputs added after it.
    Dispatched from main it takes the workflow from main and the source from the tag,
    publishes to the stable repo at a higher pkgrel, and replaces the release-page assets
    (prune_release_assets: upsert_asset replaces by NAME, and a rebuild's filenames differ, so
    the superseded package would otherwise stay one click away — scoped to .pkg.tar.zst so
    concurrent packaging legs can only ever prune names they own).
  • Docs: the pacman error and what to do about it in the Arch README (plus the duplicate
    [punktfunk] block note — visible in the same field screenshot); CHANGELOG says plainly that
    0.25.0's Arch packages were wrong and that 0.25.0-2 is the fix.

Verification — on a real ffmpeg-9 Arch box, not reasoned about

  • The gate against all five published v0.25.0 artifacts: host FAIL (user-visible error
    verbatim), client / web / scripting / gamescope OK. Zero false positives — it would
    have blocked this release.
  • Parity snippet verbatim: -Si --dbpath on an empty db works, pacman does not wrap fields when
    piped, today's provides read correctly.
  • Version logic on all four paths: rebuild → 0.25.0-2 stable, tag push → 0.25.0-1 stable,
    canary unchanged, pkgrel=1 refused.
  • shellcheck clean on gitea-release.sh; asset-prune selector unit-tested (keeps the new set,
    drops the superseded set, leaves other legs' .dmg/.deb untouched).

Landing it

Merging this alone heals the canary channel: arch.yml's path filter includes itself, so the
merge triggers a canary build that exercises both guards. The stable repair is then a
dispatch of arch.yml with release_tag=v0.25.0, pkgrel=2, publishing punktfunk-host 0.25.0-2
— identical source, linked against FFmpeg 9.

## What users see ``` error: failed to prepare transaction (could not satisfy dependencies) :: unable to satisfy dependency 'libavcodec.so=62-64' required by punktfunk-host :: unable to satisfy dependency 'libavutil.so=60-64' required by punktfunk-host :: unable to satisfy dependency 'libavfilter.so=11-64' required by punktfunk-host :: unable to satisfy dependency 'libavdevice.so=62-64' required by punktfunk-host :: unable to satisfy dependency 'libswscale.so=9-64' required by punktfunk-host ``` The published `punktfunk-host 0.25.0-1` declares **FFmpeg 8** sonames; current Arch ships 63/61/12/63/10. Confirmed straight from the registry DB, and reproduced verbatim on a real ffmpeg-9 box. It fails *safely* — pacman refuses, nothing bricks, which is exactly what PR #108 was for — but it fails *broadly*: pacman prepares one transaction, so an unsatisfiable dependency of **ours** stops the user's **entire** `pacman -Syu`, not just our package. Hence the field report reading as "the package requires 8". `punktfunk-canary` has the same defect. ## Why A four-minute race, not a design error. | time (2026-08-08) | | |---|---| | 00:41 | PR #108 merges: soname depends + FFmpeg 9 + `ci/arch-ci.Dockerfile` re-keyed | | 00:45 | `v0.25.0` tagged → arch.yml starts | | 00:56 / 01:02 | canary / **stable** built — still in the FFmpeg-8 `:latest` | | later | the re-keyed FFmpeg-9 builder image lands | `arch.yml` and `docker.yml` have no ordering between them, and arch.yml deliberately runs no `-Syu` ("the image's snapshot IS the build environment"). Only Arch was ever exposed: every other format derives its dependency from the ELF at build time, so it cannot disagree with itself this way. **Nothing in the pipeline could have caught it.** The existing assert proves the libav dep is *versioned*; it cannot prove the version *exists*. ## The change - **Preflight parity** — compare the builder's libav `provides` against the live repos, `-Syu` the container if they differ. The image is a cache and may lag; on this one axis it may not. Syncs into a throwaway `--dbpath`, so the container never sits in the partial-upgrade state a bare `pacman -Sy` leaves. Also makes it moot whether `:latest` is current on any given day. - **Publish gate** — resolve every built package with `pacman -U --print` against a **pristine** `--dbpath`. An empty db means "nothing is installed", so every dependency must come from the repos exactly as on a user's box. Resolving against the builder's own installed set is precisely what hides this: a stale ffmpeg satisfies a stale bound. gamescope stays best-effort (dropped from the upload with a warning, never fatal). - **`workflow_dispatch(release_tag, pkgrel)`** — a published release cannot be repaired by re-running its tag: pkgrel would stay `1`, which is invisible to a box that already recorded the broken build, and the workflow file at a tag can never carry inputs added after it. Dispatched from `main` it takes the *workflow* from main and the *source* from the tag, publishes to the stable repo at a higher pkgrel, and replaces the release-page assets (`prune_release_assets`: `upsert_asset` replaces by NAME, and a rebuild's filenames differ, so the superseded package would otherwise stay one click away — scoped to `.pkg.tar.zst` so concurrent packaging legs can only ever prune names they own). - Docs: the pacman error and what to do about it in the Arch README (plus the duplicate `[punktfunk]` block note — visible in the same field screenshot); CHANGELOG says plainly that 0.25.0's Arch packages were wrong and that `0.25.0-2` is the fix. ## Verification — on a real ffmpeg-9 Arch box, not reasoned about - The gate against all five **published** v0.25.0 artifacts: `host` **FAIL** (user-visible error verbatim), `client` / `web` / `scripting` / `gamescope` **OK**. Zero false positives — it would have blocked this release. - Parity snippet verbatim: `-Si --dbpath` on an empty db works, pacman does not wrap fields when piped, today's `provides` read correctly. - Version logic on all four paths: rebuild → `0.25.0-2` stable, tag push → `0.25.0-1` stable, canary unchanged, `pkgrel=1` refused. - `shellcheck` clean on `gitea-release.sh`; asset-prune selector unit-tested (keeps the new set, drops the superseded set, leaves other legs' `.dmg`/`.deb` untouched). ## Landing it Merging this alone heals the **canary** channel: arch.yml's path filter includes itself, so the merge triggers a canary build that exercises both guards. The **stable** repair is then a dispatch of arch.yml with `release_tag=v0.25.0`, `pkgrel=2`, publishing `punktfunk-host 0.25.0-2` — identical source, linked against FFmpeg 9.
enricobuehler added 1 commit 2026-08-08 08:38:45 +00:00
fix(ci/arch): v0.25.0 shipped a host no Arch box can install, and nothing could tell
apple / swift (pull_request) Successful in 1m38s
apple / screenshots (pull_request) Skipped
android / android (pull_request) Successful in 5m47s
ci / rust-arm64 (pull_request) Successful in 2m35s
ci / web (pull_request) Successful in 1m53s
ci / docs-site (pull_request) Successful in 1m24s
ci / bun-nix (pull_request) Successful in 26s
ci / rust (pull_request) Successful in 7m26s
e044f68500
Arch moved FFmpeg 8 -> 9 (every libav soname +1) hours before the release. PR #108
fixed the real bug — packaging/arch/PKGBUILD now binds punktfunk-host to the sonames
it actually linked, so pacman refuses an upgrade instead of bricking the install — and
re-keyed ci/arch-ci.Dockerfile so the builder would carry FFmpeg 9.

The tag was pushed four minutes later. arch.yml and docker.yml have no `needs:` between
them, and arch.yml deliberately runs no -Syu ("the image's snapshot IS the build
environment"), so the release build pulled the still-FFmpeg-8 `:latest` and published

    punktfunk-host 0.25.0-1  depends: libavcodec.so=62-64, libavutil.so=60-64,
                                      libavfilter.so=11-64, libavdevice.so=62-64,
                                      libswscale.so=9-64

against a world that had moved to 63/61/12/63/10. It fails safely — pacman refuses,
nothing bricks — but it fails broadly: pacman prepares one transaction, so an
unsatisfiable dependency of OURS stopped affected users' entire `pacman -Syu`.

Nothing in the pipeline could have caught it. The existing assert proves the dep is
VERSIONED; it cannot prove the version EXISTS. So two guards, plus the lever to repair
a release that has already shipped:

* Preflight parity — compare the builder's libav `provides` against the live repos and
  `-Syu` the container if they differ. The image is a cache and may lag; on this one
  axis it may not. Syncs into a throwaway --dbpath so the container never sits in the
  partial-upgrade state a bare `pacman -Sy` leaves.

* Publish gate — resolve every built package with `pacman -U --print` against a
  PRISTINE --dbpath. Empty db means "nothing is installed", so every dependency must
  come from the repos exactly as on a user's box. Resolving against the builder's own
  installed set is what would hide this: a stale ffmpeg satisfies a stale bound.
  gamescope stays best-effort (dropped from the upload with a warning, never fatal).

* workflow_dispatch(release_tag, pkgrel) — a published release cannot be repaired by
  re-running its tag: pkgrel would stay 1, which is invisible to a box that already
  recorded the broken build, and the workflow file at the tag can never carry inputs
  added after it. Dispatched from main it takes the WORKFLOW from main and the SOURCE
  from the tag, publishes to the stable repo at a higher pkgrel, and replaces the
  release-page assets (prune_release_assets: upsert replaces by NAME, and a rebuild's
  filenames differ, so the superseded package would otherwise stay one click away).

Verified on a real ffmpeg-9 box (.21, CachyOS) rather than reasoned about: the gate
rejects the published 0.25.0-1 host with the user-visible error verbatim, and passes
client, web, scripting and gamescope — 0 false positives across all five artifacts.
The parity snippet reads today's `provides` correctly (`-Si --dbpath` on an empty db
works; pacman does not wrap fields when piped). Version logic exercised on all four
paths: rebuild -> 0.25.0-2 stable, tag push and canary unchanged, pkgrel=1 refused.

Ships as punktfunk-host 0.25.0-2. README gains the pacman error and what to do about
it; CHANGELOG says plainly that 0.25.0's Arch packages were wrong.
enricobuehler merged commit 1ef212a78d into main 2026-08-08 08:39:33 +00:00
enricobuehler deleted branch worktree-arch-ffmpeg9-repackage 2026-08-08 08:39:34 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#109