Regenerated daily at 06:00 UTC by scripts/gen-matrix-status.py (workflow:
.github/workflows/matrix-status.yml). It pushes only when a cell moves. Every
cell traces to the run that proved it.
This page infers nothing. A combination that no run has tested reads as
untested, not as a blank.
That distinction is the point of this document. Before it existed, "no result" and "a pass" looked identical from outside. Three defects then survived for weeks, because the absence of evidence read as evidence of absence.
| Meaning | |
|---|---|
| ✅ | A named run asserted it and passed |
| ❌ | A named run asserted it and failed |
| ⬜ | Never tested. Not a pass. Not a fail. No data at all. |
We raised the bar on 2026-08-17: green means the image works, not that it
built. GREEN-CRITERIA.md defines the criteria, and
.github/green-criteria.yml is the machine
readable source. The criteria are these: the desktop is present and it starts,
the image boots to a session, the ISO installs, the installation completes,
update and rollback work, the image matches upstream, it omits no package in
silence, it still rebuilds, and every architecture resolves.
One rule makes the list mean anything: skipped, never-tested and stale all count as not green. The axes below hold the evidence for each criterion.
A pass on one axis says nothing about the others. This caught us out repeatedly, so the page states it first.
| Workflow | Proves | Does not prove |
|---|---|---|
| Build | The image builds and pushes | That it boots |
| Desktop Contract Sweep | The published image's desktop packages/session/DM actually landed (verify-desktop-experience.sh against the pulled image, no boot) |
That it boots, or that the session actually starts on real hardware |
| LUKS E2E | ISO boots, installs to an encrypted disk, the installed system boots and unlocks | That a desktop session starts, or that the installer GUI works — it drives fisherman over SSH |
| Installer smoke | The compositor starts, autologin works, the installer frontend is running | That the install completes |
| Live overlay | The live payload (installer flatpak, autologin, polkit) builds for that variant | That it works at runtime |
A variant can pass LUKS E2E and still ship an ISO nobody can use, because LUKS
E2E never looks at the screen. yellowfin:cosmic was exactly that: LUKS green
on 2026-07-23, and nobody had once looked at its installer GUI.
A variant can also pass Build and still ship the wrong desktop entirely,
with nothing else here in place to catch it. marlin:kde published with no
/usr/share/wayland-sessions/ at all: the AUR-only input-remapper package
failed the whole kde package set in silence (tunaOS#858). Neither LUKS E2E
nor Installer smoke asks which desktop is present, only that something
boots. Desktop Contract Sweep is the axis for exactly that gap, and it needs
no boot.
The configured image matrix now has 147 cells. Eleven of them are Tuna Desktop (formerly Roost) cells for amd64: Marlin, Yellowfin, Albacore, Skipjack, Wahoo, Bonito, Bonito Rawhide, Grouper, Flounder, Flounder Sid and Sailfin. The guppy:gnome cell is back (2026-10-07, #2450). This status snapshot is from before these additions and does not mark them green; each needs its own image, desktop and boot evidence.
Generated by scripts/gen-matrix-status.py — do not edit this block by hand. It changes only when a cell moves, so the git history of this file records when each combination flipped.
.github/green-criteria.yml sets the bar. A cell is green only when every criterion that blocks it, and applies to it, has a current affirmative result. A criterion that nobody ran, nobody asserted, or that CI skipped renders ⬜, and it does not count as satisfied. These criteria block today: builds, desktop, boots, parity, no_silent_omissions. These are advisory — the sections below measure them, and they do not block yet: install, lifecycle, rebuildable, arch_honesty. Unimplemented: iso. To graduate a criterion, edit enforcement: in that file. This table and the README count then tighten with no code change.
124 of 147 published cells are composite-green.
| Variant | gnome | kde | cosmic | niri | xfce |
|---|---|---|---|---|---|
| albacore | ✅ | ✅ | ✅ | ✅ | ✅ |
| bonito | ✅ | ✅ | ✅ | ✅ | ✅ |
| bonito-rawhide | ⬜ | ✅ | ✅ | ✅ | ✅ |
| flounder | — | ❌ | — | — | ❌ |
| flounder-sid | ⬜ | ⬜ | — | — | ⬜ |
| grouper | ❌ | ❌ | ❌ | — | ❌ |
| guppy | ❌ | ✅ | — | — | ✅ |
| hummingbird | ⬜ | — | ❌ | — | — |
| marlin | ✅ | ✅ | ✅ | ✅ | ✅ |
| sailfin | ✅ | ✅ | ✅ | ✅ | ✅ |
| skipjack | ✅ | ✅ | ✅ | ✅ | ✅ |
| wahoo | ⬜ | ⬜ | ⬜ | — | — |
| yellowfin | ✅ | ✅ | ✅ | ✅ | ✅ |
Cells outside the desktop columns (base, hwe, nvidia and friends) are in the count above but not the table; only builds applies to them today.
Per-cell provenance — which run asserted which criterion, when — is machine-readable in matrix-provenance.json, regenerated with this document.
52 of 52 cells clean (52 read, 0 never read).
Green criterion 8 (no_silent_omissions): the sweep runs checks/verify-package-wishlist.sh against every published image it pulls. That is the same gate new builds pass at build time. So an image that drops a package in silence, outside package-miss-allowlist.txt, reads ❌ here even when it predates the gate. A cell whose image the sweep never read — no image, a pull error, a lost job — is ⬜, not clean.
| Variant | gnome | kde | cosmic | niri | xfce |
|---|---|---|---|---|---|
| albacore | ✅ | ✅ | ✅ | ✅ | ✅ |
| bonito | ✅ | ✅ | ✅ | ✅ | ✅ |
| bonito-rawhide | ✅ | ✅ | ✅ | ✅ | ✅ |
| flounder | — | ✅ | — | — | ✅ |
| flounder-sid | ✅ | ✅ | — | — | ✅ |
| grouper | ✅ | ✅ | ✅ | — | ✅ |
| guppy | ✅ | ✅ | — | — | ✅ |
| hummingbird | ✅ | — | ✅ | — | — |
| marlin | ✅ | ✅ | ✅ | ✅ | ✅ |
| sailfin | ✅ | ✅ | ✅ | ✅ | ✅ |
| skipjack | ✅ | ✅ | ✅ | ✅ | ✅ |
| wahoo | ✅ | ✅ | ✅ | — | — |
| yellowfin | ✅ | ✅ | ✅ | ✅ | ✅ |
43 of 52 cells at parity (49 measured, 3 never measured).
Green criterion 7 (parity), first cadence: package-parity.yml runs scripts/package-parity.sh --audit daily, and audits every desktop package set against its own base. That shape exposes a build that applies no desktop at all (#858). ❌ covers BROKEN (no more packages than the base) and suspect (fewer than 25 added). A diff against each variant upstream reference comes next, and nothing asserts it yet.
| Variant | gnome | kde | cosmic | niri | xfce |
|---|---|---|---|---|---|
| albacore | ✅ | ✅ | ✅ | ✅ | ✅ |
| bonito | ✅ | ✅ | ✅ | ✅ | ✅ |
| bonito-rawhide | ✅ | ✅ | ✅ | ✅ | ✅ |
| flounder | ❌ | ❌ | — | — | ❌ |
| flounder-sid | ⬜ | ⬜ | — | — | ⬜ |
| grouper | ❌ | ❌ | ❌ | — | ❌ |
| guppy | ✅ | ✅ | — | — | ✅ |
| hummingbird | ✅ | ❌ | ✅ | ❌ | — |
| marlin | ✅ | ✅ | ✅ | ✅ | ✅ |
| sailfin | ✅ | ✅ | ✅ | ✅ | ✅ |
| skipjack | ✅ | ✅ | ✅ | ✅ | ✅ |
| wahoo | ✅ | ✅ | ✅ | — | — |
| yellowfin | ✅ | ✅ | ✅ | ✅ | ✅ |
13 of 52 cells green (52 tested, 0 never tested).
The denominator is the set luks-e2e.yml schedules: every published desktop image (build_image), not only the ones that ship an ISO. That is wider than the ISO matrix below on purpose. The browser ISO builder can make an ISO from any image, so the image-only variants (sailfin, guppy, flounder-sid) need boot and install coverage too.
| Variant | gnome | kde | cosmic | niri | xfce |
|---|---|---|---|---|---|
| albacore | ❌ | ❌ | ❌ | ❌ | ❌ |
| bonito | ❌ | ✅ | ✅ | ❌ | ✅ |
| bonito-rawhide | ❌ | ✅ | ✅ | ❌ | ❌ |
| flounder | ❌ | ❌ | ❌ | — | ❌ |
| flounder-sid | ❌ | ❌ | — | — | ❌ |
| grouper | ❌ | ✅ | ❌ | — | ❌ |
| guppy | ❌ | ❌ | — | — | ❌ |
| gurnard | — | — | — | — | — |
| hummingbird | ❌ | — | ❌ | — | — |
| marlin | ❌ | ❌ | ✅ | ✅ | ❌ |
| sailfin | ❌ | ❌ | ❌ | ❌ | ❌ |
| skipjack | ❌ | ❌ | ✅ | ❌ | ✅ |
| wahoo | ❌ | ❌ | ❌ | — | — |
| yellowfin | ❌ | ✅ | ✅ | ❌ | ✅ |
NVIDIA cells are out of scope for this workflow — luks-e2e.yml excludes them deliberately, because -nvidia takes the identical LUKS path in headless QEMU. 39 stale pre-exclusion result(s) remain from before that change; they are not a gap and will age out.
The table above still shows a result for flounder:cosmic, flounder:gnome. .github/build-config.yml no longer declares those flavours, so luks-e2e.yml cannot schedule them and no run will ever turn them green. They are excluded from the count above — a last-measured verdict kept visible, not a gap. Same reasoning as the NVIDIA note.
Newest result 2026-10-01, oldest still-authoritative result 2026-08-06. Results older than the most recent round of fixes are the best available data, not current data.
50 of 52 cells satisfy build_scripts/checks/verify-desktop-experience.sh (52 tested, 0 never tested).
The sweep pulls the published image and runs the contract script against it directly (podman run, with no boot). It uses the same denominator as LUKS E2E above: build_image, limited to the five desktop flavors. This catches a desktop whose packages never landed, and it does not depend on whether hosted CI can boot the image at all.
| Variant | gnome | kde | cosmic | niri | xfce |
|---|---|---|---|---|---|
| albacore | ✅ | ✅ | ✅ | ✅ | ✅ |
| bonito | ✅ | ✅ | ✅ | ✅ | ✅ |
| bonito-rawhide | ✅ | ✅ | ✅ | ✅ | ✅ |
| flounder | — | ✅ | — | — | ✅ |
| flounder-sid | ✅ | ✅ | — | — | ✅ |
| grouper | ✅ | ✅ | ✅ | — | ✅ |
| guppy | ❌ | ✅ | — | — | ✅ |
| hummingbird | ✅ | — | ❌ | — | — |
| marlin | ✅ | ✅ | ✅ | ✅ | ✅ |
| sailfin | ✅ | ✅ | ✅ | ✅ | ✅ |
| skipjack | ✅ | ✅ | ✅ | ✅ | ✅ |
| wahoo | ✅ | ✅ | ✅ | — | — |
| yellowfin | ✅ | ✅ | ✅ | ✅ | ✅ |
Newest result 2026-10-17.
39 of 52 cells green (52 tested, 0 never tested).
Validates bootc image update, rebase, rollback, alias resolution, and post-switch system contracts across published stream deployments.
| Variant | gnome | kde | cosmic | niri | xfce |
|---|---|---|---|---|---|
| albacore | ✅ | ✅ | ✅ | ✅ | ✅ |
| bonito | ✅ | ✅ | ✅ | ✅ | ✅ |
| bonito-rawhide | ❌ | ❌ | ❌ | ❌ | ❌ |
| flounder | ✅ | ✅ | — | — | ✅ |
| flounder-sid | ❌ | ❌ | — | — | ❌ |
| grouper | ✅ | ✅ | ✅ | — | ✅ |
| guppy | ✅ | ✅ | — | — | ✅ |
| gurnard | — | — | — | — | — |
| hummingbird | ❌ | ❌ | ❌ | ❌ | — |
| marlin | ✅ | ✅ | ✅ | ✅ | ✅ |
| sailfin | ✅ | ✅ | ✅ | ✅ | ✅ |
| skipjack | ✅ | ✅ | ✅ | ✅ | ✅ |
| wahoo | ❌ | ❌ | ❌ | — | — |
| yellowfin | ✅ | ✅ | ✅ | ✅ | ✅ |
Newest result 2026-10-15.
29 of 33 non-NVIDIA ISO cells have ever been tested — 88% coverage. 0 of those pass.
This is the only axis that checks whether a human could install the image. For 4 combinations, nobody has confirmed the installer appears on screen.
| Variant | gnome | kde | cosmic | niri | xfce |
|---|---|---|---|---|---|
| albacore | ❌ | ❌ | ❌ | ❌ | ❌ |
| bonito | ❌ | ❌ | ❌ | ❌ | ❌ |
| bonito-rawhide | ❌ | ❌ | ❌ | ❌ | ❌ |
| flounder | ❌ | ⬜ | — | — | — |
| grouper | ❌ | ⬜ | — | — | ⬜ |
| gurnard | — | — | — | — | — |
| hummingbird | ❌ | ❌ | ❌ | ❌ | — |
| marlin | ❌ | ⬜ | — | — | — |
| skipjack | ❌ | ❌ | ❌ | ❌ | ❌ |
| yellowfin | ❌ | ❌ | ❌ | ❌ | ❌ |
cosmic, niri, xfwl4 and kde do not bring a session up on hosted CI. Nobody has diagnosed the cause -- gnome starts on the same guest, which has a render node but no 3D. See Known systemic gaps.
53 tags published.
Missing for 4 ISO cell(s): gurnard-pantheon, hummingbird-base, hummingbird-cosmic, hummingbird-gnome
The run that last asserted each verdict above. A re-run of a cell moves a row here without moving the cell, so this table refreshes only when a verdict changes. Treat the dates as "no older than".
| Date | Run | Cells |
|---|---|---|
| 2026-10-17 | 39030481433 | 52 |
| 2026-10-15 | 38713238238 | 112 |
| 2026-10-08 | 37771441491 | 1 |
| 2026-10-01 | 36875090451 | 11 |
| 2026-10-01 | 36873532817 | 98 |
| 2026-09-10 | 34487443208 | 5 |
| 2026-09-10 | 34479027185 | 1 |
| 2026-09-10 | 34459513536 | 5 |
| 2026-09-03 | 33737812664 | 5 |
| 2026-09-01 | 33506738063 | 15 |
| 2026-08-20 | 32335146969 | 2 |
| 2026-08-19 | 32238167029 | 38 |
NVIDIA is out of scope, not failing. luks-e2e.yml excludes -nvidia on
purpose (commit 5c730e9). Those flavors take the identical LUKS path in
headless QEMU, so a test there proves nothing about NVIDIA. The stale results
above predate that change, and they will age out.
The honest position: NVIDIA is untested, not broken. No workflow today can
test it, because a real test needs a host with an NVIDIA GPU. That is a
different problem from the render-node gap above, and iso-e2e-gpu.sh does not
solve it.
We plan this work; we did not abandon it. NVIDIA coverage waits on GPU
capacity from the AWS grant, through runs-on. Two steps follow that capacity:
add -nvidia back to the matrix filter in luks-e2e.yml, then point it at
those runners. tuna-os#848 comes first, because no stage-3 flavor can
build a dev ISO at all today. Until then, read the ⬜ and stale cells above as
not yet covered, not as a backlog of broken cells.
The boot gate for cosmic/niri/xfwl4 is out of scope, because it cannot
start. This failure differs from everything else in this section. The problem
is not a compositor that will not render. It is a job that never runs.
verify_boot routes those flavors to the GPU runner group for a real DRM
render node. The account vCPU limit for that instance bucket is 0, so AWS
rejects the fleet request and no runner ever attaches:
Failed to launch runner: failed to create fleet: VcpuLimitExceeded
... vCPU limit of 0 ... AllowedInstanceTypes:["g4dn*"]
Two independent runs confirm it: 33848074014 job 101038352430 on main, and
33948918141 job 101266051600 on a branch. Each ends No files were found ... verify-out/serial.log. No serial log, no screenshot, no compositor, because no
step ran.
The scoring gave those cells ❌ on boots, which asserted a boot failure
nobody observed. green-criteria.yml now puts them out of scope for that
criterion, next to -asahi. Read that as not measured: ⬜ means measured
and unproven, ❌ means measured and failed, and only the first holds here. The
exclusion moves no cell to green, because every one still fails another
blocking criterion. It reverts the moment g4dn capacity exists
(tuna-os#2123). A test holds the excluded prefixes equal to the
workflow routing, so the two cannot drift apart.
Not four of five. Four of five are measured, and three of them START. This section used to say that cosmic, niri, xfwl4 and kde all need a DRM render node, that GitHub runners have none, and that "no configuration change alters this". Measurement from inside the guest, not from the runner host, contradicts every part of that.
What we measure today:
| flavor | compositor | starts? | installer window | what actually fails |
|---|---|---|---|---|
| gnome | gnome-shell | ✅ | mapped, drives | nothing — green (run 32704425971) |
| kde | kwin_wayland | ✅ | mapped, page=welcome |
does not advance past welcome |
| cosmic | cosmic-comp (Smithay) | ✅ | mapped, page=welcome, OCR matches |
does not advance past welcome |
| xfce | xfwl4 (Smithay) | ❌ | — | aborts: err=NoRenderNode |
| niri | niri (Smithay) | ? | ? | never measured with these diagnostics |
The niri row has no measurement for a blunt reason: on albacore the image
never built, so nothing existed to measure. install-zirconium.sh points
greetd at dms-greeter for every niri image, and niri.sh installed the DMS
suite only for AlmaLinux Kitten and CentOS Stream 10. albacore is plain
AlmaLinux 10, so it got the greeter config without the greeter. The el10 list
in niri.yaml ships no wallpaper daemon either, because
test_niri_wallpaper_daemon.bats expects el10 to "pass through the dms
branch". albacore passed through neither:
FAIL: greetd launches dms-greeter but /usr/share/quickshell/dms-greeter/DMSGreeter.qml is missing
FAIL: no wallpaper daemon (swaybg/swww/wpaperd) and no dms — background will be blank
TUNAOS_BRANDING_NIRI_FAIL variant=albacore failures=2
The build then died after three attempts, and published no image. Both ISO legs refused to build from a tag that still held a previous one. That is why niri was the only desktop red on BOTH architectures, where the rest were red on arm64 alone. The missing installer Flatpak was the arm64 half; this was the amd64 half.
The exclusion cited "Qt 6.10+", and plain AlmaLinux 10 ships qt6-qtbase-6.10.1,
so the condition was narrower than its own reason. Every EL10 variant now takes
the DMS branch. This makes reality match a contract the repo had already
written down: verify-desktop-experience.sh says "Fedora and EL10 use DMS
(quickshell) while openSUSE uses the wlroots stack". albacore was the one EL10
variant where that was false.
The missing arm64 installer Flatpak was not the only blocker. Once it cleared, the arm64 ISO stayed red, because a second and independent arm64 bug sat underneath. The distinction is worth a record, because the first fix looked like a failure.
Run 33997358111,
albacore:kde, dispatched precisely because its amd64 ISO already passed and
only arm64 failed. The Flatpak now installs — Installing app/org.tunaos.InstallerKde/aarch64/master — where it died before on
error: Nothing matches org.tunaos.InstallerKde in remote tuna-os. The build
then got four steps further and stopped somewhere new:
++ bash /run/tbox-customize/1/installer-recipe-backend.sh
ERROR: cannot determine the bootc backend of this image.
No bootupd payload, no composefs pin in prepare-root.conf,
and no systemd-boot EFI binary.
The image is not unclassifiable. The published layers of
ghcr.io/tuna-os/albacore:kde-linux-arm64, read straight out of the registry,
hold a complete legacy bootupd payload:
usr/lib/bootupd/updates/EFI/almalinux/grubaa64.efi
usr/lib/bootupd/updates/EFI/almalinux/shimaa64.efi
usr/lib/bootupd/updates/EFI/BOOT/BOOTAA64.EFI
That is branch 1 of the probe. The probe spelled the filename grubx64.efi,
and the EFI binary suffix is the EFI architecture: x64 on x86_64, aa64 on
aarch64. So both copies of the probe missed every branch on every arm64 image
and took the deliberate "never guess" exit. The two copies live in
live-iso/common/src/installer-recipe-backend.sh and in
TUNAOS_BACKEND_PROBE_SH in scripts/lib/backend.sh. Both now glob the names,
which is what the branches always meant to ask.
The test file shows why this reached production. Every fixture in
test_installer_recipe_backend.bats built an x64 tree, so a suite that covered
all four branches proved nothing about half the matrix. The fixtures now take
the EFI arch as a parameter, and the suite asserts the aarch64 shapes next to
the x64 ones.
With the probe fixed (run 34001369641,
same cell), Build Live ISO passed on arm64 for the first time and produced a
5.7 GB albacore-kde-10.2-aarch64.iso. The boot gate then failed in 36 seconds:
==> Accel: tcg, CPU: max, MEM: 4096M, CPUS: 4
qemu-system-aarch64: type is NULL
qemu-system-aarch64: Virtio VGA not available. Perhaps you want to install qemu-system-modules-opengl package?
ERROR: QEMU failed to daemonize
iso-e2e.sh already chose the aarch64 emulator, the virt machine and AAVMF
by host architecture. Its display device was still -vga virtio, shorthand for
virtio-vga: a virtio-gpu behind a VGA-compatible PCI region, which exists
only on targets with legacy VGA. The virt machine has virtio-gpu-pci, and
the script now uses that there. QEMU's package hint is wrong, because the step
before it had installed qemu-system-modules-opengl. The packages the runner
installed show the mechanism (qemu-system-common and
qemu-system-modules-opengl 8.2.2+ds-0ubuntu1.18, arm64). They ship
hw-display-virtio-vga.so, but the built-in modinfo table in the aarch64
binary registers only the virtio-gpu family: hw-display-virtio-gpu,
-gpu-pci, -gpu-gl and -gpu-pci-gl. So the type lookup for virtio-vga
returns nothing and the file on disk never loads, where virtio-gpu-pci does.
Two things stay on record as unmeasured, not assumed. First, the gate ran
under TCG (Accel: tcg), because hosted ubuntu-24.04-arm runners expose no
/dev/kvm. The next run measures whether a KDE live ISO reaches its readiness
marker within the 900 s timeout on four emulated vCPUs. Second,
scripts/run-walkthrough.sh is still x86-only: qemu-system-x86_64, OVMF,
-machine pc, -vga virtio. It is advisory in the ISO job, so arm64 cells get
no installer screenshots until it learns the same three things.
Three of the four measured desktops come up and render on a GPU-less runner. Only xfwl4 aborts.
A generalisation published earlier in this branch — "kwin degrades to
software, the Smithay compositors abort" — is WRONG and is withdrawn here.
cosmic-comp is Smithay and it starts, renders 9/9 non-blank frames, maps its
installer window and reaches the welcome screen. Whatever stops xfwl4 is
specific to xfwl4, not a property of its toolkit.
Smoke run 32681262659,
yellowfin:xfce, read from the live session itself:
crw-rw----+ 1 root video 226, 1 card1
crw-rw-rw-. 1 root render 226, 128 renderD128
[drm] pci: virtio-vga detected at 0000:00:02.0
[drm] features: -virgl +edid -resource_blob -host_visible
The guest has a render node. What it lacks is 3D: the two ends never
negotiate virgl, because the host QEMU has no node of its own to pass through.
Those are different claims, and the evidence supports only the second. Somebody
collected the earlier eglinfo evidence on the runner HOST, which says nothing
about what the guest can do.
gnome is the counter-example, and we now measure it instead of an assumption. On
the same run it boots. gnome-shell starts through the software path in Mesa
(libEGL warning: egl: failed to create dri2 screen), the installer frontend
launches and stamps a window, and the walkthrough captures 9 non-blank frames
across 8 distinct visual states. So a compositor CAN come up on this hardware.
Run 34006244094
(main @ 1735625f, both probe and display fixes merged) is the first run in
which an arm64 live ISO went all the way through its own gate:
| step | iso:kde (linux-amd64) |
iso:kde (linux-arm64) |
|---|---|---|
| Provenance gate (this run published the image) | pass | pass |
| Build Live ISO | pass, 6 min | pass, 5.5 min |
Boot gate (scripts/iso-e2e.sh) |
pass, 15 min 18 s, KVM | pass, 15 min 21 s, TCG |
| Checksum, sign, verify | pass | pass |
| Upload signed ISO artifact | pass | pass |
| Installer walkthrough (advisory) | cancelled at 03:18Z, see below | no-op (x86-only script) |
| Upload to R2 | skipped (walkthrough cancelled first) | cancelled at 03:18Z, see below |
The TCG number answers the open question from the previous section. A KDE live
ISO on four emulated aarch64 vCPUs reaches its readiness marker in about 15
minutes, inside the 900 s boot-gate budget with margin. The image legs, the
manifest, the signature, the KVM boot gate and the desktop contract all passed
on the same run. Attest SBOM failed on Sigstore, the #1560 shape, and
Promote still ran, by design.
The fan-out cancelled itself. Once kde went green, we dispatched gnome,
xfce, cosmic and niri back to back at 03:18Z. Every generated
build-<variant>.yml used concurrency.group: build-<variant>-${{ github.ref }}
with cancel-in-progress: true, so four dispatches on main became four
members of one group. Runs 299, 300 and 301 (gnome, xfce and cosmic, in
dispatch order) died at birth. The newest one also cancelled the kde run above,
which still had the amd64 walkthrough to capture and the arm64 ISO to upload
to R2. The gates had already passed, and only the two trailing steps were lost.
The group now varies per run for workflow_dispatch
(${{ github.event_name == 'workflow_dispatch' && github.run_id || github.event_name }}),
the same rule iso-e2e.yml and live-iso-bootc.yml already followed, and
tests/test_expensive_builds_collapse_redundant_pr_runs.py asserts it for
every workflow .github/build-config.yml generates. gnome, xfce and cosmic are
therefore still unmeasured on the arm64 ISO axis: not failed, never run.
niri fails its own gate, on every architecture, before any ISO exists. Run 34008659578 is the first albacore:niri build under the #2349 branding gate, and the gate did its job. All three image legs (amd64, amd64-v2, arm64) end with:
ok: greeter command dms-greeter present
FAIL: greetd launches dms-greeter but /usr/share/quickshell/dms-greeter/DMSGreeter.qml is missing
FAIL: no wallpaper daemon (swaybg/swww/wpaperd) and no dms — background will be blank
TUNAOS_BRANDING_NIRI_FAIL variant=albacore failures=2
The ISO jobs then failed at the provenance gate, because no image reached the
registry. That is the correct consequence. The cause lies in upstream packaging, not
in the package list in the manifest. The transaction installed exactly what the
manifest asks for: dms-greeter 1:1.6.0-1.el10 and quickshell-git from the
avengemedia/danklinux COPR, and dms and dms-cli 2:0.0.git.4694 from
avengemedia/dms-git. None of those RPMs carries a .qml file. The
filelists.xml of each COPR for epel-10-x86_64 says so: dms-greeter holds
/usr/bin/dms-greeter plus sysusers, tmpfiles and completions, dms-cli holds
/usr/bin/dms, and dms is a shell of .desktop, service and icon files.
The 1.6 line provisions the greeter and shell QML at runtime, through
dms-greeter enable and dms-greeter sync. So an image built from those COPRs
can never satisfy an offline check for DMSGreeter.qml, and the "no dms"
branch of the background check trips on the same missing directory. The
tunaos-packages recipes (dms, dms-cli, dms-greeter, quickshell)
install the QML payload where the gate looks
(usr/share/quickshell/dms-greeter, usr/share/dankmaterialshell), and they
target el10. The factory status still lists all four as needed, not built,
for el10/x86_64 and el10/aarch64. Until somebody publishes them, the el10
niri cell has no COPR-free path to green, and its COPR path is red by
construction. The niri gate issue filed with this run tracks it.
This page recorded kde as the flavor whose greeter and autologin session "both
die instantly", on run 30234237855, and credited the render-node theory. That
entry stood because the smoke capture named only greetd and cosmic-greeter
in its journal line. kde runs sddm, so the capture held no unit for kde and
nobody ever read what it said.
Smoke run 32704425971 is the first run that read it. kde comes up:
sddm-helper: pam_unix(sddm-autologin:session): session opened for user liveuser
sddm-helper: Starting Wayland user session: "/etc/sddm/wayland-session" ...
session 1: VTNr=1 Seat=seat0 Type=wayland Class=user Active=yes State=active
A properly seated session on seat0, VT 1. And the checks agree:
ok - compositor running (kwin_wayland)
ok - installer frontend launched (org.tunaos.InstallerKde)
ok - kde: installer is frontmost at session start
ok - kde: screen is not blank
ok - kde: screen changes between steps
kwin hits the same missing DRM node as everyone else —
kwin_wayland: Failed to open drm node : No such file or directory
kwin_wayland: couldn't find dev node for drm device
libEGL warning: egl: failed to create dri2 screen (everywhere, llvmpipe)
— and carries on regardless. That is the difference that matters. kwin degrades to software, and xfwl4 aborts. An earlier draft of this sentence said "the Smithay compositors abort". cosmic-comp is Smithay and does not abort, so the split does not follow the toolkit. See the table above. "These desktops need a render node" is not one claim about four desktops. It is false for kde and gnome, and for xfwl4 it describes an abort, not an absence.
kde fails one step later:
not ok - installer readiness stamp present
not ok - kde: reached 'welcome' screen (and disk, encryption, summary, install, done)
The frontend runs and the screen paints, but nothing identifies an installer
window and OCR matches no screen in the spec. One file would settle which: the
installer's own debug log, which records do_activate called, the window class
it built, and any traceback. The capture had never collected that file on
this flavor, because it read ~/.var/app/org.bootcinstaller.Installer/...,
which is the gnome app id. kde is org.tunaos.InstallerKde. So the capture
printed "(no installer-debug.log found)" on four of five flavors, and that
string reads like a fact about the guest when it is a fact about the path. The
path is fixed, and the next kde run carries the log.
Both come up. Both map the installer window on the welcome page. Neither gets past it.
cosmic, run 32718219267:
ok - compositor running (cosmic-comp)
ok - installer frontend launched (org.tunaos.InstallerCosmic)
ok - cosmic: screen is not blank # 9/9 frames above stddev
ok - cosmic: reached 'welcome' screen # seen on visual state(s) [0, 1]
not ok - cosmic: reached 'disk' screen # not found on any state the installer advanced to
# 1/8 transitions changed >500px; primary action activated with 'spc'
# 2 distinct visual state(s) across 9 frames
Its readiness stamp agrees: app_id=org.tunaos.InstallerCosmic signal=first-frame page=welcome. The kde stamp says the same thing,
window=ApplicationWindow signal=frame-swapped page=welcome. It matched no OCR
keyword at all.
So the open question for both is not the render path, the seat, or DRM.
The keyboard drive in the walkthrough — ret, then spc, then a wider focus
search — does not move these frontends off their first page. gnome advances
7/8 transitions on the same harness. Nobody has yet established whether the
walkthrough is the limit or the two frontends hold a real defect, and this page
does not guess.
greetd reports only its own bookkeeping: greeter exited without creating a session, five times, up to start-limit-hit. So the compositor's own reason
went nowhere for five rounds of this. The live adapters now run the session
under systemd-cat -t tunaos-live-session, and smoke run
32691426582
is the first run to capture what xfwl4 itself says:
INFO xfwl4: Starting xfwl4 on a tty using udev
INFO xfwl4::backend::udev: Using renderD128 as primary GPU
INFO smithay::wayland::socket: Created new socket name=Some("wayland-1")
INFO xfwl4::core::state: Listening on wayland socket name="wayland-1"
WARN smithay::backend::drm::device::fd: Unable to become drm master, assuming unprivileged mode
INFO smithay::backend::drm::device: DrmDevice initializing
INFO smithay::backend::egl::display: Successfully selected EGL platform: PLATFORM_GBM_KHR
INFO smithay::backend::egl::display: EGL Initialized
INFO smithay::backend::egl::display: EGL Version: (1, 5)
WARN xfwl4::backend::udev::device: failed to initialize gpu err=NoRenderNode
ERROR xfwl4: Failed to initialize primary GPU node
So the compositor starts, it finds the render node and names it, it
opens a Wayland socket and listens, and it initialises EGL on GBM. It then dies
at err=NoRenderNode, from the udev device init in Smithay. That happens
after it selects renderD128, and after it fails to become DRM master.
So "these desktops need a DRM render node" is worse than unproven. The
compositor's own log contradicts it: the node is present, and xfwl4 names the
one it picked. The open question is now specific and much smaller. Why does
Smithay report NoRenderNode for a device it has just selected, and is the
earlier Unable to become drm master the cause or a separate symptom? This
page does not guess, because the previous five attempts guessed.
The same log shows a second and independent defect. It is a packaging bug, not a graphics one:
INFO xfwl4::core::config::xfwl4_config: Failed to reload defaults:
/usr/local/share/xfce4/xfwl4/defaults does not exist; using hard-coded defaults
WARN xfwl4::core::util::rc: Missing value for required setting box_move
... ~45 more required settings missing ...
A distribution package does not install data under /usr/local/share, so
xfwl4 ships and then looks for a defaults file its own packaging never puts
there. This is not the fatal error, and it deserves a fix on its own.
kde failed the same way on run 30234237855: the plasmalogin autologin session
and its greeter both exit at once (plasmalogin-helper exited with 5), in a
restart loop. Somebody collected the eglinfo output cited next to it — no
EGL_EXT_device_drm at all — on the runner HOST, not in the guest. It says
nothing about the environment kwin_wayland runs in, and this page no longer
offers it as evidence.
gnome is no longer an assumption. This page described it as "the only desktop still thought verifiable without a render node", and nobody had tested it, because the smoke matrix had never run it. It has now run, on 32681262659: it boots, the compositor and installer frontend both come up, and the walkthrough captures 9 non-blank frames over 8 visual states. It reaches welcome, encryption and summary; it does not reach the disk screen, which was a keyword gap in the screen contract — the contract had been measured against the other four frontends and never against this one.
Two real defects that run surfaced, neither of them about graphics:
- The installer starts behind the GNOME Activities overview. The
walkthrough had to press
escbefore it could drive anything, and records it as a failure precisely because a user booting this ISO sees the same thing. Reproduced on runs32450214451and32681262659. e2e-installer-gui-checks.shhad never executed. It resolved its TAP helpers to a path the guest does not have, so every assertion wascommand not foundand the harness printed the shell's 127 as a count of failures. The per-flavor compositor and frontend assertions have therefore never run, on any flavor, in any smoke run.
scripts/iso-e2e-gpu.sh runs the harness on a GPU host and is the intended
answer; somebody must drive it by hand today.
Screenshots on the GPU path. screendump returns Error: no surface once a
guest scans out through virgl, and attaching VNC does not rescue it — the VNC
client path does. iso-e2e.sh handles this as of #844; note that
installer-walkthrough.py, run-walkthrough.sh and the weekly screenshot
workflows still call screendump directly and will silently produce nothing on
a GPU host.
Confirmed working, so nobody re-debugs it. The plasmalogin autologin
drop-in (#833) is being read — run 30234237855 shows
pam_unix(plasmalogin-autologin:session): session opened for user liveuser
before the session dies on the GPU limitation above. Autologin is not the kde
blocker.
Failures that look like successes. Recurring shape, worth checking for in any new harness code:
-
pgrep -f <pattern>run viabash -cmatches its own command line. The bracket trick ([t]una-installer) is the fix;installer-smoke.ymldocuments it and other call sites still do not use it. -
gh pr checksrenders a cancelled run asfail. A superseded run is not a broken build. -
After a partial
workflow_dispatch, a bare tag such asguppy:basemay still point at the previous build. Checkorg.opencontainers.image.versionbefore concluding anything from it. -
install_availableskips unresolvable packages by design, so missing packages never fail a build. They surface only in/usr/share/tunaos/missing-on-*.txtinside the built image, which nothing reads on a schedule. Three package gaps were found this way in one morning:tunaos-packages#120,#121,#122. -
A failing install cell blames the registry when the real fault is 90 lines earlier.
iso-e2e.shprobes the ISO's embedded offline store and falls back to a network pull when it misses. That fallback fails after four retries ten minutes apart, so the last line of the log is a TLS timeout against ghcr.io — 40 minutes after the actual mistake, which was that the probe looked for the wrong name. Nine cells in run30249218614looked like a flaky registry and were not; all nine had already passed their live-ISO checks. One grep separates the two:passing: ==> Found canonical offline image ghcr.io/... — bootcDirect failing: ==> No local image in offline store, falling back to network pullNote also that
(mount failed)in the offline-store diagnostics is benign — it means "already mounted", and passing cells print it too.
Two different denominators, on purpose. LUKS E2E is measured against
build_image (every published desktop image), because luks-e2e.yml builds its
matrix that way: the browser ISO builder can make an ISO from any image, so
image-only variants (sailfin, guppy, flounder-sid) need boot and install
coverage too. Installer smoke is measured against build_iso. The two totals
answer different questions and should not be compared directly.
None of the axes above say anything about this — they run in QEMU/KVM on x86,
and Apple Silicon needs its own kernel, DTBs, and boot chain
(build_scripts/overlay/asahi.sh) that no x86 guest ever exercises. That gap is
exactly how six of the eight promoted :gnome-asahi tags turned out to be
unbootable on real hardware despite every other axis being green
(tunaOS#776) — a tag existing in
ghcr only means some build promoted it once, never that the image boots on a
Mac.
scripts/verify-asahi-image.sh is the check that looks: it unpacks
the image and inspects it for the asahi kernel, the 12 hardware-critical
modules, Apple DTBs, the m1n1/U-Boot payloads, and the audio stack, graded
against a known-working Fedora Asahi Remix image. .github/workflows/ verify-asahi.yml runs it in sweep mode daily at 05:40 UTC against every
promoted :gnome-asahi tag (workflow_dispatch for one flavor at a time), so
— same principle as the rest of this document — a result decays on its own if
nobody re-runs it; the workflow's run history
is the live, current answer. This document does not mirror it into a generated
table. Only two flavors are wired up at all, where the three axes above cover
many. The per-check state on this axis is also why the numbers below carry
their own tracking issues instead of one matrix cell each.
As of the last full sweep:
| Variant | Harness | Status |
|---|---|---|
| bonito | 36 / 0 | ✅ verified |
| grouper | 36 / 0 | ✅ verified |
| yellowfin, skipjack | 29 / 7 | ❌ no update-m1n1 — tunaOS#777 |
| sailfin | 27 / 9 | ❌ no Apple DTBs, no U-Boot payload — tunaOS#914 |
| marlin, flounder | 23 / 13, 21 / 15 | ❌ overlay never applied — stock kernel — tunaOS#911 |
| albacore | 11 / 25 | ❌ stock kernel not removed — tunaOS#912 |
| guppy | — | not offered — overlay is stubbed, not broken, and deliberately excluded from the sweep matrix |
Only bonito and grouper are safe to hand to a user today. The installer
catalog agrees. The catalog.json of bootc-installer-asahi ships exactly one
hand-written entry, bonito, and does not list every *-asahi tag. Its README
documents the same six-of-eight gap
(tuna-os/bootc-installer-asahi#42).
The tables between the GENERATED markers are rewritten by
scripts/gen-matrix-status.py; everything else is hand-written and the
generator will not touch it. Add narrative freely outside the markers.
scripts/gen-matrix-status.py # rewrite the block in place
scripts/gen-matrix-status.py --check # exit 1 if it would changeIt runs daily at 06:00 UTC and pushes only when something changed, so the file's git history doubles as a record of when each cell flipped.
The underlying queries, if you want to check a cell by hand:
# LUKS E2E, per cell
gh run view <run-id> --repo tuna-os/tunaOS --json jobs \
--jq '.jobs[] | select(.name|test("^LUKS ")) | "\(.conclusion)\t\(.name)"'
# Installer smoke, per flavor
gh run view <run-id> --repo tuna-os/tunaOS --json jobs \
--jq '.jobs[] | select(.name|test(":")) | "\(.conclusion)\t\(.name)"'
# Published overlay tags
TOK=$(curl -s 'https://ghcr.io/token?scope=repository:tuna-os/live-overlay:pull' | jq -r .token)
curl -s -H "Authorization: Bearer $TOK" \
https://ghcr.io/v2/tuna-os/live-overlay/tags/list \
| jq -r '[.tags[] | select(test("^sha256-")|not)] | sort | .[]'
# The ISO matrix this is measured against
yq -r '.variants[] | .id as $v | .flavors[]
| select(.build_iso == true) | "\($v)\t\(.id)"' .github/build-config.ymlIt never downgrades a cell on error. When the API call fails, the script raises and the workflow fails loudly. It does not rewrite cells to ⬜. A status page that degrades itself in quiet is worse than no status page, and it would recreate the "absence of evidence" failure this document exists to prevent.
It also reads only the conclusion values success and failure. It ignores
cancelled and skipped runs, because gh reports a cancelled run in a form that
a reader can easily mistake for a failure.