diff --git a/docs/todo/INDEX.md b/docs/todo/INDEX.md
index 6d70ef7..1dfdc67 100644
--- a/docs/todo/INDEX.md
+++ b/docs/todo/INDEX.md
@@ -12,15 +12,15 @@ nowhere else. This file is a list, not an order and not a log.
| | open | partially done | done | total |
|---|---|---|---|---|
| measurement | 5 | 1 | 0 | 6 |
-| infrastructure | 7 | 0 | 2 | 9 |
+| infrastructure | 6 | 0 | 3 | 9 |
| blocked | 7 | 0 | 2 reclassified | 9 |
-| **total** | **19** | **1** | **2 done, 2 reclassified** | **24** |
+| **total** | **18** | **1** | **3 done, 2 reclassified** | **24** |
By priority, across the open and partially-done rows:
| priority | count |
|---|---|
-| high | 6 |
+| high | 5 |
| medium | 7 |
| low | 0 |
| blocked (no priority, because a machine unblocks these rather than a decision) | 7 |
@@ -45,7 +45,7 @@ By priority, across the open and partially-done rows:
| T-15 | [A corpus test with a fresh process per library](infrastructure.md) | infrastructure | medium | open |
| T-16 | [Delete the path that would mask the failure](infrastructure.md) | infrastructure | medium | open |
| T-17 | [The IBT property note is documented and is not there](infrastructure.md) | infrastructure | medium | open |
-| T-18 | [There is no release](infrastructure.md) | infrastructure | high | open |
+| T-18 | [There is no release](infrastructure.md) | infrastructure | high | done |
| B-01 to B-09 | [the blocked list](blocked.md), nine rows. ⚠ **Two of them are not blocked**; the correction is at the foot of that file and the work is T-02 and T-06 | blocked | n/a | 7 open, 2 reclassified |
---
diff --git a/docs/todo/PROGRESS.md b/docs/todo/PROGRESS.md
index 7bf71bb..0a23b0a 100644
--- a/docs/todo/PROGRESS.md
+++ b/docs/todo/PROGRESS.md
@@ -8,63 +8,95 @@
## Where the work is right now
-[v0.2.6](https://github.com/pkgforge-dev/cross-libc-dlopen/releases/tag/v0.2.6) is
+[v0.2.9](https://github.com/pkgforge-dev/cross-libc-dlopen/releases/tag/v0.2.9) is
published.
| workflow | latest on `main` |
|---|---|
| `gates` | ✅ |
| `secret-sweep` | ✅ |
-| `release` | ✅ on `v0.2.6` |
-
-**This branch fixes the host lookup that stopped on a library the loader would
-refuse, on branch `fix/gl-fwd-wrong-class`, and needs a release once merged.
-`v0.2.6` was released from `main` before it, so no released artefact carries
-the fix.**
+| `release` | ✅ on `v0.2.9` |
+
+`v0.2.9` adds the three NixOS host directories: `/run/opengl-driver/lib` and
+`/run/current-system/sw/lib` join the shim's fixed host list, the assembled
+`--library-path`, and the libva `
/dri` list. NixOS is neither FHS nor a
+host that writes `/etc/ld.so.conf`, so before this each of the three failed
+differently and silently.
+
+⚠ **The three cases ran in the container; no NixOS host was behind them.** E104,
+E105 and E106 create `/run/opengl-driver/lib`, which needs root, so the container
+suite runs all three and reports MATCH, which is why the local total is higher
+than the previous release's. Where `/run` is not writable each SKIPS by name
+instead. ⛔ **Nothing in this tree has run the shims on a NixOS host.** The cases
+prove the lists carry the directories and that a library placed there is found;
+they do not prove the directories are where a real NixOS system puts its GPU
+userspace, and
+[`../report/10-measured-versus-assumed.md`](../report/10-measured-versus-assumed.md)
+does not yet carry that distinction. Writing it there is open work, not a
+citation.
+
+The mechanism was also confirmed here as an A/B independent of the suite: the
+fake-libva contract run against the `v0.2.8` source prints `NO-DRIVER`, and
+against the shipped source prints `DRIVER=NIX`. The published binaries carry
+the directory strings.
+
+`v0.2.8` remains the first release carrying the ppc64le resolver frame repair
+(PR #60). `v0.2.7` and earlier ship a `gl-fwd.so` whose `glfwd_resolve_asm`
+saved r5 and r6 where the C callee writes its caller's LR and TOC slots, so the
+first call of any GL entry point taking a third or fourth integer argument was
+handed garbage. Issue #59 is the report.
---
## ⛔ The work order
-### 1. `glfwd_try_soname` must not stop on a library the loader will refuse
+### 1. `glfwd_try_vendor` accepts a matching filename without checking its class
+
+`glfwd_try_soname` skips a candidate of the other ELF class, and
+`glfwd_try_vendor` does not. A host whose only vendor library is the wrong
+class is therefore reported as having one. It is a different path, reached only
+when the bundle carries a dispatcher, so it needs a bundled-dispatcher case to
+measure. Nothing in this session's change touches it.
-`glfwd_try_soname` took the first directory in which its own soname existed, and
-`glfwd_each_dir` stopped the walk there. A candidate of the other ELF class
-cannot load, so a host whose own answer named a 32bit library directory first
-produced a target that could not load, and the directory after it was never
-reached. Reproduced by removing `libGL.so.1` from the host-drivers demo AppDir,
-which forces the host path, and naming the 32bit directory first with
-`CROSS_LIBC_DLOPEN_GL_HOST_DIR`.
+### 2. Wire `make gl-fwd-qemu-check` into a runner
-The repair reads the candidate's ELF ident and skips a candidate of the other
-class, so the walk continues. `docs/report/09-the-second-boundary.md` 9.21 has
-the transcript, E103 is the case, and `docs/report/08-test-results.md` owns the
-new totals.
+The case that caught the ppc64le frame exists and fails on the old source, but
+nothing runs it: `gates.yml` builds ppc64le and runs
+`scripts/verify-artifacts.sh`, which never executes a trampoline. The target is
+run by hand today. ⚠ This is issue #59's first loose end and it is the reason
+that class of bug reached a release at all.
-### 2. What is still open
+### 3. The stale totals in `docs/ground-truth.md`
-`glfwd_try_vendor` accepts a matching filename without checking its class, so a
-host whose only vendor library is the wrong class is reported as having one. It
-is a different path, reached only when the bundle carries a dispatcher, and it
-needs a bundled-dispatcher case to measure. Nothing in this session's change
-touches it.
+It still states the evidence table as 22/22 (issue #45). The home for the
+totals is [`../report/08-test-results.md`](../report/08-test-results.md), which
+carries the current pair. This is the last document of the release-shaped
+staleness class that issue #46 opened, and T-18 is closed.
---
## ⚠ What a new session should distrust
-- **E103 forces the order through `/etc/ld.so.conf.d`, and a host whose own
- answer puts the 64bit directory first never reaches the defect.** Debian 10
- is such a host: `libc6-i386` names `/usr/lib32` and `/lib32` in
- `zz_i386-biarch-compat.conf`, which sorts after `x86_64-linux-gnu.conf`. So
- the case pins the predicate, not a claim about any one distribution.
-- **The host-drivers demo AppDir bundles the glvnd dispatcher, so the shim
- takes the bundled path on a glvnd host and never reaches the host lookup.**
- Removing `libGL.so.1` from that AppDir is what exposes the path, and the
- deletion is a harness step rather than a change to any shipped AppImage.
-- **A Qt6 GLX path does not link `libGL.so.1`.** `libqxcb-glx-integration.so`
- and `libQt6Gui.so.6` need `libGLX.so.0` and `libOpenGL.so.0`, so which arm a
- Qt application reaches depends on what else in its AppDir links
- `libGL.so.1`. Check `readelf -d` before assuming.
-- **`docs/todo/INDEX.md` carries the counts it carried before this session.**
- Reconcile them if an entry moves.
+- **The scheduled `appimage-suite` on `main` is red, and the failure is
+ upstream's, not this tree's.** Both architectures fail at
+ `experiments/41-extract.sh` with `no dispatcher slot in AppDir/lib.`: the
+ `demo` release of `Anylinux-AppImages` no longer carries
+ `cross-libc-dlopen.so` or `foreign-dlopen.so` in `lib/`. The extraction
+ refuses rather than guessing, which is what it was built to do. Verify against
+ the upstream asset before treating it as a defect here.
+- **The release gate is not the wide suite.** `release.yml` runs the evidence
+ table on x86_64 and aarch64 and then builds all five architectures. It does
+ not run `run-appimage.sh`, so a green `v0.2.9` does not mean the AppImage
+ suite is green.
+- **The NixOS directories are a host class nobody has run.** The three cases
+ prove the lists carry the directories and that a library placed there is
+ found. No NixOS host has been behind the shims, and
+ [`../report/10-measured-versus-assumed.md`](../report/10-measured-versus-assumed.md)
+ is where that distinction belongs if it is not already written there.
+- **A tag's tree, not its commit, is what CI tested.** `v0.2.8` and `v0.2.9`
+ are both squashed commits, so their hashes appear in no run. Check
+ `git rev-parse ^{tree}` against the PR head's tree before concluding a
+ tag went untested.
+- **`docs/todo/INDEX.md`'s counts are checked by hand.** `sh scripts/check-drift.sh`
+ reconciles the rows against the entries and does not check the totals, so a
+ future closure moves numbers no gate reads.
diff --git a/docs/todo/infrastructure.md b/docs/todo/infrastructure.md
index deaceae..4cf930a 100644
--- a/docs/todo/infrastructure.md
+++ b/docs/todo/infrastructure.md
@@ -378,7 +378,7 @@ cites exists, and every module a tool imports is reachable from that tool's own
- **Source.** The operator, at the end of the port session.
- **Category** infrastructure, **Priority** high, **Effort** medium,
- **Status** open
+ **Status** done
- **Problem.** `scripts/build.sh` produces verified artefacts and a manifest,
and nothing publishes them. A consumer who wants `gl-fwd.so` has to build it,
which means having a container engine and this repository.
@@ -397,3 +397,51 @@ cites exists, and every module a tool imports is reachable from that tool's own
- **Prove.** A release exists whose assets' checksums match
`build-manifest.json` for both architectures, and whose body was not written
by hand.
+
+### Correction: the title was true when it was written and has not been since v0.1.0
+
+The title is kept because it is how this entry has been cited everywhere else.
+The premise above is superseded, and the correction is written here rather
+than edited into it.
+
+`v0.2.8` is published, and so are the eight releases before it. The workflow
+that publishes them is [`release.yml`](../../.github/workflows/release.yml),
+which builds **five** architectures, not the two the premise names: x86_64,
+aarch64, riscv64, ppc64le and loongarch64. The aarch64 premise in particular is
+disproved on every release: the `build and package aarch64` job cross-compiles
+it on the floor and the `evidence table (aarch64)` job runs the suite on
+`ubuntu-24.04-arm`.
+
+**Acceptance command and its output**, run against the published release:
+
+```console
+$ gh release list --repo pkgforge-dev/cross-libc-dlopen --limit 3
+v0.2.8 Latest v0.2.8 2026-10-22T10:48:20Z
+v0.2.7 v0.2.7 2026-09-24T18:29:17Z
+v0.2.6 v0.2.6 2026-09-13T10:01:17Z
+
+$ gh release download v0.2.8 --repo pkgforge-dev/cross-libc-dlopen
+$ for a in x86_64 aarch64 riscv64 ppc64le loongarch64; do
+> sha256sum -c cross-libc-dlopen-$a.sha256 --quiet && echo "$a: ok"
+> done
+x86_64: ok
+aarch64: ok
+riscv64: ok
+ppc64le: ok
+loongarch64: ok
+
+$ tar -tf cross-libc-dlopen-x86_64.tar | grep -c /
+0
+```
+
+The release carries 40 assets. Every one of the five `.sha256` files verifies
+against the assets it names, both archives per architecture are flat (no
+nested directory, as the entry required), and the manifest inside the archive
+matches the loose files beside it. The body was generated by
+[`../../scripts/release-notes.sh`](../../scripts/release-notes.sh) from
+`build-manifest.json`, so no checksum in it was typed.
+
+⚠ **What the closure does not prove.** The ancestor refusal in `release.yml`
+passes on every tag because every tagged commit is on `main`; it has never been
+seen to refuse a tag that was not. That remains T-10's subject, not this
+entry's.