Watch
1
0
Fork
You've already forked souveraine
0
Commit graph

458 commits

Author SHA1 Message Date
Fimeg
25d1e19246 upower: var/ is a legitimate prefix, not a leak
Run 1234 failed because the new stray check omitted var/lib/upower.
2026-07-27 13:46:48 -04:00
Fimeg
7eb2135906 upower: relocate the cross-install udev leak, and assert it stays gone
udev rules and hwdb installed under the sysroot prefix, so none of the
fork's quirks were ever active on the phone.
2026-07-27 10:11:14 -04:00
Fimeg
2170ba686f deploy: fail loudly when a qmldir names a file the composed tree lacks 2026-07-27 09:15:48 -04:00
Fimeg
53dcb40c5d dial: bind the ring origin instead of assigning it before the surface is sized 2026-07-27 08:29:44 -04:00
Fimeg
002a1a7b08 gestures: consumer + routing table, wired to the dial 2026-07-26 21:01:11 -04:00
Fimeg
bef6e6a653 dial: radial picker, haptic detents, dial IPC 2026-07-26 20:36:07 -04:00
Fimeg
2c1ee8bc00 sessiond: gate the blank actuator on logind LockedHint 2026-07-26 20:16:58 -04:00
Fimeg
29a3c472f5 sensord: buffer every source, and make the light threshold real
Two halves of the same defect. The lux threshold compared each sample to the
previous sample, so a slow drift never tripped it — it now compares to the
last value actually reported. And no source had any rate floor at all, which
is how light managed ~8 reports/sec; 250ms per source, enforced separately so
a busy light sensor cannot delay a proximity edge.
2026-07-26 20:07:31 -04:00
Fimeg
de3a332513 sensord: report edges, not every sample
Light drifts continuously; monitor-sensor emitted ~8 lines/sec and each was
a socket round trip re-asserting the same Changing(false). The keepalive is
what keeps silence meaningful, so only changes need reporting.
2026-07-26 20:03:21 -04:00
Fimeg
be72019883 settings: write the dim value through, drop the invented clamp
Clamping the lock-screen grace to blank-1 turned a 30s dim on a 15s blank
into a 14s grace, dimming one second after you stop touching the phone.
2026-07-26 20:00:50 -04:00
Fimeg
28e8b78284 sensord: needless_bool 2026-07-26 19:16:46 -04:00
Fimeg
43c1fdbc05 session: one IPC owner for target 'session', not a load-order coin flip
Session.qml and Lock.qml both registered target 'session'; quickshell keeps
whichever loads first and drops the other. Lock.qml's handler is a strict
superset, so the duplicate goes.
2026-07-26 19:04:14 -04:00
Fimeg
83fab1a1b6 sensord: one reporter for every sensor, replacing the per-sensor scripts
blueline-proximity-lock is 113 lines of shell implementing a subtle contract
(heartbeat inside SOURCE_DOWN_AFTER, seed from the probe banner, report both
edges) that would have been hand-copied for light and again for accel. One
binary, one contract, shipped in the package instead of the rootfs overlay.
2026-07-26 18:46:27 -04:00
Fimeg
4d652bbbc6 sessiond: fix the self-deadlock that made the daemon stop answering
Action::Restore bound the brightness inside an if-let, so the MutexGuard
lived for the whole body and run_executor locked again. Non-reentrant: the
state lock was held forever and every later request thread queued behind it
(measured: 347 threads in futex_wait, 691/1024 fds, accept loop healthy, no
request answered in 3h). Bind first, then branch.

