TASK-26 had two subjects. Yesterday's note closed the proximity-authority half and read past the other one — evidence-source health, which is what PAF/slpi.md was actually pointing at. Built now, so both are closed. TASK-28 gains two measured cases: the reporter/sessiond split-delivery order, and sessiond restart putting up the fallback PIN surface.
5.8 KiB
TASK 28 — Handover on upgrade
Status: scoped 2026-07-25, not started. Size: design first, then one session per mechanism.
An upgrade can move authority from one component to another. If the two halves land at different times, behaviour regresses silently — the system keeps running, nothing fails, and a capability is simply gone. Today that was caught by hand. It should be caught by the packaging.
The case that motivated this (2026-07-25)
blueline-proximity-lock was demoted from an authority to a reporter: it used
to call blueline-screen-toggle itself, and now it reports proximity to
sessiond, which decides. Correct design — but the new script is inert unless
the sessiond it reports to is new enough to act on it. The two halves ship by
different routes:
- sessiond ships in the
souverainepackage, via CI →pacman -Syu - the script lives in
rootfs-overlay/, which reaches a device only at provision time (TASK-27)
Installing the script alone would have removed proximity blanking and put nothing in its place. Nothing would have errored. The phone would simply have stopped blanking in a pocket, and the next person to notice would have been Casey, in a pocket. The only reason it went well is that both halves were installed together by hand, in order, with the result checked.
A second instance, measured 2026-07-26
Two more data points, both from shipping the source-health change.
The split-delivery case fired again, and this time the failure direction is
the loud one. Source health lives in sessiond (souveraine package, CI →
pacman -Syu); the heartbeat that makes silence meaningful lives in
blueline-proximity-lock (rootfs-overlay/, provision-time only). New sessiond
with the old reporter declares proximity down every 90 s forever, because
nothing heartbeats. That was avoided by deploying the reporter first, by hand,
in order — which is the same "the only reason it went well" as the case above.
Note the asymmetry is worth encoding: here the wrong order is noisy, in the
proximity demotion it was silent. Packaging cannot rely on which.
Restarting sessiond puts up the fallback PIN surface. Measured: 07:55:00
systemctl --user restart souveraine-sessiond → 07:55:05 session lock acquired → the shell reconnected at 07:55:05 but sessiond kept the lock and ran
PAM itself → one failed attempt 07:55:18, success 07:55:33, unlocked at the
device. This is lock-before-shell working as designed (a session authority that
restarts must not leave an unlocked gap), but it means every package upgrade
that restarts the daemon locks the phone into the fallback surface, not the
shell's. The handover this task is named for is exactly the missing piece: the
new sessiond should take the lock from the old one and hand it to the live shell
rather than re-running the crash-path surface.
Related, same shape: restarting sessiond to pick up the new binary dropped the
shell's heartbeat, so shell_alive went false and the session fell back to
sessiond's spartan PIN surface instead of the shell's lockscreen. Nothing
re-registered it. A user who picked the phone up at that moment would have
concluded the lockscreen was broken.
What is missing
-
A capability handshake, so a reporter can refuse to demote itself. Before
blueline-proximity-lockstops deciding, it should be able to ask sessiond "do you act on proximity?" and keep its old behaviour if the answer is no. Thedevice_stateIPC already exposes enough to build this (apanel_onfield and a policy the daemon will admit to); what is missing is a declared capability list and the reporter checking it. Same argument applies to any future "X stops deciding, Y decides now" change. -
Both halves in one transaction. As long as the overlay is not a package, no dependency can express "this script needs that daemon". Packaging the overlay (TASK-27) is the prerequisite; then a normal
depends=('souveraine>=0.1.r219')does the work, and pacman refuses the half-upgrade rather than performing it. -
Restart order and session re-registration. A
souveraineupgrade should restart sessiond and cause the shell to re-establish its bridge, in that order, without the user seeing the fallback surface. Today the pacman hook enqueues marked services with no knowledge of the lock handoff that sits between them. sessiond already supports the zero-gap handoff in both directions — the upgrade path just never uses it. -
A post-upgrade assertion. After the transaction, something should check that the capability still exists: proximity still blanks a locked screen, the shell still owns the lock, gestures still bind. A silent regression is only silent because nothing asks.
Prior art worth reading before designing this
Android's PackageManager restarts a component's dependents on replace, and
its ACTION_MY_PACKAGE_REPLACED exists for exactly this. systemd has
ExecReload= and Also=/PartOf= for restart propagation, which is likely
enough for the restart-order half without inventing anything.
Acceptance
- No upgrade can install one half of an authority move. Attempting it fails the transaction with a legible reason.
- A reporter whose consumer is too old keeps its previous behaviour and says so, once, in the journal.
- A
souveraineupgrade leaves the shell owning the lock, with no fallback surface visible and no manual restart. - A post-transaction check reports which device capabilities are live, so a regression is a failed check rather than a discovery weeks later.
Connects to
TASK-27 (packaging the overlay is the prerequisite), TASK-08 / TASK-15 (the
device state machine is the consumer in the case above),
SESSION-AUTHORITY-DOCTRINE §11 (one authority — this is how authority moves
between processes without a gap).