13 (grip) and 35 (sessiond deadlock) are done; both moved to archive/ with closing notes. Index regrouped by state with a one-line what's-left per task instead of a flat table of history; the archive prose moved to archive/README. Corrected stale statuses: 29 shipped (read-only pending polkit), 31's component and squeeze are live and only its contents are open, 42's layer 1 is built, 34 no longer blocks grip, 43 was numbered 42. New: 44 fingerprint (DT + shim sat unused in staging/ since 07-07), 45 haptics (settle which chip drives the motor before porting cs40l2x), 46 camera (drivers and CAMSS are already in-tree; blueline's DT has no camera node).
12 KiB
TASK 29 — SouveraineOS Updater: finish it and put it through the pipeline
Status: shipped 2026-07-25; app rewritten 2026-07-26. Builds, runs, and
installs on the phone by pacman -Syu from the edge archive.
Left: it is read-only on the device — nothing in the session runs a polkit
agent, so every privileged action refuses. See "What it still needs".
Also owns TASK-42 layer 2: compare how old the newest thing in edge is
against the branch head it tracks. That one comparison is what would have
caught a publish that quietly stopped.
The Updater is a Rust/Qt rewrite of the GTK/Python pachub. New code, new name
— it is not a fork to be maintained against upstream. It is also the first
chance to build something into the pipeline instead of retrofitting it, which
is the whole point of TASK-27.
Where it is now
~/Projects/souveraine-updater — fresh git repo, one commit (e678fd6), no
remote, no Gitea repo, no CI. Copied out of ~/Projects/PacHub (which is a
GitHub fork of mrks1469/PacHub and still holds the deleted Python app
uncommitted; leave it alone as the reference).
Renamed throughout: crates updater-core + updater-ui, binary
souveraine-updater, QML module com.souveraine.updater, cache dir
~/.cache/souveraine-updater, icon net.souveraine.Updater.svg.
~2,500 lines. updater-core (pacman/AUR/auth/cache/models) looks coherent and
is plain Rust. updater-ui is the cxx-qt half and is where the work is.
What was already wrong, and is now fixed in that commit
-
It could not build at all.
updater-ui/Cargo.tomldeclaredqmetaobject = "0.2.10"whilebuild.rscalledcxx_qt_buildandmain.rsusedcxx_qt_lib— and there was no[build-dependencies]section. cxx-qt 0.9.1 was in the lockfile from some earlier state, so the lock lied about it resolving. Now:cxx,cxx-qt,cxx-qt-lib(qt_full), and build-depscxx-qt-build+qt-build-utils, all 0.9 to match culver. -
PackageManager {}could never resolve in QML — the QObject had no#[qml_element]. Main.qml instantiates it directly, so the root component would have constructed to null. -
Every signal handler was dead. The bridge declared
packages_loaded,operation_completed,detail_loaded; the QML listens ononPackagesLoaded,onOperationCompleted,onDetailLoaded. Without#[auto_cxx_name]those never bind — the same silent defect that cost culver its Matrix login on 2026-07-20. Added#[auto_cxx_name], which also camelCases invokables and properties, so all 46pm.*references across the four QML files were converted (pm.load_packages→pm.loadPackages). JSON field names from serde (build_date,disk_free,depends_on, …) were deliberately not touched — those follow the Rust struct fields. -
The UI was loaded from
/usr/share/pachub/qml/*.qmlby absolute path, with a cwd fallback. Replaced with a qrc module wired inbuild.rs(com.souveraine.updater), so the binary carries its own UI.main.rsnow calls bothinit_crate!andinit_qml_module!— both are required in a binary crate, and the aarch64 lld/LTO link drops them as unreferenced otherwise. Also addedon_object_creation_failedso a null root reports the real reason instead of a guess. The hand-writtenqml/qmldirwas deleted; cxx-qt-build generates the module's own.
Next: make it compile
Build on archdev. Never on the laptop — that includes cargo check and
cargo clippy. Casey stopped a local lint run on 2026-07-25.
Unverified and likely to need work on first compile:
#[qproperty(String, …)]on thirteen properties. cxx-qt's QML-visible properties normally wantQString; RustStringmay not expose to QML even if it compiles. If QML reads empty strings, this is why — convert toQStringand adjustPackageManagerRust.- The bridge does its pacman work synchronously inside invokables.
full_upgradeandrate_mirrorswill freeze the UI.updater-core::authshells out via polkit; check what it does on a phone with no agent running. - The icon is referenced as
qrc:/qt/qml/com/souveraine/updater/net.souveraine.Updater.svgbutbuild.rsregisters only.qmlfiles, so it will not resolve. Either register it as a module resource or drop to a themed icon name. - The SVG is still
mrks1469's artwork, renamed. Replace it before this is a first-party app.
Then: be the first repo built the right way
This is the piece that matters beyond the app. Nothing in ~/Projects except
souveraine reaches the phone through a pipeline (TASK-27). Do not repeat that.
- Create the Gitea repo
Fimeg/souveraine-updater, default branchprimary, pushe678fd6and onward. - CI on the
archdevrunner:cargo test,cargo clippy -- -D warnings, theno-ai-attributionguard (copy culver's), then a packaging step — the thing culver's CI still lacks. Build aarch64 + x86_64,repo-add --include-sigs --signintosouveraine-{arch}, publish to theedgerelease. - Publishing into
edgeneeds the additive shape first (TASK-27 finding 3): souveraine's CI currently deletes and recreates that release, so a second producer's assets are erased on the next souveraine push. This is a hard prerequisite — shipping the Updater intoedgebefore fixing it means the next souveraine commit silently deletes the Updater. - A PKGBUILD exists in
~/Projects/PacHub/PKGBUILD(pkgnamepachub, installs loose QML to/usr/share/pachub/qml). It needs rewriting for the new name and the qrc build — the QML install loop is now wrong, since the UI is embedded.
What actually broke, 2026-07-25
Eight compile errors, then one runtime failure. In order:
#[qproperty(String, …)]— as predicted. cxx-qt has noStringconversion across the property boundary; all thirteen areQStringnow.apply_filterwas declared onPackageManagerRusttakingPin<&mut ffi::PackageManager>, which is not a validselftype without the unstablearbitrary_self_types. Moved onto the QObject.rust()/rust_mut()needuse cxx_qt::CxxQtType.QGuiApplication::execwants the pinned app out of theUniquePtr.- The root still constructed to null after all of that.
Main.qmlinstantiatesPackageManager {}but never importedcom.souveraine.updater. Loaded by its qrc URL, a file's implicit import is its directory — which resolves sibling.qmlviews but not a Rust-registered type, which belongs to the module.on_object_creation_failedis what surfaced it; without that hook this is a silent blank window. Culver imports its own module in every file for the same reason. - The icon URL resolved to nothing, as predicted.
qrc_resourceswith aQmlModuleset prefixes each path with the module import path, landing exactly on the URLSidebar.qmlalready used, so the QML was left alone. The svg moved intoupdater-ui/to keep one copy for both qrc and packaging.
CI ends in a smoke test that loads the QML root offscreen and fails on any
stderr. A null root builds green — cargo build cannot see this class of bug,
which is how it survived from the start.
Acceptance — met
- Builds on archdev for aarch64;
build-cross.shasserts the binary is aarch64 and that everylibQt6*it NEEDs resolves in the sysroot it linked against (TASK-27 finding 6: a mixed-ABI cross build otherwise looks healthy). - Runs on the phone: root constructs, 13,278 packages cached, and it lists
itself as
souveraine-updater 0.1.r1.ge4f00c5ffcc9-1from repoSouveraine. - Installed by pacman from
edge, signature verified against the archive key. All four files owned; nothing unowned. pachubretired —conflicts/replacesremoved it in the same transaction, taking/usr/share/pachubwith it.- A souveraine push can no longer delete it (TASK-27 finding 3, fixed first).
Not verified: the on-screen window. Offscreen proves construction; the visible check is Casey's, at the device.
The app itself, 2026-07-26
The pipeline half shipped first; this is the program. 0.1.r7.g82b1429 on the
phone.
- The package list is a
QAbstractListModel.PackageManageris the model and the controller — one QObject, rows as roles. The JSON string is gone. - Nothing touches pacman on the GUI thread. Every invokable hands off to a
std::threadand comes back throughCxxQtThread::queue. Privileged output streams into the log a line at a time while it runs. - The catalog cache is keyed to pacman's own databases (mtime of
local/plus eachsync/*.db), so a-Syuin a terminal invalidates it. Age alone is still a 6h ceiling. pacman -Ss ./-Qs .instead of-Sl.-Slcarries no description — its[installed]marker was landing in the description column. Installed version now wins over the offered one, and detail merges-Siwith-Qi(install date, reason and reverse deps exist only in the local database).- Privileged calls take an argv.
pacman -S --noconfirm ${name}throughsh -cwas a command-injection hole in the one program that then runs pacman as root. - Theme + drawn icons.
Theme.qmlis culver's palette; icons are stroked paths (Icons.qml,Glyph.qml). The phone image has no emoji font, so every 📦 / 🔍 / ⚡ in the first build was a tofu box. - New first-party app icon, in the culver / player family.
- 9 unit tests, up from zero.
Five things that had to be found the hard way:
- Qt logs to the journal, not stderr, whenever
JOURNAL_STREAMis set — and it always is under systemd, which the Gitea runner is. The CI smoke test was therefore blind to every QML error; it only ever saw main.rs's own line.QT_FORCE_STDERR_LOGGING=1is now set there, withLC_ALL=C.UTF-8so Qt's locale notice does not count as output. font.familiesdoes not exist on the QML font value type in Qt 6.11. Assigning it makes the whole component unavailable. Theme picks one installed family withQt.fontFamilies()instead.AbstractButton.iconis FINAL — a subclass declaringproperty string iconfails at creation, which is a null root.pm: pmbinds a property to itself. The right-hand side resolves in the child's own scope first. Everything through it reads undefined.- A Control lays its contentItem into the content rect, so anchoring inside
that rect fights the layout; and
Dialogsizes its content from itself, so a wrapped paragraph in one is a binding loop.
What it still needs
- No polkit agent runs in the phone session (verified 2026-07-26:
pkexec --disable-internal-agent→ "No authentication agent found"). So every privileged action — install, remove, upgrade, sync — fails. The Updater now says exactly that instead of reporting a bare failure, but the agent is the shell's to run, and until it does the app is read-only on the device. rate-mirrorsis not in the phone image; the action is hidden unless the binary exists.paccacheandreflectorare absent too.- No cancel: a running
-Syucan be watched but not stopped. - AUR support was deleted with
aur.rs— it was dead code that never parsed a version, and there is no helper on the phone. Foreign packages are still listed and upgraded by pacman; building from the AUR is not offered. - The on-screen window on the phone is still Casey's check. Both layouts are verified by headless render on archdev (Xvfb + the shipped binary).
Connects to
TASK-27 (pipeline; the edge clobber is the blocking prerequisite), TASK-25
(one repo, all packages), Pixel3Arch/pkgs/pachub (the old Python package —
retire it when this lands, or the phone carries both).