Also close connections idle for 120s, exempting the shell heartbeat.
2026-07-26 18:26:31 -04:00
Fimeg
c7c92d2680 settings: one Brightness dimming section, one blank timeout
The page asked the same question twice because two daemons answer it, and
split the blank budget by held-vs-resting — which makes the user arbitrate a
guess the accelerometer is making. Held-ness belongs in the confidence
arithmetic as an adjustment to one budget, not as a second budget here.
2026-07-26 18:11:01 -04:00
Fimeg
4d64b4540c polkit: stop the prompt covering the on-screen keyboard
The fullscreen overlay sat above squeekboard's layer and owned the whole
screen's input region, so taps in the keyboard area hit the dialog. It now
yields the bottom third while the OSK is up. Exclusive keyboard focus was
tried and reverted — it stopped the keyboard taking touch entirely.
2026-07-26 17:39:38 -04:00
Fimeg
c9e11a54f7 upower: bump to aeeb107 (charge thresholds via the supplier walk) 2026-07-26 17:06:00 -04:00
Fimeg
2b69957f18 lock: surface charge rate, and stop counting down while plugged in
UPower's ChargeType (fork, read off the charger) reaches the lock line as
fast/slow charging. Discharging with the cable in is the charger resting
between top-ups, not a drain — it now reads Charged/Plugged in.
2026-07-26 16:56:39 -04:00
Fimeg
7475fae505 osk: polkit holds the keyboard open instead of poking it once
squeekboard hides itself when input-method focus drops, so the password
field was left with no keyboard. A hold re-asserts through that and
releases on dismiss; a manual close drops the hold.
2026-07-26 16:56:34 -04:00
Fimeg
4f113ad62d shell: the dim is a grace before the lock, not a second absolute timer 2026-07-26 15:09:17 -04:00
Fimeg
3174e7813c ship souveraine-verify-trail in the package that writes the trail 2026-07-26 14:58:48 -04:00
Fimeg
f2a4b93ee3 sessiond: a torn tail is a hard reboot, not tampering — resume the chain
Measured: the hard reboot left the trail at exactly 4096 bytes with the last
entry cut in half. Rotating that aside cost chain continuity at the one moment
it is worth most.
2026-07-26 14:49:00 -04:00
Fimeg
43eee1e3c5 sessiond: carry the caller's intent into the trail 2026-07-26 14:38:40 -04:00
Fimeg
c44deda7c2 sessiond: restore the exact brightness the dim captured, not a guessed floor 2026-07-26 14:00:37 -04:00
Fimeg
18bd4a0974 sessiond: make the verb surface enumerable and its refusals branchable 2026-07-26 13:00:58 -04:00
Fimeg
4cd7ddd046 sessiond: debounce the proximity edge that actually flaps
Measured: 44 near-episodes in 2.4h, median dwell 1s, 15 sub-second. The
negative debounce §9.5 specified would have delayed each blip, not removed it.
2026-07-26 12:50:26 -04:00
Fimeg
813010dbb6 sessiond: proximity stops actuating; it vetoes tap-to-wake and nothing else 2026-07-26 12:19:52 -04:00
Fimeg
4d0c7a1c0b sessiond: the forensic trail leaves tmpfs, gains bounds and a hash chain 2026-07-26 11:24:41 -04:00
Fimeg
01fda8ab87 sessiond: persist the device-state policy
SetPolicy only mutated memory. Every lock-screen timer set in Settings
reverted to the built-in 15s on the next restart, silently — the page was
honest about reading the daemon, the daemon was forgetting.

Saved to $XDG_CONFIG_HOME/souveraine/device-state-policy.json, write-then-
rename. serde(default) on the container so an older file loads with new
fields defaulted instead of dropping the user's settings on upgrade.

A failed save returns ok:false rather than a success-shaped switch.
2026-07-26 09:01:36 -04:00
Fimeg
862d1d7bba sessiond: evidence-source health as device state
A source that reported and then went silent past 90s is DOWN, not quiet,
and says so as an error-security entry. Sources that never reported stay
unknown — a light sensor with no reporter is correctly silent forever.

Every forensic snapshot now carries sensors_degraded, so a decision taken
during an outage doesn't read like a healthy one. This is the 2026-07-25
SLPI case: hexagonrpcd-sdsp exited 0, every sensor dead for hours, nothing
in any log.

Health is not read from SensorProxy Has* — measured lying both ways.

Also: evidence_fresh meant "ever seen" and read true through the outage;
repeated readings no longer append to the trail, since reporters heartbeat.

55 sessiond tests.
2026-07-26 08:30:07 -04:00
Fimeg
bd8a4eca50 sessiond: refuse half-applied unlocks; log the real sensor source
set_session_locked(false) discarded transition()'s return. There is no
Suspending/Asleep -> Active edge, so an unlock reported while asleep left the
state put but voided pending_blank, blank_requested and dimmed anyway. Now
refused, loudly, with the state intact.

