<!-- Closed Linux refactor items, archived 2026-09-09. OPEN work lives in refactoring-backlog.md. -->

# Refactoring backlog archive — 2026-09-09

Closed on 2026-09-09 and moved out of
[`refactoring-backlog.md`](refactoring-backlog.md), which is OPEN-only.
Each entry keeps the evidence that closed it.

**One entry, and a build closed it.** VK2 had been open since 2026-09-05 with the
standing note that only a real chain could settle it. Two sdk runs settled it:
`sdk-20260908-132426` for aarch64 and `sdk-rv64-20260908-211949` for riscv64.

**Read the numbers here as dated, not current.** Two of the figures in the first
draft of this entry did not survive an adversarial re-derivation — the
`x86_64/bin` count and the composition of its delta were both quoted before
either directory had been listed. Both were re-measured against the pushed
digest before this file was written; the method is stated with each.

---

## VK2. CLOSED — 20/20 on both foreign arches, and a fifth defect nobody had named [M, ★★★]

**What closes this entry**, as the entry itself required: `<arch>/bin` carrying
everything `x86_64/bin` does that is not structurally host-only.

    #20 4328.1 [INFO] Vulkan cross-targets riscv64: 20/20 component(s) built
    #20 3705.1 [INFO] Vulkan cross-targets aarch64: 20/20 component(s) built

riscv64 at `out/build-logs/sdk-rv64-20260908-211949/sdk-riscv64.log:20826`
(a live 4328 s build, pushed `@sha256:09a4d255`); aarch64 at
`out/build-logs/sdk-20260908-132426/sdk-arm64.log:21316` (3710 s, pushed
`cross-sdk-arm64@sha256:de6eaad2`). 20 = the 17 `_VK_TARGET_COMPONENTS` rows plus
the three hardwired ones (loader, SPIRV-Tools, glslang); the counter has exactly
four increment sites and the logs carry 20 distinct `Cross-building <label>`
lines, name for name.

### The four named defects: all four diagnoses held, all four fixes landed

1. **`vulkan-profiles` / jsoncpp PIC** — `-DCMAKE_POSITION_INDEPENDENT_CODE=ON`
   moved into `_cross_build_sdk_component`'s fixed args rather than the one row,
   since any archive handed to the SDK can end up inside a layer `.so`.
2. **`gfxreconstruct` header bridge** — `_vulkan_setup_sdk_includes` now bridges
   `X11 xcb GL KHR EGL GLES2 GLES3`. `KHR` was not optional: `GL/gl.h` and
   `glcorearb.h` include `<KHR/khrplatform.h>`.
3. **`vulkancapsviewer` / raw loader path** — `-DVULKAN_LOADER_INSTALL_DIR="${archdir}"`
   in the `vulkancapsviewer)` arm of `_vulkan_target_dynamic_args`. The
   "still unproven" note in the open entry — that AUTOMOC/AUTORCC under
   `QT_HOST_PATH=/usr` had never run on a foreign arch — is now proven both ways:
   `[2/17] Automatic RCC for vulkancapsviewer.qrc` on riscv64,
   `[17/17] Linking CXX executable vulkanCapsViewer` on aarch64.
4. **`slang` / vendor copy step** — `_xbuild_extra_targets+=(copy-gfx-slang-modules)`,
   the helper extension the entry called the largest of the four.

**Two of the entry's own diagnoses had been wrong** and are recorded as such: slang
was never a Canadian-cross problem (it was a prebuilt Dawn WebGPU zip upstream ships
for x86_64 and aarch64 only, whose arch cascade `FATAL_ERROR`s on riscv64 even with
the backend off), and vulkancapsviewer was never missing target Qt6 in the way the
entry described.

### The fifth defect, which no route in VK2 had named

riscv64 sat at **19/20** through two further runs after all four fixes were in.
`vulkancapsviewer` alone kept failing, at `find_package(Qt6)`, traced to

    install_target_packages: 'qt6-base-dev:riscv64' apt-get exited 100
    libappstream5:riscv64 : Depends: libcurl3t64-gnutls:riscv64 (>= 7.63.0) but it is not going to be installed
    libproxy1v5:riscv64   : Depends: libcurl3t64-gnutls:riscv64 (>= 7.16.2) but it is not going to be installed

Every package in that message existed in ports at a satisfying version, so the
error named a symptom and not a cause. The cause was a **host/target apt pocket
asymmetry**: the compiler stage wrote `ubuntu.sources` for amd64 **without**
`-security` and `ubuntu-ports.sources` **with** it. `libcurl3t64-gnutls` is
`Multi-Arch: same`, so amd64 topped out at `8.18.0-1ubuntu2.4` while riscv64's
candidate was `8.18.0-1ubuntu2.5`, and no common version existed in apt's view.

