|
| 1 | +# Pinned tags are created once |
| 2 | + |
| 3 | +⭐ **Decided 2026-10-15.** A pinned tag is created by the first build that |
| 4 | +names it and is never pointed at another image: `v<date>`, `<alias>-v<date>` |
| 5 | +and `<alias>-<anchor>`. `latest` and `<alias>` move with every build. The |
| 6 | +maintainer asked for the rule that is easier to maintain. This is it: one rule |
| 7 | +for every pinned family, in one place, under one test. The other choice, an |
| 8 | +anchor tag that follows the newest build with its pacman version, is a second |
| 9 | +rule with its own tests. |
| 10 | + |
| 11 | +## What moved |
| 12 | + |
| 13 | +`README.md` said all three pinned families never move. The publish job created |
| 14 | +every tag on every build, and only the backup cron stood down when the day's |
| 15 | +`v<date>` existed. So a second real publish on one day moved that day's dated |
| 16 | +tags. The runs whose `Create tags` job succeeded, read from their logs: |
| 17 | + |
| 18 | +```bash |
| 19 | +gh run list --workflow build-deploy.yml --limit 200 --json databaseId --jq '.[].databaseId' |
| 20 | +gh run view RUN --log | grep -E '^Create tags' | grep -oE 'ghcr\.io/pkgforge-dev/archlinux(-ci)?:v[0-9.]+' | sort -u |
| 21 | +``` |
| 22 | + |
| 23 | +| day | real publishes of `v<date>` | runs | |
| 24 | +| --- | --- | --- | |
| 25 | +| 2026-08-26 | 5 | `32992678276`, `33003974783`, `33005958600`, `33008010432`, `33009605655`, all dispatches | |
| 26 | +| 2026-08-28 | 3 | `33184973002` dispatch, `33195147714` schedule, `33206327128` dispatch | |
| 27 | +| every other day with a real publish, 2026-08-29 to 2026-10-14 | 1 | | |
| 28 | + |
| 29 | +⚠ On 2026-08-28 the last of the three added the PowerPC ports, so |
| 30 | +`v2026.08.28` went from five platforms to eight. Completed row 42 of |
| 31 | +`HISTORY/CONTINUE.md` records that as a result. It was also a pinned tag |
| 32 | +moving. |
| 33 | + |
| 34 | +⚠ The anchor tags moved every day, not only on days with two builds. pacman |
| 35 | +stayed at one version for weeks, and each build created |
| 36 | +`<alias>-7.1.0.r9.g54d9411-2` again. Measured 2026-10-15 on GHCR: `x86_64`, |
| 37 | +`x86_64-v2026.10.14` and `x86_64-7.1.0.r9.g54d9411-2` were one digest, |
| 38 | +`sha256:690e2a8d99a37a5f64c6d45d78d367f9211f94c4e2bf698e57c59b78ab758275`, |
| 39 | +and `x86_64-v2026.10.13` was another. So the anchor tag had moved from the |
| 40 | +2026-10-13 image to the 2026-10-14 image. |
| 41 | + |
| 42 | +## Who pins what |
| 43 | + |
| 44 | +GitHub code search, 2026-10-15: |
| 45 | + |
| 46 | +| search | results | |
| 47 | +| --- | --- | |
| 48 | +| `"pkgforge-dev/archlinux:v2026"` | 11. Nine are outside this repository: three release workflows of `VSCodium/vscodium`, their copies in `crow-cli/crow-cli`, and one each in `stoatchat/for-desktop`, `Chillboio-Studios/Dawnchat-For-DESKTOP` and `edgarcnp/homebrew-tap` | |
| 49 | +| `"pkgforge/archlinux:v2026"` | 1, in this repository | |
| 50 | +| `"pkgforge-dev/archlinux:x86_64-v2026"` and the Docker Hub name | 0 | |
| 51 | +| `"pkgforge-dev/archlinux:x86_64-7"` and the Docker Hub name | 0 | |
| 52 | + |
| 53 | +```bash |
| 54 | +gh api -X GET search/code -f q='"pkgforge-dev/archlinux:v2026"' --jq .total_count |
| 55 | +``` |
| 56 | + |
| 57 | +The dated index is what consumers pin, and a moved one gives a consumer who |
| 58 | +pinned it another image. No result pins an anchor tag, so freezing those is a |
| 59 | +change nobody found in use. |
| 60 | + |
| 61 | +## How |
| 62 | + |
| 63 | +1. The publish job reads the target repository's tag list with |
| 64 | + `scripts/registry-tags`, before it creates a tag. GHCR is read because every |
| 65 | + digest is staged there, and one `imagetools create` gives both registries |
| 66 | + their tags. |
| 67 | +2. Both tag steps pass that list to `scripts/tag-names --existing`, which |
| 68 | + leaves out each pinned tag on it and names it on stderr. A tag that is not |
| 69 | + passed to `imagetools create` cannot move. |
| 70 | +3. The verify step reads the platforms of `latest`, which this run always |
| 71 | + writes, and requires the dated index to exist. A dated index from an earlier |
| 72 | + build of the day carries that build's platforms. |
| 73 | + |
| 74 | +⛔ A list that cannot be read stops the publish. Read as empty, it would move |
| 75 | +every pinned tag. That is the same choice as the anchor floor: a guard that a |
| 76 | +transient switches off is not a guard. |
| 77 | + |
| 78 | +The backup cron still stands down when `v<date>` exists. It saves the runner |
| 79 | +time; it is no longer what keeps the dated tag in place. |
| 80 | + |
| 81 | +## What it changes for a consumer |
| 82 | + |
| 83 | +| case | before | after | |
| 84 | +| --- | --- | --- | |
| 85 | +| a second build on one day, a dispatch or a cron | every tag of that day moves | `latest` and `<alias>` move | |
| 86 | +| a build with the same pacman version as yesterday | `<alias>-<anchor>` moves | `<alias>-<anchor>` stays on the first image with that version | |
| 87 | +| a port that published nothing earlier in the day | its `<alias>-v<date>` is created | the same | |
| 88 | +| the emergency path, TODO 4: a dispatch after the day published | moves every tag of the day | moves the rolling tags. The fix reaches `latest` and `<alias>`, and the dated tags keep the morning's image | |
| 89 | + |
| 90 | +⚠ An anchor tag that exists now stays where it is, so each |
| 91 | +`<alias>-7.1.0.r9.g54d9411-2` keeps the image it pointed at when this change |
| 92 | +landed. |
| 93 | + |
| 94 | +⚠ If the first image with a pacman version is defective, its anchor tag keeps |
| 95 | +it. The rolling and the next day's dated tags carry the fix. |
| 96 | + |
| 97 | +## Measured |
| 98 | + |
| 99 | +Dry run `38675434990` on a branch, into the scratch repository, the second |
| 100 | +build of 2026-10-15 there. The first was dry run `38670811239`. Digests read |
| 101 | +anonymously before and after, first 12 hex digits: |
| 102 | + |
| 103 | +| tag in `ghcr.io/pkgforge-dev/archlinux-ci` | before | after | |
| 104 | +| --- | --- | --- | |
| 105 | +| `latest` | `445762b79215` | `6ee338a7249d` | |
| 106 | +| `v2026.10.15` | `445762b79215` | `445762b79215` | |
| 107 | +| `x86_64` | `316148d98390` | `dc77face842c` | |
| 108 | +| `x86_64-v2026.10.15` | `316148d98390` | `316148d98390` | |
| 109 | +| `x86_64-7.1.0.r9.g54d9411-2` | `316148d98390` | `316148d98390` | |
| 110 | +| `aarch64` | `ddf3f281cbab` | `e74f4e376390` | |
| 111 | +| `aarch64-v2026.10.15` | `ddf3f281cbab` | `ddf3f281cbab` | |
| 112 | +| `aarch64-7.1.0.r9.g54d9411-2` | `ddf3f281cbab` | `ddf3f281cbab` | |
| 113 | +| `riscv64` | `aa6be4315646` | `80b633486e7b` | |
| 114 | +| `riscv64-v2026.10.15` | `aa6be4315646` | `aa6be4315646` | |
| 115 | +| `riscv64-7.1.0.r9.g54d9411-2.1` | `aa6be4315646` | `aa6be4315646` | |
| 116 | + |
| 117 | +The rolling tags moved and the pinned ones did not. The tag steps named 21 |
| 118 | +pinned tags as kept, which is every pinned tag of the five ports that built, |
| 119 | +four for amd64, arm64 and loong64, six for armv7, two for riscv64, and |
| 120 | +`v2026.10.15`. The verify step read the platforms of `latest`: |
| 121 | +`published ghcr.io/pkgforge-dev/archlinux-ci:latest with platforms amd64 arm/v7 arm64 loong64 ppc ppc64 ppc64le riscv64, and ghcr.io/pkgforge-dev/archlinux-ci:v2026.10.15 exists`. |
| 122 | + |
| 123 | +```bash |
| 124 | +gh run view 38675434990 --log | grep -E '^Create tags' | grep -c 'is pinned and exists' |
| 125 | +``` |
| 126 | + |
| 127 | +## What this does not verify |
| 128 | + |
| 129 | +- ⚠ Docker Hub. The list is read from GHCR only. A pinned tag that exists on |
| 130 | + GHCR and not on Docker Hub, because an earlier copy failed, is not created on |
| 131 | + Docker Hub again. That earlier run's verify step fails loudly on the missing |
| 132 | + copy. |
| 133 | +- ⚠ Two aliases of one architecture can diverge if one pinned name exists and |
| 134 | + the other does not. Both are created by one command, so that needs an earlier |
| 135 | + partial failure. |
0 commit comments