CI stages deploy.sh in a scratch HOME and tars the composed tree
dereferenced, then makepkg ships it to /etc/xdg/quickshell/souveraine
on both arches — the shell becomes package-owned, and a writable
~/.config copy keeps shadowing it.
souveraine-admit creates or adopts one account via systemd-sysusers, writes
the root-owned mapping health reads, and reports what NSS says afterwards
instead of what it asked for. An account belongs to one agent: handing
annie's to souvie is refused in both directions, as is adopting a uid >=1000.
Binding is by SeedID, not the UUID filename — a mismatch is identity-drift.
souveraine-session is machine-tier — one per install, like souveraine.
Agent principals stay out: sysusers is per-install, admission is per
agent and per node, and a line here would mint annie on every device the
package touches. sessiond stays a user unit until the lock handshake,
PAM path and key-eviction order are proven under the new uid.
The development recipe installed the sysusers declaration; the CI package that devices consume quietly dropped it. Ship the machine-tier souveraine account in the prebuilt package and assert the file is present before publishing.\n\nThis account belongs to machined. Souvie gets her own principal through agent admission.
ChargeEvidence was the rule violated four times in one type: a side door
past the gate, the decider probing sysfs on the clock, the driver
interpreting, and nothing leaving tick(). Its comment cited bearer —
another instance of itself.
SensorSource::Charge now enters through sensor_input like every other
source, reported by sensord on a 30s poll off /sys/class/power_supply.
conclude_charge lives on the machine as its one decision; source health,
freshness and Absent/Down come from the gate for free. The 5s clock probe
is gone, the driver type is gone, and sensord ships x86_64 too: the
laptop has a battery.
The authority takes ext-session-lock before any surface exists, which is the
window -pre reserves. It cannot start earlier — ext-session-lock is a Wayland
protocol and needs the compositor's socket. Also drops the injected venv
variable from eleven surface scripts; each finds the path itself.
/etc/pam.d/souveraine-sessiond arrived by hand on 2026-07-16 and is owned
by no package on either device — root-owned config can only arrive by
package. Same rail as souveraine-stepup, plus a bsdtar assertion so a
future drop fails the build instead of shipping quietly. TASK-76.
blueline-fingerprintd installs /usr/lib/security/pam_souveraine_fpc.so
(PKGBUILD:48). cc541d1 named pam_fpc_polkit.so, which exists nowhere.
The leading '-' means a missing module is skipped without complaint, so
a wrong name here fails silently and permanently: the finger does
nothing and no log says why.
The module returns PAM_IGNORE outside PAM_SERVICE=polkit-1
(fpc-polkit-pam.c:86), so step-up falls through to the password stack
until that allowlist widens.
SESSION-AUTHORITY-DOCTRINE §3 names /etc/pam.d/souveraine-stepup by path.
Root-owned config can only arrive by package: rootfs-overlay/ reaches a
device on flash and the phone does not flash.
auth_self, never auth_admin — step-up re-proves the user to themselves, it
does not escalate. The leading '-' on pam_fpc_polkit.so loads the fingerprint
factor only where it is installed, so one file serves phone and laptop.
Inert until a consumer calls it; no src/ path does yet.
CI already built the x86_64 daemon and then threw it away — the binary and
its user unit were copied into the package only under aarch64, so the
laptop could never receive the thing every lock, blank and button report
assumes is running.
Both now ship, and the package still neither enables nor starts the unit.
Phone-only reporters and surfaces stay aarch64.
Reports down/up edges to sessiond and decides nothing. Wire it in
hyprland.lua as a bind/bindr pair — reporting only the press leaves the
machine believing the button is held forever.
sensord hung off graphical-session.target, which nothing on this device
starts, so it was enabled and dead from every boot. Down starts from a
last-seen stamp and structurally cannot see that. Bind the reporter to
sessiond and make the machine say so.
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.
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.
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.
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.
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).
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.
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.
The submodule was pinned one commit behind the fix, so every CI-built
upower-souveraine fell through to the warning path and reported
charge-type unknown while discharging.
Seal items AES-256-GCM under a random store key (item id as AAD).
Machine wrap: HKDF over a deterministic machined signature (domain
secrets-store-key, socket /run/souveraine/machined.sock; legacy
seed-id fallback, same framing) — private key never enters the daemon.
Argon2id passphrase wrap slot (64MiB/3, HKDF-mixed with the machine
signature) managed via org.souveraine.Secrets.Manage: SetPassphrase /
VerifyPassphrase / HasPassphrase; rotation re-wraps only.
Client-found spec fixes: DH prime was the 2048-bit group-14 constant,
not the 1024-bit Second Oakley group libsecret uses (pinned by test);
item ids now simple UUIDs (hyphens illegal in object paths); collection
also served at /aliases/default; interfaces registered before name
claim. Added spec signals, real timestamps, content types, atomic 0600
store writes.
packaging: systemd user unit, D-Bus activation shadow, idempotent
phone deploy script (masks gnome-keyring, verifies round-trip).
seat0-active polkit rule so power off/reboot/suspend stop returning
challenge into a session with no polkit agent on the lock surface.
Lock-surface buttons stay gated behind allowPowerFromLock (opt-in) and
requirePasswordToPower. SystemGestureRail progressive gesture + mission
control nav flag.
- packaging/upower-souveraine submodule pinned at fork head (f18b1a4,
souveraine/charge-type): ChargeType + PercentageTrusted on Device
- PKGBUILD.upower.prebuilt: prebuilt meson install tree, provides/
conflicts upower, replaces stock (IgnorePkg=upower on the phone)
- ci.yml: build the fork per-arch against the aarch64 sysroot, assert
binary arch, fold into the souveraine-{arch} pacman db
- LockSurfaceHost: charge line driven by device state, not onBattery —
no more eternal 'Charging N%' on a topped-off pack
- docs/tasks/power-indication.md force-added past the docs/ gitignore
The machine seed moves out of the user session: souveraine-machined owns
/var/lib/souveraine/seed-id as the souveraine system user and serves
pubkey/sign over /run/souveraine/machined.sock (SO_PEERCRED logged,
domain-separated signatures, group-gated socket). Seed is a precondition
- provision via 'souveraine machine init --fresh|--migrate-from', the
daemon never generates. reach/consult now resolves the machine key
through the daemon with a loud legacy fallback. Hardened system unit +
sysusers shipped in the Arch package.
.cargo/config.toml + aarch64-pkg-config wrapper are load-bearing for the
cross build (CI artifact job + local build-cross.sh both depend on them);
were gitignored before. souveraine.service is the systemd user unit the
packaging/deploy scripts template.
Both were stranded untracked in the Pixel3Arch tree; they source from
this repo, so they live here. deploy-phone.sh ships the cross-built
binary + user unit (seed-id excluded, machine binding stays doctrine);
PKGBUILD builds from a synced local checkout, no network fetch.