A/B on `ghcr.io/kataglyphis/kataglyphis_beschleuniger:base`, same image, one line
different:

| amd64 stanza | amd64 candidate | riscv64 candidate | `qt6-base-dev:riscv64` |
| --- | --- | --- | --- |
| without `-security` | 8.18.0-1ubuntu2.4 | 8.18.0-1ubuntu2.5 | `E: Broken packages` |
| with `-security`    | 8.18.0-1ubuntu2.5 | 8.18.0-1ubuntu2.5 | installs |

Nothing about it was Qt-specific or riscv64-specific: it disabled **every**
`Multi-Arch: same` library that has ever had a security-only upload. The full
account, including why `archive.ubuntu.com` made the `0` cost-free, is
[`cross-build-verification.md#host-and-target-apt-sources-must-expose-the-same-pockets`](cross-build-verification.md#host-and-target-apt-sources-must-expose-the-same-pockets).

**Why aarch64 reached 20/20 without the fix**: its run ended 16:39, before the
skew had anything to bite on for that lane. It was timing, not immunity — the
same host file, the same `Multi-Arch: same` rule. That is why VK5 (below, in the
live file) asks for one confirming aarch64 run rather than treating the earlier
20/20 as current.

### What the shipped bytes say

Both `bin` directories listed inside a container from the pushed
`@sha256:09a4d255` — not derived from the log, which never enumerates the
tarball-seeded x86_64 tree:

* `x86_64/bin` **52** entries, `riscv64/bin` **37**.
* riscv64 is a strict **subset**: zero entries exist there that x86_64 lacks.
* The 15-entry delta is exactly `dxa dxa-3.7 dxc dxc-3.7 dxl dxl-3.7 dxopt
  dxopt-3.7 dxr dxr-3.7 dxv dxv-3.7 llvm-tblgen vkconfig vkconfig-gui`.
* `spirv-remap` is in **neither** tree, so `-DENABLE_SPVREMAPPER=OFF` on the cross
  glslang costs no parity. (It was raised as a concrete reason to doubt
  subset-ness before the directories were listed; listing them settled it.)
* Spot-checked ELF machine in `riscv64/bin`: `vulkanCapsViewer`, `slangc`,
  `gfxrecon-info`, `vulkaninfo` — all **RISC-V**.

The 15 are what LunarG's prebuilt x86_64 tarball carries and **no** arch builds
from source: `vkconfig`, `Vulkan-Configurator` and `dxc` appear nowhere in
`vulkan.sh` (0 hits, case-insensitively, in 912 lines). That is a never-asked
question rather than a regression, and it is now VK4 in the live file.

### What this cost, and the lesson

Four rebuild attempts of the sdk stage. The three that ended 19/20 each had a
green static battery behind them. **No gate in this repo can see this class**: the
two sources files are individually valid, the skew is invisible to a same-arch
build, and it only bites once a `Multi-Arch: same` package receives a
security-only upload. The `mirror-consistency` gate now asserts the pair rather
than either file, and `cross_align_host_apt_pockets` repairs an inherited skew at
the point of use so a stage need not be rebuilt to benefit.

---

## VK4 + VK5. CLOSED — all three arches ship 52 binaries [S, ★★]

**Measured on the pushed digests, both directories listed inside a container.**
Not derived from a log.

| | amd64 | arm64 `@sha256:6eefc3c3` | riscv64 `@sha256:1bdfbb3a` |
| --- | --- | --- | --- |
| `bin/` | 52 | **52** | **52** |
| `share/vulkan/explicit_layer.d` | 9 | **10** | **10** |
| entries amd64 has and the arch lacks | — | **0** | **0** |

The sets are *identical*, not merely equal in size — zero entries in either
direction. `dxc`, `vkconfig`, `vkconfig-gui` and `llvm-tblgen` report ELF machine
AArch64 and RISC-V respectively, so nothing is a copied host binary. Both foreign
arches are AHEAD on layers: only they ship `VkLayer_khronos_timeline_semaphore`.

`Vulkan cross-targets riscv64: 24/24` and `aarch64: 24/24`, with zero
`unavailable on <arch>` lines in either log.

### The gap was a question nobody had asked

LunarG's `./vulkansdk` builds **24** components under `all`. The HOST list in
`_vulkan_build_components` named **18**. A component not named there is never
checked out, and `_vulkan_target_install_component` returns at its `-d` test
*before* incrementing `_vk_attempted` — so the three missing ones were never
counted as attempted, and the verdict line read a clean `N/N`. That is why it
survived months of green runs.

The fix was four table rows (`dxc`, `vulkantools`, `crash-diagnostic-layer`,
`yaml-cpp`) plus three dynamic-arg arms, and the count now equals the vendor's
own `build_all` flag count: 21 rows + 3 hardwired = 24.

### Two traps that would have faked success

* **`vulkantools` would have reported BUILT with no vkconfig in it.** Upstream
  does `find_package(Qt6 ... QUIET)` and, when Qt6 is missing, skips the entire
  configurator with a `message()` and **exits 0**. `_vk_ok` would have
  incremented. `-DCMAKE_REQUIRE_FIND_PACKAGE_Qt6=TRUE` turns that into an honest
  failure; the shipped ELF proves Qt6 really was found.
* **`dxc` failed the first run, and NOT because of riscv64.** Configure
  completed — LLVM 3.7 does know the host triple, upstream PR #4894 carries. It
  died compiling `external/SPIRV-Tools/source/util/timer.h` on GCC 16's
  `-Warray-bounds`, promoted by `-Werror`. `_vulkan_target_build_spirv_tools` in
  the same file had carried `-DSPIRV_WERROR=OFF` for that identical warning since
  it was first diagnosed; DXC vendors its **own** copy of SPIRV-Tools, which
  never saw the flag.

**That fix saved both lanes, not one.** The rebuild logged 164 `array-bounds`
warnings on riscv64 and **93 on aarch64** — arm64 would have died at the same
header. It only surfaced on a foreign arch because GCC inlines differently on
x86_64, where the host build passed.

### What it cost, and what it leaves

Four sdk builds. VK5 is closed in the same breath: arm64's previous 20/20 was
measured against a tree that no longer existed, and the rebuild settles it on the
current one.

Left behind, both small and both owner decisions: **VK6** (13 shared libraries
the foreign arches do not get, caused by our own `ENABLE_OPT=OFF` /
`SPIRV_CROSS_SHARED` flags) and **VK7** (11 DXC files the foreign arches ship
that the vendor prunes from the tarball).

---

## VK6 + VK7. CLOSED — the foreign prefixes are 2 files from amd64, and both are explained [S]

**Measured inside a container on the pushed digests, both `lib/` trees listed.**
Not derived from a log.

| | amd64 | arm64 `@sha256:f77f97fa` | riscv64 `@sha256:028ce048` |
| --- | --- | --- | --- |
| `lib/` | 118 | **123** | **123** |
| `bin/` | 52 | 52 | 52 |
| layers | 9 | 10 | 10 |
| only amd64 has | — | **2** | **2** |

The gap VK6 opened at **72** is now **2**, identically on both arches:
`VulkanLoader` (a layout difference the consumers already assume: the foreign
prefixes keep `libvulkan.so*` flat) and `libshaderc_util.a` (below).
`libglslang.so`, `libSPIRV.so` and `libspirv-cross-c-shared.so` report ELF
machine AArch64 and RISC-V respectively.

### VK6: the entry's own fix would have made it worse

`BUILD_SHARED_LIBS` is **exclusive, not additive**. Flipping it ON would have
gained 9 files and LOST 6, because glslang guards its static installs of
`MachineIndependent`, `GenericCodeGen` and `OSDependent` behind
`if(NOT BUILD_SHARED_LIBS)`. The vendor configures glslang **twice** into one
prefix, and the order is load-bearing: the second install owns
`lib/cmake/glslang`, so the STATIC pass must land last or `find_package(glslang)`
silently starts describing SHARED imported targets. Both passes are now in
`_vulkan_target_build_glslang`, and a test asserts the order rather than just
their presence.

`SPIRV_CROSS_SHARED=ON` is the separate, genuinely additive half.

**`libshaderc_util.a` is deliberately NOT replicated.** It has no `install()`
rule anywhere — the vendor copies it by hand after `cmake --install` — and
amd64 ships **no** libshaderc_util headers, so the archive is unlinkable as
shipped even there. Copying it would be file-name parity for something nobody
can use.

Also left alone, and worth knowing: our `libglslang.so` is still not the same
library as amd64's, because we pass `ENABLE_OPT=OFF` and the vendor does not.
That is a deeper difference than a file list and belongs in its own measured
change.

### VK7: mirrors the vendor's own prune, with the guard that matters

`_vulkan_target_prune_nonsdk_dxc` deletes exactly what `clean_nonsdk_files`
deletes under `BUILD_DXC=1` — four include trees and four libs, all
arch-relative. It is guarded on `include/dxc/dxcapi.h`, the vendor's own marker
that a dxc install landed in THIS prefix. Without that guard the helper would
`rm -rf include/llvm` out of whatever directory it is handed, and
`/opt/llvm-target/include/llvm` is 41 MB of real LLVM 23 headers that
`LLVMConfig.cmake` and 1.3 GB of `libLLVM*.a` resolve through. Four mutations,
one of them on that guard.

### VK5 closed with them

arm64's earlier 20/20 was measured against a tree that no longer existed. Both
foreign arches have now been rebuilt from the current one and report
`24/24 component(s) built` with zero `unavailable on <arch>` lines.