update_sensors hardcoded SensorSource::Proximity in the forensic entry, so
accel/light/touch readings were written to the trail labelled as proximity.
Takes the source now; update_sensors() stays as a proximity wrapper.

47 tests, 1 new.
2026-07-26 07:30:06 -04:00
Fimeg
70a05819e8 settings: presets instead of seconds; two ordering fixes
IdleConfig: ConfigSelectionArray presets (30s/1/2/5/10 min for the shell's
timers, 15s/30s/1/5 min/Never for the lock screen) instead of seconds
spinboxes. Nobody reasons about a lock screen in 5s increments; the policy
struct already said "iOS Auto-Lock shape". Wire values stay seconds.
Header comment corrected — hypridle no longer owns screen-off.

set_panel(false) clears pending_blank. A panel darkened by any other route
left it armed, so the next tick emitted a second Blank or hit the
blank-without-lock error path for a blank nobody was waiting on.

SetPolicy refuses dim_grace >= lock_blank_after. dim_at saturates to zero, so
the panel dimmed the instant it went idle with no lit period.

Verified by rendering the page, not by parse alone.
2026-07-26 07:24:28 -04:00
Fimeg
df654e6bee sessiond: take lock truth from logind, not from our own history
Measured on device 40min after boot: device_state=locked, panel_on=false, while
LockedHint=no. Screen dark, session unlocked.

locked_ack was the only lock ingress and there was no unlock one outside the
fallback PIN surface, so after the first unlock the machine believed it was
locked forever. Every is_locked() rule then fired against an in-use phone —
proximity blanked it, and blanks never asked for a lock because one was
"already held". Doctrine §4: never hold state the protocol owns.

New lockhint.rs watches LockedHint via gdbus monitor (§8's pattern, not
polling). Resolve via User.Display, NOT GetSession(auto) — auto returns the
CALLER's session and sessiond is a session-less user unit; over ssh it answered
_310 while the graphical session was _31.

set_session_locked() moves the base tier and voids lock-screen intent on
unlock: pending blank cleared, dim restored.

46 tests, 8 new.
2026-07-26 07:18:02 -04:00
Fimeg
9ab1cfbccc settings: don't render a missing ack budget as "waits 0s"
An older sessiond has no lock_ack_budget_secs. Show the upgrade note instead of
inventing a number.
2026-07-25 18:57:48 -04:00
Fimeg
24ce6d382b shell: execute authority directives; Settings reads sessiond
SessiondBridge: handle pushed {"directive":"lock"} on the heartbeat line and
raise the lock surface. Unknown directives are console.error — a silent drop
leaves the daemon blanking unlocked when its budget expires.

SessiondPolicy: new singleton, reads/writes DeviceStatePolicy over its own
short-lived connection (not the bridge socket — that EOF is shell-death
detection).

IdleConfig: new "Lock screen (device authority)" section bound to it. SetPolicy
had zero callers, so the old spinboxes moved a JSON file the daemon never read.
Says so in the error colour when sessiond is unreachable.

New file needs a deploy.sh manifest line or the services qmldir fails whole.
2026-07-25 18:53:19 -04:00
Fimeg
6eb87a542d publish-edge: ship the bare .files names too
pacman -Fy asks for souveraine-<arch>.files, not .files.tar.zst, so it 404'd.
2026-07-25 18:45:02 -04:00
Fimeg
15a32796ba sessiond: make lock-before-blank an invariant, not a timer coincidence
LOCK-DPMS-LESSONS §1 is "Ordering: lock, then off". It held only because
hypridle's 300s lock listener fired before its own 600s screen-off listener —
two numbers in a config file, not a guarantee. Anything that skipped the lock
(idle inhibitor, native coordinator off, dead shell) still met the blank, and
the panel went dark on an unlocked session with nothing said about it.

Every path to a dark panel now routes through request_blank():

- locked  -> Blank, unchanged, no added round trip
- unlocked -> Action::Lock, blank withheld, pending_blank deadline armed
- ack lands -> Blank
- budget expires -> Blank anyway + error-security in the forensic trail

