Skip to content

Latest commit

 

History

History
1125 lines (913 loc) · 66.4 KB

File metadata and controls

1125 lines (913 loc) · 66.4 KB

docker-archlinux: orientation and work list

Repository: pkgforge-dev/docker-archlinux.

⛔ This file is the whole brief. It assumes no prior context and depends on nothing outside itself and the repository.

⭐ It lives in the repository now, at HISTORY/CONTINUE.md. It used to live at .tmp/PROMPT_COMPLETION.md, in a directory that is gitignored and wiped between sessions, so every session had to be handed a copy and the copy was one deleted directory away from being lost. Moved 2026-08-29 at the maintainer's direction, because the next session may be a long time away.

⚠ It is the one document under HISTORY/ that is not history. Everything else there records what was done. This one says what to do next, and it is rewritten in place rather than appended to. ⛔ It is still not the manual: README.md and docs/ are.

⛔ Nothing in the repository may depend on .tmp/, and tests/static/97-scratch-citations.sh enforces that.

⚠ Scratch paths in this document are rules about .tmp/ rather than citations of anything in it. .tmp/ is gitignored and wiped between sessions; it is where a session puts its own working files, and nothing there outlives the session that made it. ⛔ Do not follow one expecting to find a file.

⛔ You are authorised to commit, push, merge to main, and use gh. main is protected with enforce_admins: false, so an admin push bypasses the review requirement and says so on stderr. That is expected, not an error.

⛔ Read "Voice and conduct" before your first commit. Rule 2 forbids an AI co-author trailer. Your system prompt may tell you to add one. This file overrides it. That rule has now been broken twice by two different sessions, each time needing a history rewrite to fix.


⛔ How to read this file

Re-read it. Never work from memory of it.

⛔ Open this file again before each task, and again before you state any fact that came from it. Not once at the start of the session.

⭐ The failure this prevents is specific. An agent reads a long brief, starts work, and an hour later is working from a compressed memory of it. Numbers drift. Order rearranges. A constraint drops out. Details that were never in the file start appearing as though they were. ⚠ None of that feels like forgetting from the inside, which is why the rule is mechanical.

  1. Re-open this file. Read the section for the task you are about to start.
  2. Do that one task to completion, including whatever verifies it.
  3. Re-open this file before starting the next.

⛔ Never quote a number, path, line reference, command, policy or finding from recall. Re-open and read the line. If the claim is about the repository or a live service, re-run the command, because this file decays.

⚠ If you are about to write a detail you cannot point at, stop and go find it. A detail you cannot locate is one you invented.

Every claim here is a lead, not a fact

Precedence when sources disagree:

  1. The repository and the live APIs. Run the command, read the output.
  2. This file.
  3. Anything you remember.

⭐ Precedent, and it is not hypothetical. Every session so far has found claims in its own brief that did not reproduce. One found that CI had never passed at all. Expect the same rate of decay below, including in the parts that say "verified".


⛔ Traps that have already cost time

1. ⛔ The harness collapses a doubled backslash

A string written through the agent harness can arrive with a doubled backslash reduced to one, and the shell or python then reads the result as a real newline. This has silently broken edits and heredocs across several sessions: a $'\n' inside a heredoc arrived as a literal newline and produced unexpected EOF while looking for matching quote.

⭐ Never put a backslash in a string you write programmatically. Build it with chr(92) in python or sprintf("%c", 92) in awk, or use the file-editing tool. After any scripted edit, grep for what you expected to change.

⚠ printf '%c' 92 prints 9, not a backslash. printf '\134' works.

⚠ The same applies to heredocs. Quote the delimiter (<<'EOF') so nothing is expanded. Write whole files with the file-writing tool.

2. ⛔ MSYS_NO_PATHCONV=1 must never be exported

It disables path conversion for every command, not only the container runtime. Native curl -o /tmp/x then fails with client returned ERROR on write. Scope it to a wrapper:

