STATE.md was five days stale. It now carries what is on the phone (viewtop r76, souveraine r333) and the four defects fixed on the glass: per-window capture was unreachable because quickshell holds wlr handles and the ext capture source is named by an ext handle with nothing correlating them; titles were never republished on commit; the border was a filled slab that relied on the client covering its middle; capture came back flipped. Also the packaging hole, which was the important one — viewtop's PKGBUILD existed and appeared in none of packages.yml's build lists, so nothing ever built it. Half closed: it builds now, but a viewtop commit still does not trigger a rebuild. TASK-15 question 1 is answered and closed: dropping the accelerometer claim took iio-sensor-proxy from 15.3% to 1.1%, sensors overall 17.2% to 2.6% of a core. Consequence noted against 08(g) — confidence loses accel's +0.3. New: 54, 55, 56, and 57/58 (Pi dock head, SMB2 OTG boost) with the USB notes they point at.
11 KiB
TASK 08 — Device state manager / power profiles
Status: in progress. The shell-side state projection and reason-tracked inhibitors exist; the durable manager and device tiers remain open. Largest of the tasks; sub-divide before starting.
Goal
One owner for device power states instead of scattered timeout listeners. Android is the reference — it has tiers of idle (screen-off, doze-light, doze-deep) with promotion rules. We have exactly one blunt s2idle and it wedges touch (TASK-09).
Wants (from handoff doc item 12):
-
(a) Idle tiers with device-aware gating. The souveraine settings device flag should gate which states a device may enter. blueline: no suspend until TASK-09 closes. SailfishOS mce (libwakelock.c + datapipe.c pub/sub) and FuriLabs batman (dual-source idle: logind IdleHint + wlr-output-management) are the steal-this prior art (both researched 2026-07-17 with full findings).
-
(b) Charge-aware profiles. UNBLOCKED 2026-07-24 — the kernel work is done and running. The old note here (no charge-threshold sysfs on mainline, needs software current-pacing) is obsolete:
qcom_smbx.cnow exposes read/write charge control, live on the device since kernel7.1.1-sdm845-g58892902459c:/sys/class/power_supply/pmi8998-charger/ charge_control_end_threshold 99 (percent, ABI-correct) charge_type N/A (Trickle/Fast/Taper/…) voltage_max 4395000 (CV target) constant_charge_current_max 1950000 (capped at 1C = 2.97A)upower exposes
ChargeType+PercentageTrustedover D-Bus (the Souveraine fork,souveraine/charge-type). Both properties existed but reported defaults until 2026-07-25 — do not trust a D-Bus property merely because it is present.ChargeTypereadunknownandPercentageTrustedreadnoon hardware for days. Three fixes were needed, and neither of the first two moved the symptom on its own:628283f(kernel spells NONE asN/A),718e921(SDM845 splits fuel gauge from charger —qcom-batteryhas nocharge_type; walk the kernel's device links topmi8998-charger), andf974714— the one that actually did it:up-enumerator-udev.ctriesUP_TYPE_DEVICE_SUPPLY_BATTERYfirst for every power_supply and only falls back toUP_TYPE_DEVICE_SUPPLY, so a real battery never ran the refresh where the fork set these.beb94dcfixesPercentageTrusted, whose heuristic testedcharge_full— absent here, and not what upower reads the percentage from anyway (it usescapacity). Verified on device 2026-07-25:charge-type: fastagainst the charger'sFast. Notecharge_type(active regime) is a different attribute fromcharge_types(the settable profile listup-device-supply-battery.calready uses for thresholds) — easy to conflate when wiring (b). Hardware gaps that are not bugs:charge_full,charge_now,energy_*,cycle_countand model/serial are all absent, soenergy-fullfalls back to design (13.068 Wh), health reads a meaningless 100%, and charge-cycles is N/A. Also seen:temp= 570 → 57 °C while charging — verify whether that is real before building thermal policy on it. pop-os/system76-powercharge_thresholds.rsis still the capability-probing pattern to copy — probe, do not assume, since the attrs exist only on this kernel. Cosmetic: 4.4V encodes to 4.395V so the threshold getter reads 99, not 100. Errs low, safe. Full design + review history:Pixel3Arch/PAF/HANDOFF-upower-charge-control.md. -
(c) USB role awareness. The USB-C role/mode plumbing installed earlier has NO UI toggle. Shell should show current mode (gadget/host/charge-only) and switch it. abrauchli/usbeehive (Rust MIT, typec.rs/pd.rs sysfs) is liftable as a dependency. No existing OSS GUI toggle to fork — build fresh.
-
(d) Wifi handoff gating. Radio transitions belong to the same state model, not ad-hoc.
-
(e) Bounded maintenance/update leases. A deploy or system update may inhibit automatic idle lock while it mutates live session components, but never bypass an existing lock or disable manual lock. The lease has an owner/reason/deadline, survives a shell reload, expires on deploy failure, disconnect or timeout, and restores the exact prior idle state.
Context
- Idle coordinator already exists:
souveraine b18fa33, native path ENABLED on phone (dim 120s / lock 300s). See handoff doc "Idle manager, first real pass" for the hard-won Wayland protocol-error lesson (inhibit checked INSIDE handlers, monitor objects never destroyed). - hypridle is the redundant fallback (300s lock, 600s screen-off, suspend DISABLED per TASK-09 interim).
Session.qmlalready implementsstate(), reason-trackedinhibit()/uninhibit(), and theIdle.inhibitprojection. A 2026-07-21 audit found that noIpcHandleractually exposed those methods despite comments saying it did; a worktree fix adds the missing localsessionIPC seam.- Measured maintenance failure (2026-07-21 19:29): the phone auto-locked
during a live shell deploy. Quickshell reloaded while the compositor lock
was secure, hit
FATAL: Tried to show lockscreen surfaces without active lock, crashed, and sessiond correctly fail-closed. Repeated shell owners then contended for notifications/polkit until cleanup. A deploy must never race the idle lock again. - The conclusions want to land in
power-profiles-daemonas the hint layer.
Two things the machine knows and nothing says (open, 2026-07-26)
(f) sensors_degraded reaches no surface. Source health landed
(DEVICE-STATE-MACHINE.md §10): a source that reported and went silent for 90 s
is down, not quiet, and every forensic snapshot carries sensors_degraded.
The trail knows. No screen does. The crash reporter structurally cannot help —
all three of its sources key on something failing, and the SLPI outage exited
status 0 (archive/05). This is the surfacing half: an ambient indicator when a
source that used to work has stopped, legible on the lock surface, saying which
and since when. device_state already ships sensor_health over IPC.
(g) The confidence gates are computed, logged, and never branched on. §4's threshold table (<0.3 / 0.3–0.6 / 0.6–0.8 / >0.8) and the cross-sensor disagreement rules are written to the trail and ignored. This needs a decision before code, and the decision moved on 2026-07-26: the table was written when proximity suppressed DPMS wake and blanked locked screens, and proximity now does neither — it vetoes tap-to-wake and is otherwise evidence. So the question is no longer "what should confidence gate" but "is anything left for it to gate". The honest candidate is the doze promotion rate and possibly nothing else. Answer that, then wire it or delete the table and record why.
(h) Thermal policy — the zones are there and the policy is empty (2026-08-04)
Measured on the device, so this is inventory rather than plan:
- 23 thermal zones, all on the
step_wisegovernor.power_allocator(IPA — the PID that allocates a power budget across cooling devices) is available on every zone and used by none. - Cooling devices:
cpufreq-cpu0(17 states),cpufreq-cpu4(23),devfreq-5000000.gpu(6), pluscxandebi. - Trip points: passive at CPU 90/95 °C, GPU 85 °C, PMIC 105 °C;
hot90,critical110. Nothing below 85 °C. So nothing throttles until the die is already hot, and there is no gentle first response at all. - cpuidle: two states (WFI, little-rail power collapse), governor
menu, andmenuis the only one built. Android downstream had far deeper cluster and rail collapse; upstreamteois not available to compare against. - GPU:
simple_ondemanddevfreq, idling at 257 MHz. - Battery reads 41 °C at 22% while idle — and note the existing warning above
about
temp= 570 while charging. Verify the sensor before building policy on it; a skin model fed a lying thermistor is worse than no model.
The Android prior art, and which parts transfer. The HAL types zones
(CPU/GPU/SKIN/BATTERY/USB_PORT/POWER_AMPLIFIER) and maps each to a severity
ladder — NONE → LIGHT → MODERATE → SEVERE → CRITICAL → EMERGENCY → SHUTDOWN.
That ladder is the interesting part and it is ours to define, because it is
policy, not hardware. PowerManager.getThermalHeadroom() is its app-facing half:
a normalized 0–1 forecast some seconds ahead, so software degrades itself before
the kernel throttles it. Skin temperature is a virtual sensor — a weighted
model over die zones, because there is no thermistor on the glass; on SDM845
those coefficients lived in Qualcomm's per-device thermal-engine.conf, which
is the one piece of blueline-specific prior art worth going and reading.
How it lands here, and the trap to avoid. §12's rule is not optional and an
outside adviser will propose breaking it: a thermal daemon that reads a zone
and calls a governor is the eighth blind actor. Temperature enters through
sensor_input like proximity and lux; the response leaves as an Action from
tick(), through the executor table that already owns Dim/Restore/Blank.
One writer, one trail. The severity ladder is then a DeviceStatePolicy field
the agent can read and rewrite, which is what makes "the phone is getting warm"
something she can act on instead of something that happens to her.
Do the cheap thing first. TASK-15's 2026-08-04 measurement found ~36% of a core burning at idle in the sensor stack, with the SoC nowhere near any trip. No thermal policy would have helped that. Fix the spinning before modelling the heat.
Sub-tasks (do separately)
- State machine + pub/sub (mce/datapipe shape), device-flag gating.
- Charge-aware profile (needs the PMIC investigation first).
- USB role UI toggle.
- Wifi handoff in the state model.
- Maintenance/update lease in the durable manager. Interim
deploy.shmay use a systemd-timer-backed hypridle lease, but it must first refuse an already locked session and schedule automatic recovery before stopping idle handling.
Start with (1) — it's the spine the others hang off.
Acceptance
One daemon owns device power state. session state IPC reports the
current tier. A plugged-in-at-100% phone doesn't float-charge blindly.
USB mode is visible and switchable from the shell.
An update lease is visible in the same state projection with owner, reason and deadline. Manual lock during a lease wins immediately. Kill the deploy/SSH mid-run: automatic idle handling returns within the bounded timeout.
Connects to
TASK-09 (suspend must be safe before it's a tier), idle coordinator
(b18fa33), session-authority layer (handoff item 4).