The panel fails open because a lit unlocked phone in a pocket is worse than a
dark one (§1); the *claim* fails closed because doctrine §8 forbids pretending
the session locked. Input inside the ack window cancels the blank outright.

Action::Lock does not go through the executor table — the lock is not a shell
command. While a live shell owns steady state the directive goes down the
heartbeat connection and the shell raises its rich surface; with no shell alive
sessiond raises its own. Same relationship DEVICE-STATE-MACHINE §6 gives the
DPMS executor: the authority decides, the surface executes. A directive that
cannot be delivered takes the lock here rather than timing out into a dark
unlocked screen, and is recorded either way.

Policy gains lock_ack_budget (2s, refuses 0) and unlocked_blank_after (None by
default — the shell's IdleCoordinator owns the unlocked idle timer through
ext-idle-notify per doctrine §5, and a second one here would recreate the
competing-owner disease). Both are on the GetPolicy/SetPolicy seam so Settings
is a view over the owning daemon, per TASK-19.

Unknown directives are loud on the shell side: an older shell silently
dropping one would leave the daemon waiting out its budget and blanking
unlocked.

38 tests pass, 5 new covering the ordering.
2026-07-25 18:38:32 -04:00
Fimeg
044c373cad ci: publish into edge additively
edge is a shared archive but ci.yml deleted and recreated it each push, erasing
other producers. publish-edge.sh merges into the live db under a flock and
removes only its own superseded builds. Verifies the merge kept every foreign
entry, and reads the db back before trusting it.
2026-07-25 17:50:30 -04:00
Fimeg
9d8bce2a08 sessiond: log transitions prev → next (args were next, next, prev) 2026-07-25 15:08:47 -04:00
Fimeg
aa3027845d sessiond: give the device state machine a clock and real actuators
The state machine landed in b4b30b1 and never ran. Verified on the phone
after 2.5h of use: device_state=active, observed_confidence=0.0,
forensic_log count=0, forensic.jsonl never created. Not one transition.

Root cause was not a missing caller. The daemon had no timebase at all —
every Duration in it was a one-shot handshake deadline — so the machine
could answer requests but nothing ever told it time had passed. It could
not be the proactive half of anything.

- tick loop (1s) drives every timed rule; tick_at() takes the clock so the
  rules are testable without sleeping
- panel is a field, not a ninth state: the enum had no cell for "locked,
  screen dark", and DozeLight (frozen apps, Wi-Fi save) is not a dark glance
- lock-blank rule, two stages: dim as a visible pre-warning, blank one grace
  window later. Nothing owned this before — the only backstop was hypridle's
  600s screen-off timer shared with the desktop case, so glancing at the
  clock lit the panel for ten minutes
- dim/restore via brightnessctl + blueline-undim. The dim listener was
  dropped from hypridle.conf by accident and never restored; it now lives
  here, so brightness has one writer and the save happens exactly once —
  which is the actual fix for the 10/255 wedge the old -s/-r pair hit
- input truth from ext-idle-notify (new idle.rs), not inferred. Tracking the
  compositor's active/quiet boolean instead of stamping last-input is what
  keeps a long swipe from being blanked mid-gesture: continuous input emits
  no events at all
- evidence staleness, 30s TTL per source, clearing toward safety: a stale
  proximity stops suppressing wake, a stale accelerometer stops buying the
  longer budget. Measured need — hexagonrpcd-sdsp exited 0, systemd left it
  dead, and iio-sensor-proxy went on reporting HasProximity=true for hours
- proximity-near while locked blanks now, taking that decision back from
  blueline-proximity-lock
- get_policy/set_policy IPC, Auto-Lock shaped with 0 = never, so Settings can
  be a view over the owning daemon instead of a switch that looks like it worked
- fix: PAM unlock was refused as an illegal transition from Observed,
  DozeLight and DozeDeep. Unlocking a phone whose proximity had fired hit it