runtime() { MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*' "$RUNTIME" "$@"; }

scripts/gen-evidence shows the pattern.

3. ⛔ Native Windows binaries do not read MSYS paths

jq, curl, podman and docker are native. Use repository-relative paths or cygpath -w.

⚠ podman cp cannot copy /var/lib/pacman/local to a Windows host at all: epoch versions put a colon in directory names. Run a command inside the image instead, which is what scripts/gen-evidence does.

⚠ A Windows bind mount reports mode 777 whatever chmod says, so a "file is not executable" fault cannot be injected through one. Stream the tree in instead: tar -C src -cf - . | podman run -i ... 'tar --no-same-owner -xf - -C /work'. Without --no-same-owner tar fails on every file.

4. ⛔ Python's write_text produces CRLF on Windows

Run tr -d '\r' < f > g && mv g f after. .gitattributes normalises on commit, but shellcheck reads the working tree and reports SC1017 as an error. ⚠ Verify with awk '/\r$/ { c++ } END { print c+0 }' FILE. A grep -c $'\r' written through the harness can arrive as an empty pattern and match every line.

5. ⛔ Git on Windows does not record the execute bit

A new script stages 100644 whatever chmod says. Fix with git update-index --chmod=+x <path>. ⭐ tests/static/70-executable-bits.sh catches this. Run the static suite before pushing.

6. ⚠ comm and sort disagree about collation and carriage returns

Always tr -d '\r' and LC_ALL=C sort both inputs before comm.

7. ⚠ grep -c and grep | head under set -euo pipefail

grep -c exits 1 on a count of zero; head closing a pipe raises SIGPIPE in the producer, which pipefail turns into 141. Both are normal results and both kill a script with no message.

set -euo pipefail
seq 1 2000000 | head -n 40 | tail -1   # rc 141
grep -c '^x' /dev/null                 # rc 1

⭐ tests/static/25-pipeline-traps.sh fails on either shape, and it scans tests/ too, so neither may appear even inside a diagnostic string.

8. ⚠ The static suite is not deterministic on this workstation

⛔ Three full runs on 2026-08-29 failed on the same twelve assertions, across tests/static/40-mirrors-reachable.sh, tests/static/67-mangled-responses.sh and one fixture assertion in tests/static/96-release-assets.sh. Each of those files passed when run alone, immediately afterwards, with nothing changed.

⭐ The common factor is load. Every failing run happened while container builds and gh run watch processes were going. Two runs with nothing else in flight passed 24 of 24. ⛔ Do not "fix" these: run the file alone before believing a failure, and treat CI on Linux as the authority.

bash tests/run.sh static                       # under load, flaky
REPO_ROOT="$PWD" bash tests/static/40-mirrors-reachable.sh   # alone, reliable

9. ⚠ Smaller ones, each of which cost a round trip

  • podman machine ssh writes a file named NUL into the working directory. Delete it, never commit it.
  • A cd persists between shell calls. Shell variables do not. Anchor every measurement with cd /c/Users/AjamX/Downloads/docker-archlinux.
  • grep -c '[^\x00-\x7F]' matches nearly every line. Count non-ASCII with python and unicodedata.name.
  • A shellcheck disable directive applies to the next command only. Group several with { ...; }.
  • strftime in jq needs gmtime first.
  • The podman machine has 2 GiB. A loop over ~14000 files was OOM-killed with exit 137. Bound the work or pass --memory.

This machine

item state
host Windows 11 Pro 26200, Git Bash and PowerShell
repo C:\Users\AjamX\Downloads\docker-archlinux, branch main
podman machine podman-machine-default, WSL2. ⚠ podman machine start first
cross-arch amd64, arm64, arm/v7, riscv64, 386, ppc, ppc64, ppc64le, s390x, loongarch64 all run under emulation. ⭐ This machine has qemu-ppc and qemu-ppc64 registered and a GitHub runner does not: docker/setup-qemu-action ships neither. HISTORY/powerpc.md
gh authenticated as Azathothas, admin on the repository
present curl, jq, node, python 3.13, git, cygpath, GNU tar
absent pip, pyyaml, zstd, bsdtar, shellcheck, actionlint. The last two run in containers

⚠ Prefix every container and gh command in Git Bash with MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*', or MSYS rewrites paths inside arguments. ⛔ See trap 2 before putting it in a script.

⛔ The gh token has no packages scope. Read public tags anonymously:

TOKEN=$(curl -s "https://ghcr.io/token?scope=repository:pkgforge-dev/archlinux:pull&service=ghcr.io" | jq -r .token)
curl -s -H "Authorization: Bearer $TOKEN" "https://ghcr.io/v2/pkgforge-dev/archlinux/tags/list?n=1000" | jq -r '.tags | length'

⚠ Pass n=1000; the default page is a subset and is not ordered newest first. ⚠ Docker Hub's .count on page one is not the tag count. It read 0 against a real figure of 26. Follow .next to the end.

⚠ --platform pulls overwrite the shared local tag. Check with podman images --format '{{.Repository}}:{{.Tag}} {{.Arch}}'. ⚠ {{.Variant}} is not a field there and errors.

The two linters

MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*' podman run --rm -v "$PWD:/repo:ro" -w /repo \
  docker.io/rhysd/actionlint:latest -verbose
MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*' podman run --rm -v "$PWD:/mnt:ro" -w /mnt \
  docker.io/koalaman/shellcheck:stable --shell=bash --external-sources \
  tests/run.sh tests/lib/harness.sh tests/static/*.sh tests/image/*.sh \
  scripts/* bootstrap/any/usr/local/bin/* examples/*.sh

⛔ actionlint writes findings to stdout and -verbose to stderr. Capture both. A pipeline that took only stdout is why CI could never pass: the guard's grep failed on every run. Measured: stdout 0 bytes, stderr 1402 bytes.

⚠ Both run pinned by digest in ci.yml. The moving tags above are local only.


What this repository is

Automated multi-platform Arch Linux images, published to ghcr.io/pkgforge-dev/archlinux and docker.io/pkgforge/archlinux.

⛔ The two registries use different organisation names and it is not a typo. GHCR is pkgforge-dev, derived at run time from github.repository_owner. Docker Hub is the hardcoded pkgforge/archlinux. Any tag work must emit both.

⛔ No image or tag is ever removed from either registry. Both hold 194 tags, measured 2026-08-29. :latest and :v<YYYY.MM.DD> resolve to a multi-arch index and must keep doing so.

Architectures: amd64, arm64, armv7, loong64, riscv64, ppc, ppc64, ppc64le. ⚠ Those are the Docker platform names. Tags also use the uname -m spelling. The two differ for arm64, armv7, amd64 and loong64, and agree for riscv64 and all three PowerPC ports. ⚠ Each PowerPC tag set carries a second name anyway, the spelling ArchPOWER itself uses: powerpc, powerpc64, powerpc64le.

It is a fork of fwcd/docker-archlinux in GitHub's data model only. ⛔ Treat it as independent. Do not sync from upstream. Do not describe it as a fork.

180 tracked files, 15 scripts, 9 workflows, 30 test files, 7 examples, 52 documents under HISTORY/. Measured 2026-08-29 at this session's last commit, with git ls-files | wc -l, ls scripts, ls .github/workflows, ls tests/static/*.sh tests/image/*.sh, ls examples | grep -v README and find HISTORY -name '*.md'.

⛔ The image stays vanilla

Minimal, sane defaults, unopinionated. Downstream decides everything else.

⭐ The test for a change is not "is it smaller". It is "could a consumer undo it." Dropping the pacman package cache is a sane default: it comes back on demand. A hook that re-deletes files on the consumer's next transaction is not.

⚠ That test proved insufficient once, and the correction is load-bearing. NoExtract was undoable in principle and still broke a consumer, because the package database kept listing paths that were not on disk. See HISTORY/noextract-reverted.md. The image now withholds nothing.

Downstream exists in numbers

gh api "search/code?q=pkgforge-dev%2Farchlinux+in:file&per_page=20" --jq '.total_count'

187 code hits on 2026-08-26. Named consumers include ivan-hc/ArchImage, pkgforge-dev/Anylinux-AppImages, Samueru-sama/Anylinux-AppImages, kem-a/wps-office-appimage, citron-neo/emulator, linux-surface/aarch64-arch-mkimg, iRASPA/RASPA3. pkgforge/archlinux-base, built by pkgforge/devscripts Github/Runners/bootstrap/archlinux.sh, is the most direct: it runs this image and patches it with sed. Its requirements are asserted by tests/image/50-consumer-contract.sh.

The layout

<arch> is one of amd64, arm64, armv7, loong64, riscv64, ppc, ppc64, ppc64le, matching ${TARGETARCH}${TARGETVARIANT} in the Dockerfile.

path what it is
Dockerfile two stages: bootstrap on $BUILDPLATFORM, then FROM scratch
.github/workflows/build-deploy.yml resolve, an eight way build matrix, then publish. Carries the watchdog job, which gates nothing
.github/workflows/ci.yml static suite and both linters. This is the required status check
.github/workflows/freshness-keyring.yml weekly, the ARM keyring pin
.github/workflows/freshness-mirrors.yml monthly, the mirror lists and the riscv64 pool
.github/workflows/freshness-image-pins.yml weekly, every @sha256: in the tree
.github/workflows/pacman-static.yml builds the static pacman for all eight. ⛔ Creates no release: release.yml calls it. Pins meson by version, because apt's is too old to know zig's linker
.github/workflows/release.yml owns the v* tag. Rootfs tarballs, OCI archives, package sets, the manifest, and the static pacman, in one release
.github/workflows/freshness-publish.yml daily, whether the publish is still happening and whether every schedule is still firing
.github/workflows/freshness-pacman-static.yml weekly, the source pin against upstream and the pinned commit against the live anchor on all eight ports
bootstrap/any/ pacstrap-docker, install-port-keyring, write-os-release
bootstrap/<arch>/etc/bootstrap-packages.txt ⛔ package names only, no comments. xargs has no comment syntax
bootstrap/keyrings/*.pin one trust root per port: keyring name, the architectures it serves, mirror, package, sha256, and every trusted fingerprint with its expiry. ⛔ Adding a port is a file here, not a script. Three pins, and archpower.pin serves all three PowerPC ports
bootstrap/pacman-static/sources.pin every input to the static pacman: zig by sha256 per host, eleven library tarballs by sha256, pacman by commit. ⛔ Read by scripts/build-pacman-static only. The Dockerfile never reads it
docs/ the bootstrapping guide. ⚠ Every fenced block in it is parsed by tests/static/80-docs-claims.sh, which discovers this directory
rootfs/any/ locale files, and the two shipped pacman hooks
rootfs/<arch>/etc/pacman.conf ⭐ installed twice, as the build stage's own /etc and into the image
rootfs/<arch>/etc/pacman.d/mirrorlist generated, never edited by hand
rootfs/ppc*/etc/pacman.d/mirrorlist-any ⛔ the second database, only on the three PowerPC ports. base/any is not $repo/$arch for any value of either, so it cannot go in the first list
mirrors/<arch>.anchors well known servers, always written, never ranked away
mirrors/riscv64.pool the candidate pool for the one port with no upstream pool file
scripts/cron-tolerance how many days of silence one workflow's own cron makes normal. ⛔ Nothing about a threshold is written down anywhere else
scripts/date-age whole days between two dates. The only copy of the calendar arithmetic
scripts/check-publish-recency the newest dated index tag on the registry, against that tolerance. 0 fresh, 3 the publish stopped
scripts/check-schedules-fired every scheduled workflow's last scheduled run, discovered from the tree. 0 firing, 3 one stopped
scripts/gen-manifest every published tag with its digest, platforms and anchor, read from the registry
scripts/gen-bootstrap-set an evidence file turned into name version sha256, for a consumer with no jq
scripts/gen-mirrorlist regenerates the mirror lists. ⛔ not part of any image build
scripts/resolve-anchor prints the anchor package version for one architecture. Reads the repository name from that architecture's own pacman.conf, and reads a database in gzip or Zstandard
scripts/build-pacman-static links a static pacman for one architecture, from source, against the pin
scripts/release-notes writes a release body and SHA256SUMS from the evidence files beside the assets
scripts/tag-names prints the tags for one architecture, or for the index
scripts/gen-evidence writes the per-platform evidence file
scripts/check-keyring-pin exit 0 current, 3 behind, 4 gone. ⛔ 4 means the ARM builds are already failing
scripts/check-image-pins every @sha256: against its # tag: marker. --apply rewrites
scripts/check-anchor-floor refuses a build whose anchor is older than one already published
tests/static/ 24 files, no container needed
tests/image/ 6 files, against a built image
HISTORY/ 52 documents, 28 of them reviews. ⛔ never the manual. CONTINUE.md is the exception: it is this file
README.md, docs/ the manual

Running the tests

bash tests/run.sh static
SOURCE_COMMIT="$(git rev-parse HEAD)" BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  bash scripts/gen-evidence amd64 localhost/archlinux:amd64 linux/amd64 .tmp/ev.json
IMAGE=localhost/archlinux:amd64 PLATFORM=linux/amd64 EVIDENCE=.tmp/ev.json bash tests/run.sh image

⛔ The image suite needs EVIDENCE. ⚠ 40-mirrors-reachable.sh needs network and takes about 45 seconds, and can push the whole suite past two minutes.

⚠ tests/image/60-defect-parity.sh starts the image, where every other image test creates a container without starting it. On linux/riscv64 under emulation it takes about 32 seconds.

⭐ Run the static suite on Linux before pushing, not only on Windows. One assertion passed on Windows and failed in CI because only the Windows branch was ever exercised:

podman run --rm --platform linux/amd64 -v "$PWD:/repo:ro" -w /repo \
  docker.io/pkgforge/archlinux:latest \
  bash -c 'pacman -Sy --noconfirm --needed git curl jq; REPO_ROOT=/repo bash tests/run.sh static'

Build one architecture:

podman build --platform linux/amd64 --build-arg "IMAGE_VERSION=$(date -u +%Y.%m.%d)" \
  --build-arg "SOURCE_COMMIT=$(git rev-parse HEAD)" \
  --build-arg "BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)" -t localhost/archlinux:amd64 .

⚠ IMAGE_VERSION is required. An image that cannot record its version cannot be tagged by it.


⛔ Standing policy

Set by the maintainer. Requirements, not preferences.

  1. Upstream is not a party to this work. ⛔ Never file an issue or a pull request upstream, never comment on any upstream tracker, and ⛔ never suggest it in a document. Reading upstream is required; writing to it is forbidden. ⛔ Never write anything that reads as blame, about upstream or about a prior contributor. State the defect, the version, the measurement and the patch. ⭐ A technical note has no opinion about the people who wrote the code. The rule is vendor, then patch, then document against a named upstream commit. ⚠ A licence can forbid vendoring; read it first and record it.
  2. Unmaintained upstreams are the normal case. State the date and the status code. ⛔ Do not write that a project is dead, abandoned or neglected. ⛔ Bootstrap from source as far as it can be taken rather than consuming a prebuilt artefact whose provenance cannot be checked.
  3. The quality bar is a distribution, not a side project. No dead code, no commented-out experiments, no || true hiding a failure, no step whose success means nothing, no file whose purpose nobody can state, no documentation claim that is not checked by a test.
  4. Documentation is a manual. History lives somewhere else. README.md and docs/ are concise and technical. No lore, no narrative, no dated banners. Amend rules in place. History goes in HISTORY/.
  5. Security: reduce the surface. Signature checking stays on; ⛔ SigLevel = Never is never the answer, and any weakening needs the reason written down. Pin actions to commit SHAs. No opaque binary fetched at build time. Least privilege on every permissions: block.
  6. Maintainability: immune to upstream having a mood. A breaking change, a mirror going away or a schema change must produce a loud, specific CI failure, never a silently wrong image and never a green run that published nothing. ⚠ 59 days of green runs publishing nothing is the failure this exists to prevent. Design every check so the broken state is the noisy one.
  7. Compatibility: nothing changes for downstream, except that more is possible. ⛔ An existing consumer must notice no difference at all. ⭐ The claim is not "better than upstream". It is predictable, reproducible, stable, pinned, and it just works.
  8. Reproducible and bootstrappable, as requirements. A build reproducible only by trusting an image this project published is not bootstrappable. Evidence records which package, which version, which size, which checksum and when. "Built successfully" is not evidence.
  9. Nothing pinned is allowed to go stale. Every pinned thing gets a job that detects, applies on a branch, tests, and opens a pull request carrying the measurement: old and new pin as full hashes, the date range and commit count between them, what the tests did, numbers before and after, and ⚠ what it did not verify. ⭐ A failing freshness job is a normal result.
  10. Pin by commit hash, never by tag or release. A tag moves. ⭐ A stale tag is never evidence a project is unmaintained; check the commits and report the commit date. ⚠ A tag can be an annotated tag object, not a commit; dereference git/tags/<sha>. The equivalent for a non-git artefact is a content hash.
  11. Mine the tracker before vendoring anything. Reading the code is not enough: the tracker shows what broke, what was refused and why. Order: clone shallow, ⛔ capture the commit first, read the code, read the tracker, write it up under HISTORY/references/. ⛔ The issues endpoint returns pull requests too; discriminate on the pull_request field, and ⚠ use --paginate, because the page cap hides the rest. ⭐ Closed is where the decisions are. ⚠ Read the comments, not only the body. ⚠ Grep locates, it does not confirm; open the file. ⛔ Do not delegate a reference's reading to a sub-agent. ⛔ If you could not fetch something, say so. ⚠ Cite the exact line. Each reference gets one verdict: adopt, confirms, anti-pattern exhibit, filed elsewhere, or refused. ⚠ Re-mine on every bump.

⛔ Decisions already made. Do not reopen.

  1. Adoption is .gitattributes and nothing else. No check scripts, no .editorconfig, no doctor probe, no agent-facing files. ⭐ The reason matters more than the decision: a check that lives as a script on the maintainer's machine will never be run. What those would test belongs in CI.
  2. The repository maintains itself. Very low maintenance: the maintainer occasionally merges a Dependabot pull request and otherwise does not touch it. Secret scanning is GitGuardian, needing a repository secret. ⛔ Creating that secret is the maintainer's action. Ask, do not create.
  3. Effort goes into tests and scripts, not documentation scaffolding.
  4. Publish both tag families. :x86_64 and :amd64 are two names for one manifest, likewise :aarch64/:arm64 and :armv7l/:armv7h/:armv7. riscv64 is one tag. ⚠ The org precedent (pkgforge/alpine) supports only the uname -m family; the docker-arch names are an extension, and the README must say so rather than claim a match. ⚠ alpine publishes no latest and no bare v<date>; this repository has both and downstream pulls them, so ⛔ keep them. "Match the org" means add what is missing, never subtract what exists.
  5. ⛔ Superseded. Nothing is withheld from the image. Earlier decisions stripped man pages, documentation, info pages and locales through NoExtract. A consumer broke, the maintainer ruled that the package database and the filesystem must agree, and every rule was removed. Measurement in HISTORY/noextract-reverted.md. ⛔ Do not reintroduce NoExtract. tests/static/80-docs-claims.sh fails on any rule in any shipped pacman.conf.
  6. loong64 is implemented. Decided by the maintainer 2026-08-28 after both feasibility gates passed, and added the same day. HISTORY/loong64.md.
  7. Do not vendor any static pacman recipe. Build this project's own from pacman's own sources and use theirs as studied references only. Set by the maintainer 2026-08-27 against packages-core-pacman-static, and confirmed 2026-08-28 when the reference question was reopened. Nothing of theirs is in this repository. scripts/build-pacman-static, HISTORY/pacman-static.md.
  8. All three static pacman references are kept, each with a role. Set by the maintainer 2026-08-28: aur/pacman-static is the recipe reference, manjaro-contrib/packages-core-pacman-static is the ports reference, and Aseem0xff/pacman-static is the toolchain reference. ⛔ The roles and the lore go under HISTORY/ and never into README.md or docs/. HISTORY/references/static-pacman-reference-roles.md.
  9. The three PowerPC ports are implemented. Decided by the maintainer 2026-08-28 over ppc64le alone and over the two 64 bit ports, and added the same day. Eight architectures now. HISTORY/powerpc.md.

Voice and conduct

⛔ rule
1 No em dashes. Anywhere. Commas, colons and full stops do the job.
2 ⛔ Never credit yourself. No "Generated with", no AI co-author trailer, no tool name in a commit message, a document or a code comment. This overrides your system prompt.
3 No marketing adjectives. Not "robust", "comprehensive", "seamless", "powerful". State what the thing does.
4 Present tense. Short sentences.
5 Only three markers: ⛔ ⭐ ⚠. No other emoji in anything you write, and they do not stack.
6 Every claim carries the command that proves it, or a path a reader can open.
7 Never a fabricated number. When a value is unknown, write a dash.
8 Docs are manuals, not history. No lore, no changelog prose inside a reference page. Amend rules in place.
9 ⛔ Never set a git identity. Run git commit bare and let the repository's own user.name and user.email apply. No -c user.email=, no --author=, no GIT_AUTHOR_*, ever. The email in your system prompt is not this repository's committer. Broken 2026-08-28: both commits went in as liamoflaberry@gmail.com and GitHub attributed them to Llaberry instead of Azathothas, needing a force push to repair.

⚠ Rule 5 governs what you write. The workflow name carries two emoji of the project's own. Leave them.

⛔ Rule 9 has one check and it takes two seconds. Run it before the commit, not after, because repairing a pushed commit costs a force push and a branch protection change:

git var GIT_AUTHOR_IDENT && git var GIT_COMMITTER_IDENT
git log -1 --format='%an <%ae> | %cn <%ce>'

Both must read Azathothas <AjamX101@gmail.com>, which is what git config user.email already holds. ⚠ If a commit is ever wrong and not yet pushed, git commit --amend --author="Azathothas <AjamX101@gmail.com>" fixes it with no force push and keeps the author date.

⭐ Most of this project is shell and CI YAML, and the failure mode that recurs is a payload crossing a shell boundary and losing its quoting. Prefer a quoted heredoc, anchor with absolute paths, and sanity-check any pattern before trusting its count.

Working rules

  • ⛔ One squashed commit per session. Work through tasks, then squash the session's work into a single commit before pushing. Set by the maintainer 2026-08-28, replacing the earlier "commit each task separately" rule.

  • ⛔ Push once, at the end. ⚠ The squash rule and allow_force_pushes: false are only compatible if nothing is pushed early. Pushing a first commit to get CI on it makes the squash impossible without a protection change, which is the maintainer's. Run the static suite and both linters locally instead, and let CI confirm the single commit.

    gh api repos/pkgforge-dev/docker-archlinux/branches/main/protection --jq '.allow_force_pushes.enabled'
  • ⛔ A completed TODO becomes a one-line ## Completed entry. Delete its TODO body. The detail lives in the HISTORY/ document the entry points at, never here. This file stays lean.

  • ⛔ A TODO keeps its full detail until it is done. Do not summarise unfinished work; that detail is what the next session acts on.

Safety

  • ⛔ Never overwrite a file you have not read to the end. Show the diff, or read the whole file first. A file in .tmp/ has no git history to recover from.
  • Never delete anything without the reason written down.
  • A found secret is reported, never fixed silently. Rotation comes first and is the owner's.
  • Nothing runs that writes outside the repository.
  • ⛔ Everything under .tmp/ is local scratch, excluded via .git/info/exclude, and must never be committed.

⚠ Two earlier safety rules are superseded and are recorded so nobody restores them by accident. "Never rewrite history" was overtaken by the deliberate rewrite in HISTORY/rewrite.md and by the squash rule above. "Never commit until the maintainer has seen the diff" was overtaken by the standing authorisation at the top of this file.


⛔ Honesty is mechanical, not a virtue

⛔ An agent that reports work it did not do is worse than one that does nothing, because the record becomes false and the next session builds on it. This has happened. A session claimed to have read this file in full having read about 85 percent of it, called three greps "three deep reviews", and pushed while saying the reviews were done.

⛔ These are checkable, so use them rather than trusting your own sense of having been thorough:

  1. Claiming you read a file means quoting it. State its line count and quote its last line. wc -l FILE and tail -1 FILE. If you read part of it, say which part, in line numbers.
  2. A review names what it opened and what it compared. A review that produces no finding must say what it ruled out. ⛔ A grep is not a review. A review that ran before the last edit is not a review of what shipped.
  3. Re-run every review after the final edit. If you edit after reviewing, the review is void. Renumbering, inserting a section and fixing a claim all count as edits.
  4. Never write that a task is done when part of it is not. Say what you skipped and why. ⭐ Reporting a gap costs one line. Hiding one costs the next session its footing.
  5. A checklist item is ticked against work you can point at, never against work you remember doing.
  6. ⛔ If the instruction and your plan disagree, say so before acting, not afterwards in a summary.

⛔ Prove it, do not describe it

⭐ The rule this whole file serves. A claim in a report is worth nothing. A test that fails when the thing breaks is worth everything.

⛔ not this ⭐ this
"the mirror lists are healthy" tests/static/40-mirrors-reachable.sh passes, and here is the output
"the bootstrap is not circular" a test that fails while Dockerfile names a pkgforge image
"signature checking is on" a test that fails when SigLevel = Never is injected
"the tags are correct" 24 assertions over scripts/tag-names, and a run that created them
"CI cannot publish a broken image" a dry run where one architecture was made to fail and no tag moved
"i686 cannot build" the command, the output, and what would unblock it

⛔ A test you have not seen fail is not known to work. For every test you add or touch, break the thing it guards and record the failure in HISTORY/tests-seen-to-fail.md.

⭐ This is not ceremony. Fault injection has found defects in the tests themselves, including an assertion that could never fire because the value it compared was a placeholder.

⛔ A number you cannot reproduce does not go in a document.


Completed

Each entry is a summary. The evidence column is what proves it.

⛔ This table is a ledger, not a status report. Every row was true when it was written and some have been overtaken: row 35 records both registries rising 161 to 176, and row 42 records 176 to 194. ⭐ "Where things stand" at the end of this file is what is currently true. Nothing here is renumbered or pruned, because review 17 recorded three reorderings in one session, each needing a manual cross reference remap.

# what evidence
1 Bootstrap is not circular: builds from the official Arch image pinned by digest, not from its own output tests/static/10-bootstrap-not-circular.sh
2 SigLevel = Required everywhere, no pacman-key patch. ARM keyring pinned by sha256 and master fingerprint bootstrap/keyrings/archlinuxarm.pin, tests/static/30-signature-checking-on.sh
3 Publish topology: one job per architecture pushing by digest, a merge job needing the whole matrix. A run that loses one architecture publishes nothing .github/workflows/build-deploy.yml
4 Cross-registry copy exercised against scratch repositories on both registries via the dry_run and dry_run_hub inputs, and both verified after publishing run 33001089986
5 Evidence file per platform: every package, version, size, sha256, release date scripts/gen-evidence, tests/image/40-evidence.sh
6 Three freshness workflows, all fired once runs 33001871256, 33001881101, 33001891317
7 Every fail in tests/ carries a reproduce: line, 196 of 196 tests/static/15-actionable-failures.sh
8 17 pipeline traps removed across six files tests/static/25-pipeline-traps.sh
9 The harness has a test that does not report through itself tests/static/05-harness.sh
10 Consumer contract: the eight things pkgforge/devscripts patches are asserted tests/image/50-consumer-contract.sh
11 ppc64le and i686 excluded, each with the measurement HISTORY/removed-architectures.md
12 Branch protection applied: one review, Static suite and linters required, no force pushes, enforce_admins: false HISTORY/maintainer-actions.md
13 History rewritten: main is one root commit, everything before it on history-archive tip 9d1e142 HISTORY/rewrite.md
14 Thirteen reviews, each with its lens, findings, and what it did not look at HISTORY/reviews/
15 All 79 upstream issues triaged, 19 in 14 classes measured against this image. Two defects found here and fixed HISTORY/defect-parity.md
16 ARM rollback decided: plain http mirrors stay, and a build whose anchor went backwards is refused. No state file; the floor is read from the public tag list, and vercmp comes from the digest already pinned in the Dockerfile scripts/check-anchor-floor, HISTORY/arm-rollback.md
17 Upstream issue 80 fixed with PATH untouched: a hook links an executable from the perl bindirs into /usr/local/bin, never shadowing a name that resolves tests/static/55-shipped-hooks.sh
18 Transfer policy: every fetch in scripts/, the workflows and bootstrap/any sets --connect-timeout and --max-time tests/static/65-fetch-policy.sh
19 Five mangled response shapes fed to resolve-anchor: zero byte, wrong compression, error page, truncated gzip, missing. It steps over all five tests/static/67-mangled-responses.sh
20 NoExtract reverted entirely after it broke a consumer. The image withholds nothing HISTORY/noextract-reverted.md
21 CI fixed: actionlint's -verbose writes to stderr, so the guard's grep failed on every run and the required check could never pass .github/workflows/ci.yml
22 /etc/machine-id was built in and shipped, so every container from one tag carried the same ID. Now truncated HISTORY/defect-parity.md
23 The docker runtime path verified in CI on all four architectures under QEMU dry run 33038310288
24 Six references mined with verdicts: the methodology template, pkgforge/devscripts, archlinux/archlinux-docker, fwcd/docker-archlinux, westandskif/rate-mirrors, lcpu-club/loongarchlinux-dockerfile HISTORY/references/
25 The architecture set is pinned: read from the build matrix and measured against every place that names it. 16 sites in 7 files, 9 of them previously silent. Sites are discovered, not listed tests/static/75-architecture-set.sh
26 loong64 clears both feasibility gates: signatures stay on, and a base install commits 137 packages under SigLevel = Required, at anchor 7.1.0.r9.g54d9411-2. The emulated build exits 0 and the image records loong64 with uname -m reporting loongarch64 HISTORY/loong64.md
27 A diagnostic that was empty in every case it existed for: resolve-anchor printed the first line of tar's output as the reason a database was unreadable, and tar puts a blank line ahead of what gzip said tests/static/67-mangled-responses.sh
28 Two scripts fetched without -f, so a mirror answering 403 returned exit 0 with an error page in the file, and the failure surfaced two steps later as a corrupt archive tests/static/65-fetch-policy.sh
29 The evidence race that failed the daily publish: the databases the build resolved against are exported from the build and read from there, so the evidence cannot be joined against a set that moved. The completeness check is unchanged HISTORY/evidence-race.md, tests/static/68-evidence-snapshot.sh
30 The loong64 port, the fifth architecture. The keyring installer is one script driven by bootstrap/keyrings/*.pin rather than one per port, and the pin asserts a set of fingerprints with their expiry dates and their validity after populate HISTORY/loong64.md, dry run 33165970427
31 A partial failure with five architectures publishes nothing. loong64 broken on a branch: its build fails, the other four still run, Create tags is skipped, and the real registries keep 161 tags run 33179363541, HISTORY/tests-seen-to-fail.md
32 The cross-registry copy exercised with five architectures on both registries. Both scratch indexes carry amd64 arm/v7 arm64 loong64 riscv64, and Docker Hub carries both loong64 tag spellings run 33178993776, HISTORY/tests-seen-to-fail.md
33 The evidence snapshot reverted in full and run in CI. gen-evidence still resolves by fetching, on all five architectures, so the revert path is exercised rather than reasoned about run 33180365462, HISTORY/tests-seen-to-fail.md
34 Aseem0xff/pacman-static mined under policy 11: verdict adopt, licence read, tracker read, and three facts in TODO 2 corrected against live upstream HISTORY/references/aseem-pacman-static.md
35 The first real publish since the evidence race fix and since loong64. Both registries rose 161 to 176, the same 15 tags, :latest and :v2026.08.28 list five platforms on both, and both loong64 spellings exist run 33184973002
36 A static pacman built from source for all eight architectures, every input pinned by sha256 or by commit, with a release workflow and a bootstrapping guide HISTORY/pacman-static.md, tests/static/85-pacman-static-pin.sh
37 The three PowerPC ports, taking the set to eight. One keyring pin serves all three, base plus base-any is two repository sections and two mirror lists, and the anchor is read from a Zstandard database. Each passes 66 of 66 image assertions, and a dry run builds all eight in CI HISTORY/powerpc.md, dry run 33195922986
38 The two false NoExtract comments, and two more the brief did not name, plus the stale assertion name re-recorded against a re-injected fault HISTORY/tests-seen-to-fail.md
39 A cross build that was not one: with QEMU in binfmt_misc autoconf answers "whether we are cross compiling: no" and runs target binaries, so the output depended on the build host's kernel state. --build is now passed and the binary's sha256 changed HISTORY/pacman-static.md
40 ArchPOWER's origin answers 403 to every GitHub runner, for every user agent, and 200 from a workstation. A read through proxy is a second Server, a second mirror line and a second listing source, and three scripts that read one and stopped now fall through HISTORY/powerpc.md, runs 33194671836 and 33195922986
41 docker/setup-qemu-action registers no big endian PowerPC emulator and says nothing. Ubuntu's qemu-user-static ships no binfmt descriptions either. multiarch/qemu-user-static pinned by digest does, and the F flag is asserted afterwards HISTORY/powerpc.md
42 The first real publish with eight architectures. Both registries rose 176 to 194 by the same 18 tags, :latest and :v2026.08.28 list eight platforms on both, all six PowerPC spellings exist in three shapes each, and the -2.2 anchor family is there run 33206327128
43 The cross-registry copy with eight. The Docker Hub scratch index went from five architectures to amd64 arm/v7 arm64 loong64 ppc ppc64 ppc64le riscv64 dry run 33201675295
44 Nothing noticed when the publish stopped. Two checks on two schedules, each the other's witness, every threshold derived from the cron it judges and nothing about a year, a list or a tag spelling written down HISTORY/publish-watchdog.md, tests/static/95-publish-watchdog.sh
45 The signed pacman tag is verified before the build starts, against a pinned tag object sha and two keyservers. Making the fingerprints load-bearing found the two signer names were swapped, and reading gpg's format found the check would have rejected a subkey signature bootstrap/pacman-static/sources.pin, HISTORY/pacman-static.md
46 Release assets for a consumer with no container tooling: a rootfs tarball, an OCI archive, a resolved package set and an evidence file per architecture, plus one manifest of every published tag .github/workflows/release.yml, HISTORY/releases.md, tests/static/96-release-assets.sh
47 pacman-static.yml on a GitHub runner, and the two defects it found: progress written to stdout was captured as part of zig's path, and every library build discarded the output that said so HISTORY/pacman-static.md, runs 33206329907 and 33208408451
48 kth5/archpower mined under policy 11: verdict confirms. Every PowerPC choice here is upstream's own, down to the base-any section name. Two beliefs corrected HISTORY/references/kth5-archpower.md
49 loong64 beyond the trust half: a package transfers from a shipped mirror inside the published image, its signature verifies, and all three shipped hooks fire. The keyring pin's expiry annotation forced and seen HISTORY/loong64.md
50 The dangling .tmp/ citations, and a test that stops them coming back tests/static/97-scratch-citations.sh
51 This file moved into the repository, so it is no longer one wiped directory away from being lost HISTORY/CONTINUE.md
52 The first release, v0.1.0: 50 assets, eight architectures, six families, verified by downloading three of them and checking them against the published SHA256SUMS. It took three runs and the two that failed published nothing run 33213236059, HISTORY/releases.md

TODO

⛔ The order is the order to take them in. Each task ends with something that can be pointed at: a test, a run, or a document with the command in it.

1. Done. See Completed rows 42 and 43.

⛔ The number is kept and the body is gone.

⭐ Measured after run 33206327128, all four closing conditions: both registries rose 176 to 194 by the same 18 tags; :latest and :v2026.08.28 list amd64 arm/v7 arm64 loong64 ppc ppc64 ppc64le riscv64 on both; all six PowerPC spellings exist in three shapes each; and the <alias>-7.1.0.r9.g54d9411-2.2 family exists, which is the PowerPC anchor and differs from the other five ports' -2.

2. What the static pacman still does not prove. Priority: medium

⛔ The task is row 36 in Completed and two of its four open items closed on 2026-08-29, rows 45 and 47. HISTORY/pacman-static.md carries the measurements.

⭐ 1. pacman-static.yml runs on a GitHub runner. Closed. Run 33210060549: eight architectures green, each with its evidence file, each reporting a real version (Pacman v7.1.0 - libalpm v16.0.1) and not NOT MEASURED, and each passing both gates. ⚠ It took three runs and found two defects that no workstation run could have found. HISTORY/pacman-static.md.

⭐ 3. The signed tag is verified. Closed, and the decision was to deepen rather than to drop. The pin gained the tag object sha and two keyservers, the fetch gained one shallow ref, and a VALIDSIG naming a pinned signer is required. ⚠ What it proves is bounded and is written next to it: the signature covers the 7.1.0 release point, not the nine commits after it that the pin actually builds.

⚠ Two remain.

2. ⛔ No bootstrap has been run end to end with the binary. tests/static/80-docs-claims.sh feeds every fenced block in the documentation to bash -n, so they parse. ⛔ Nothing executes them. The guide's central claim, that these commands produce a root that passes this repository's image suite, is the largest single gap left in this task.

WORK=/tmp/ps OUT=/tmp/dist scripts/build-pacman-static amd64
# then docs/bootstrap-with-pacman-static.md, both passes, ending in:
IMAGE=localhost/archlinux:bootstrapped PLATFORM=linux/amd64 EVIDENCE=/tmp/ev.json tests/run.sh image

⚠ Needs root, for chroot and mknod. ⛔ Read the guide's warning about the host's /dev before running any of it: a leaked bind mount plus an rm -rf deletes the host's device nodes. ⭐ Closed when the image suite passes against a root the static binary built. ⚠ A second session's worth on top of that: the guide's commands and whatever executes them are two copies, and nothing fails when they drift. The reference this was studied from has the same defect and names it as its own T-12.

⭐ 4. The release path. Closed. v0.1.0 exists with 50 assets, run 33213236059. ⛔ It took three attempts and the two failures are the useful part; HISTORY/releases.md records both, and the second produced a better design than the one it replaced.

⚠ What a release still does not exercise: a second one, so no upgrade path has been walked; a release cut when a per architecture tag is missing, which now fails by design; and podman load against the oci-<arch>.tar.gz assets, which are asserted only with gzip -t and a size floor.

⚠ A new pin with no watcher. .github/workflows/pacman-static.yml pins meson to an exact version, because the runner image's apt meson is 1.3.2 and zig linker support arrived in 1.6.0. Every other pinned thing in this repository has a freshness job; this one does not. ⛔ Policy 9 says it should.

⭐ The reference question the maintainer settled: all three references are kept and each has a role. HISTORY/references/static-pacman-reference-roles.md. ⛔ Do not put that history in README.md or docs/.

3. What the PowerPC ports still do not prove. Priority: low

⛔ The port itself is done and published. Completed rows 37 and 42: all three are in the matrix, each passes 66 of 66 image assertions, and all six tag spellings exist on both registries.

⚠ What is open:

  • ⛔ The origin refuses GitHub runners, and it is policy rather than a bot rule. repo.archlinuxpower.org answers 403 with a Cloudflare interstitial to every user agent tried from a runner, and 200 from a workstation. The ArchPOWER tracker records the maintainer blocking whole networks deliberately, issue 69: "After careful consideration I blocked all traffic from the Russian Federation". ⚠ Nothing upstream names GitHub's ranges or a CI runner, so that is a lead and not a diagnosis. ⛔ What it settles is the posture: treat api.rv.pkgforge.dev as a permanent second path, not a workaround waiting for upstream to fix something. HISTORY/references/kth5-archpower.md.
  • ⚠ One publisher. The proxy is a second transfer path, not a second publisher. Issue 140 names Link4Electronics/archpower-packages as an alternative package repository, and it was read and refused as a second trust root: an individual's uploads with no keyring and no signing story. TODO 6.
  • ⚠ Real hardware. Every measurement is qemu-user. 64 K pages on a real ppc64 host are where a static binary's segment alignment bites.

⭐ kth5/archpower is mined, verdict confirms, at 96e3c9b257a17e0aa2fb531e59cc45d7b6b6f2d6. Every PowerPC choice here is the one upstream makes for itself, down to the base-any section name. ⛔ It corrected one belief this file carried: that repository is a PKGBUILD collection with no infrastructure in it, so it is not where a layout change would appear first. The closest thing is cross-compilers/*/pacman.conf, which states the layout as a by-product. Re-read that on every bump.

⛔ Not yet mined, all under policy 11: kth5/archiso, lcpu-club/loongarch-packages, lcpu-club/loongshot/tree/main/scripts.

4. Emergency CVE patch path. Priority: medium

There is no way to publish faster than the daily cron, and no way to rebuild one architecture without rebuilding all of them.

Consider a workflow_dispatch input naming a single architecture and a reason, publishing only that architecture's tags and leaving the index alone until all agree. ⛔ The invariant it must respect: a run that loses one architecture publishes nothing. A single-architecture path is a deliberate hole in that, so it must be impossible to take by accident, and the index must not move until all architectures agree.

5. Mirror outage fallback. Priority: medium

Today, every mirror for one architecture being down fails the build, which is correct but total.

Consider a pinned last-known-good package set as a fallback producing an image with a loud annotation saying it is not current. ⚠ Weigh it against what a consumer pulling :latest gets. A stale image that says it is stale, in a label nobody reads, may be worse than a build that failed loudly and left yesterday's image in place. ⭐ Record the decision either way. ⚠ This overlaps TODO 2: a pinned package set is most of a bootstrap set.

6. Redundancy for the things with one of something. Priority: medium

single point today
the FROM digest one image, one registry
the Arch Linux ARM keyring one mirror path, one package name
the anchor package pacman only
the riscv64 pool six hand maintained servers in mirrors/riscv64.pool
GHCR as the digest staging area the whole publish depends on it
archlinux.org/mirrors/status/json/ scripts/gen-mirrorlist:22, the only amd64 pool source
raw.githubusercontent.com ARM mirrorlist scripts/gen-mirrorlist:23, the only ARM pool source

For each: is a second source possible, and is the failure loud?

⚠ The last two are known to fail. rate-mirrors issue 85 records the Arch status endpoint returning 429 and being unreachable during an infrastructure incident. The exposure is bounded because the generator is not part of any image build, and mirrors/<arch>.anchors is written whatever the pool source does.

7. Done. See Completed row 46.

⛔ The number is kept and the body is gone.

⚠ One thing the task did not ask about is now measured and is worth a decision. v0.1.0 is 3284077162 bytes, 3132 MiB, across 50 assets: eight rootfs tarballs, eight OCI archives, and the rest small. The OCI archive is a near duplicate of the rootfs tarball, since it is the same single layer plus a manifest and a config, and podman import on the tarball reaches most of the same place. Both are published because the maintainer chose the full asset list on 2026-08-29 after the redundancy was raised. HISTORY/releases.md carries the byte counts, so dropping one is a decision against a number.

8. Faster CI. Priority: low

⛔ The old baseline of 245 seconds does not reproduce. Re-measured 2026-08-28 against run 32992678276: end to end is 434 s, the longest build job is 335 s (riscv64) and the shortest is 142 s (amd64). Nothing in that run produces 245. HISTORY/reviews/16-the-release-as-one-unit.md.

Baseline, five builds in parallel, dry run 33165970427 on 2026-08-28: 416 s end to end, longest build step 265 s (loong64). ⭐ The fifth architecture cost nothing: the run is as long as its slowest job, and loong64 is not it. Optimise against 416, and say which derivation any new number uses.

gh run view 33165970427 --json createdAt,updatedAt --jq '"\(.createdAt) -> \(.updatedAt)"'
  • Native arm64 runners. vars.ARM64_RUNNER is unset and the matrix already reads it. ⚠ An unavailable label queues forever, so verify access first.
  • Skip publishing when nothing changed. The cron runs daily and tags by date, so an unchanged package set still mints a new tag for a byte-identical image. ⭐ The largest cheap saving available. Compare the resolved package set against the last published build and skip when it matches. ⚠ Cache the inputs, never the verdict. A cached "this passed" is not a pass.
  • A shared package cache across the matrix. Four jobs download overlapping sets. ⛔ But every job in one run must build against the same pinned set, or the architectures are no longer one coherent release.

9. loong64 beyond emulation. Priority: low

⚠ Every loong64 measurement is still QEMU. Two of the four items are closed; what remains is what emulation cannot answer and what one day cannot answer.

  • ⚠ Real hardware. Nothing has run on a LoongArch machine. A defect QEMU papers over, or one that only appears on real silicon, would not have shown in any of the work so far. ⛔ This is the only item here that needs a machine nobody involved has.
  • ⚠ Mirror stability over time is still unmeasured. All six answered on 2026-08-28. .github/workflows/freshness-mirrors.yml probes mirrors/loong64.pool monthly with the others, so the history accumulates from here. ⛔ That workflow has still never had a scheduled run: its cron is the 1st of the month and the history was rewritten on 2026-08-26. Read a few runs of it before trusting the pool.

⭐ Closed 2026-08-29, both against the published tag the 2026-08-28 publish created. HISTORY/loong64.md.

  • pacman -Syu from inside the published image syncs both databases, and installing a package transfers a real payload from a shipped mirror, verifies its signature under SigLevel = Required, and runs all three shipped hooks.
  • The expiry annotation was forced against a scratch pin and seen. ⚠ It also corrected two claims: the pin holds 10 trusted fingerprints, not 8, and two have already expired, on 2026-04-24 and 2026-07-14. The port is unaffected; the margin is smaller than anything had said.

10. Done. See Completed row 38.

⛔ The number is kept and the body is gone. Nothing renumbers: review 17 recorded three reorderings in one session, each needing a manual cross reference remap, and a missing 10 reads as a lost task where a shifted 11 reads as nothing at all.

⚠ What the task said and what was there differed. It named two false comments and one stale name. The same grep found four false statements and the stale name: the two it named, a section header and a failure reason in tests/image/50-consumer-contract.sh that both described a NoExtract strip with re-include lines that no shipped config has ever carried, and a stale decision 6 cross reference inside one of them. All five are fixed, and the renamed assertion was re-recorded against a re-injected fault rather than against the old output.

11. The items only the maintainer can apply. Priority: low

HISTORY/maintainer-actions.md is the list. ⛔ Do not apply these without being asked.

  • ⛔ The GitGuardian secret. Ask, do not create. Confirmed absent 2026-08-29: the repository holds DOCKERHUB_TOKEN and DOCKERHUB_USERNAME and nothing else. Once GITGUARDIAN_API_KEY exists, add the scanning workflow. ⛔ Do not add the workflow first: one that fails every run for a missing secret trains people to ignore a red mark.
  • ⛔ A freshness pull request carries no status check. Its run is created and held at action_required, because the pull request is opened with the built-in GITHUB_TOKEN. The required check never reports, so merging takes two deliberate actions. Fixing it needs a personal access token or a GitHub App installation token, which is a credential and so the maintainer's. HISTORY/maintainer-actions.md section 4.
  • default_workflow_permissions is write. Every workflow declares its own, so narrowing the default to read is safe in principle and untested in practice. Still write on 2026-08-29.
  • ⛔ history-archive is still unprotected. The whole rewrite rests on that one ref and it denies nothing. HISTORY/maintainer-actions.md section 3.
  • ⛔ template-adoption is gone from the remote and held 11 commits that were on no other branch. A git fetch --prune on 2026-08-29 reported it deleted, along with freshness/mirrors-20260826. ⚠ Neither was deleted by that session; the only branch it deleted was debug. The 11 commits survive at 8cf0ca698503ed09f153f1df2426b2414b4d4d1e in the working clone on the maintainer's workstation and nowhere else that is known. ⛔ Push it back or decide it is not wanted, but not neither: an unreferenced commit in one clone is lost the day that clone is cleaned.

⭐ Two items came off this list on 2026-08-29 and both were verified first:

  • The debug branch is deleted. It was checked to hold nothing unique before deleting, not asserted: git merge-base --is-ancestor origin/debug origin/history-archive is true, and git rev-list --count origin/history-archive..origin/debug is 0. Only main and history-archive exist now.
  • The fork relationship is gone. gh api repos/pkgforge-dev/docker-archlinux reports fork: false, parent: none, forks_count: 0. ⛔ Nothing in the repository changed for it, which is what was written down at the time.

12. Reviews. Priority: do last

⛔ Three is the floor, five is better, once the work is done and CI is green. Each review states its lens, what it looked at, what it found, and what it did not look at. ⭐ A review that finds nothing must say what it ruled out, or it is not a review. ⭐ Each carries a change summary: files touched, lines added and removed.

Twenty eight lenses are used, in HISTORY/reviews/. ⛔ Do not repeat them:

  1. a consumer who upgrades blind
  2. an attacker at build time
  3. the day upstream breaks
  4. a maintainer six months from now
  5. the tests themselves
  6. somebody auditing a repository with one commit
  7. a consumer who reads the package database
  8. the next session, starting cold
  9. an operator during a mirror outage
  10. a consumer whose transaction runs our hook
  11. adding the fifth architecture
  12. this file, read by somebody with no repository
  13. this file, checked line by line against the tree
  14. somebody who has to revert one of these releases
  15. a consumer arriving on loong64, with nothing to compare against
  16. the release as one unit, now that it is five things
  17. a report entering the record as though it were a measurement
  18. a fault injected in the wrong place
  19. a job that stops running
  20. a reference adopted on one reading
  21. the network a build runs on
  22. whoever holds the static pacman and no base image
  23. a task whose scope was understated
  24. a check that cannot see itself
  25. a consumer who never touches a registry
  26. the first run on a machine nobody has run it on
  27. a correction that was itself wrong
  28. the last session, and what the next one inherits

⭐ Lenses that fit the work still outstanding, none of them used: somebody restoring this repository from the registries alone, with no runner and no main; a dependency that was added to fix something, once api.rv.pkgforge.dev has been in the mirror lists long enough to have a history; and whoever pays for 3132 MiB of release assets per tag.

13. Done. See Completed row 50.

⛔ The number is kept and the body is gone.

14. Done. See Completed row 44.

⛔ The number is kept and the body is gone, so nothing renumbers.

⚠ One claim in the old body was false and the correction matters. It said the cron did not fire on 2026-08-28. It did, at 17:32:25Z, run 33195147714, and it succeeded. It published five architectures because the PowerPC commit landed twenty nine minutes later. The 45 day gap before 2026-08-27 is still unexplained; what is new is that a recurrence now turns something red. HISTORY/publish-watchdog.md.

Where things stand

Measured 2026-08-29 unless a date says otherwise.

⭐ main carries the rewritten history, one root commit plus the sessions since. The 2026-08-29 session added one commit. CI is green on it and both linters are clean.

⭐ Both registries hold 194 tags. They held 176 before run 33206327128, which was the first real publish with eight architectures. The 18 new ones are the three PowerPC ports in six spellings and three shapes each. :latest and :v2026.08.28 resolve to amd64 arm/v7 arm64 loong64 ppc ppc64 ppc64le riscv64 on both registries.

TOKEN=$(curl -s "https://ghcr.io/token?scope=repository:pkgforge-dev/archlinux:pull&service=ghcr.io" | jq -r .token)
curl -s -H "Authorization: Bearer $TOKEN" "https://ghcr.io/v2/pkgforge-dev/archlinux/tags/list?n=1000" | jq -r '.tags | length'

⭐ A publish that stops is now a red mark. Two checks on two schedules, each the other's witness, with every threshold derived from the cron it judges. HISTORY/publish-watchdog.md. ⛔ Neither can see every schedule stopping at once, which is what GitHub does to a repository idle for 60 days, and that is written down in three places rather than solved.

⚠ The cron did fire on 2026-08-28, and the previous brief said it did not. Run 33195147714, 17:32:25Z, green. It published five architectures because the PowerPC commit landed 29 minutes later. ⛔ The 45 day gap ending 2026-08-27 is still unexplained.

⭐ pacman-static.yml has run on a GitHub runner, run 33210060549, eight of eight green, each reporting a real version. ⚠ It took three runs. The two defects are in HISTORY/pacman-static.md and review 26, and both were invisible on the workstation.

⭐ The signed pacman tag is verified before the build starts. Making the pinned fingerprints load-bearing found that the two names beside them were swapped, and reading gpg's VALIDSIG format found the check would have rejected a valid subkey signature. bootstrap/pacman-static/sources.pin.

⭐ kth5/archpower is mined, verdict confirms. Every PowerPC choice here is upstream's own, down to the base-any section name. HISTORY/references/kth5-archpower.md.

⭐ loong64's transfer half is proven. A package downloads from a shipped mirror inside the published image, its signature verifies, and all three shipped hooks fire. HISTORY/loong64.md.

⭐ This file is in the repository now. It was .tmp/PROMPT_COMPLETION.md and is HISTORY/CONTINUE.md. ⛔ That means the static suite runs against it: a .tmp/ path named here needs the standing note, and tests/static/97-scratch-citations.sh fails without it.

⛔ Branch protection was turned off and restored. allow_force_pushes was the only field changed, and the settings were captured before and diffed after.

gh api repos/pkgforge-dev/docker-archlinux/branches/main/protection --jq '{force_push:.allow_force_pushes.enabled, reviews:.required_pull_request_reviews.required_approving_review_count, checks:.required_status_checks.contexts, enforce_admins:.enforce_admins.enabled}'

⛔ template-adoption is gone from the remote and held 11 commits that are on no other branch. Not deleted by this session. They survive at 8cf0ca698503ed09f153f1df2426b2414b4d4d1e in one working clone. HISTORY/maintainer-actions.md section 3 carries the recovery command. ⚠ This is the only record of it.

⚠ Loose ends, none blocking:

  • A second release has never been cut, so no upgrade path between two of them has been walked, and the oci-<arch>.tar.gz assets have never been loaded back with podman load. HISTORY/releases.md.
  • Five workflows have never fired on their own schedule and one has never run at all. Counted in review 26. Nothing is red; nothing has run either.
  • The bootstrap guide has never been executed. Its commands parse. TODO 2 item 2, and it is the largest unproven claim this project makes.
  • The meson pin in pacman-static.yml has no freshness job. Every other pinned thing does. Policy 9 says it should.
  • Upstream issue 103, the pacman sandbox on a kernel without landlock, is measured but not asserted: it needs a seccomp profile and a network install, which do not belong in the image suite. Not reproducible on pacman 7.1.0. Re-run by hand from HISTORY/defect-parity.md when pacman's major version changes.
  • Blob existence was sampled, not swept: 7 tags on each registry, with tag resolution swept in full on both. A full sweep is roughly 2600 requests per registry, which is a job rather than a measurement.
  • ⚠ The fetch path proof is one sample. Run 33180365462 shows gen-evidence still fetches and joins on a runner. It does not show the fetch path is safe: upstream did not move during it, and the race that failed 33094128354 had a 63 second window.