33 tests. aarch64 cross-build verified on archdev.
2026-07-25 14:23:07 -04:00
Fimeg
91c0cf800b upower: fix percentage-trusted on real batteries
The heuristic tested charge_full, which qcom-battery lacks and which upower
never uses for the percentage anyway - it reads capacity directly. And like
charge-type it was only set in UpDeviceSupply, so a battery got the FALSE
default. Verified: charge-type now reads fast against the charger's Fast.
2026-07-25 07:49:20 -04:00
Fimeg
bcf4bf2f09 upower: set charge-type on UpDeviceSupplyBattery
Real batteries are backed by UpDeviceSupplyBattery, which never ran the
refresh that set charge-type, so it stayed at the UNKNOWN default.
2026-07-25 07:30:16 -04:00
Fimeg
e7afd7b104 ci: re-run for the upower charge_type package build
rust-test failed on 16aa469 with only a submodule gitlink changed and no
Rust touched, so aarch64-artifact skipped and no package was produced.
2026-07-25 07:05:57 -04:00
Fimeg
16aa469505 upower: read charge_type from the supplying charger
SDM845 splits fuel gauge and charger; qcom-battery has no charge_type at
all, so the battery-only read returned UNKNOWN regardless of the N/A
spelling fix. Follows the kernel's device links to the charger instead.
2026-07-25 06:51:34 -04:00
Fimeg
155288d96e upower version: .r not + (a + in a gitea asset name 404s) 2026-07-25 06:05:24 -04:00
Fimeg
62a3f0b0a8 don't claim gnome-keyring's dbus file; version upower from upstream
Two install blockers found on the phone:
- the package shipped org.freedesktop.secrets.service, which gnome-keyring
  owns. souveraine-secrets holds the bus name at runtime already.
- upower reused souveraine's 0.1.rN version, which sorts below the
  hand-built 1.91.3+sha, so -Syu saw a downgrade and skipped it. Now
  built from upstream version + fork sha (verified vercmp = 1).
2026-07-24 21:56:43 -04:00
Fimeg
07d45c10bc package secrets and machined too
All four repo binaries were hand-copied to the phone and owned by no
package. secrets/machined ship on both arches; sessiond stays aarch64.
secrets unit repointed /usr/local/bin -> /usr/bin.
2026-07-24 21:36:31 -04:00
Fimeg
261d74672f package sessiond (aarch64 only)
It was hand-copied to /usr/local/bin, owned by no package, so it never
updated. Not built for x86_64 — the laptop hits lock-screen errors with
it. Verified both packages assemble correctly.
2026-07-24 21:31:14 -04:00
Fimeg
f620a5d5f9 ci: upower package check greps libexec, not lib
Seventh instance of the same wrong path: the package-content assertion
looked for usr/lib/upowerd. upower installs to usr/libexec/upowerd — the
copy already on the phone confirms it — so the check failed after a
successful build and sign.

Verified on the runner with the fixed pkg-config wrapper: 32 targets,
aarch64 upowerd installed to usr/libexec.
2026-07-24 21:14:30 -04:00
Fimeg
a799bf1cde ci: make the upower cross build actually work
The upower steps were added after the last green release and had never
run successfully — the Jul 13 edge release contains no upower package at
all. Six defects, each hiding the next, found by reproducing the steps on
the runner:

- SOUVERAINE_AARCH64_SYSROOT was exported in the binaries step, not this
  one; steps do not share environment, so the pkg-config wrapper fell back
  to /usr/aarch64-linux-gnu and glib was not found.
- c_link_args had --sysroot but no -L, so ld could not resolve the bare
  paths inside the sysroot linker scripts (libm, libmvec).
- PKG_CONFIG_LIBDIR covered only lib/pkgconfig; udev.pc lives in
  share/pkgconfig. Fixed in the wrapper, which overrides the caller.
- introspection ran g-ir-scanner against an aarch64 build and failed;
  disabled for the cross build only (the phone ships no UPowerGlib
  typelib and quickshell's UPower module is native Qt).
- gtk-doc/man default on, but gtkdoc-scan is absent from the runner, so
  the x86_64 native build would have failed at the same point.
- the aarch64 sanity check tested usr/lib/upowerd; it installs to
  usr/libexec/upowerd, so the check failed even on a good build.

Verified on archdev: aarch64 56/56 targets, upowerd is aarch64, built
from 628283f with the charge-type fix; x86_64 58/58 native.
2026-07-24 20:55:33 -04:00