Watch
1
0
Fork
You've already forked SouveraineOS
0

tasks 47/48: auto-brightness is unbuilt; the shell dies on a lock-owing reload

This commit is contained in:
Fimeg 2026-07-29 10:45:06 -04:00
commit 85b172aaf1
3 changed files with 158 additions and 1 deletions

View file

@ -0,0 +1,69 @@
# TASK 47 — Auto-brightness: the design is settled, none of it is built
**Status:** open, raised 2026-07-29. **Size:** one session for the Action +
hysteresis; a second for anchors and their persistence. **Repo:** `souveraine`
(sessiond), then `souveraine` (settings surface).
## The gap
`DEVICE-STATE-MACHINE.md` §12 reads as finished work. It settles where light
belongs in the chain, that auto-brightness must be an `Action` and not an
eighth blind actor, how it interacts with `Dim`/`Restore`, that it needs
hysteresis, and — in a subsection marked **"settled 2026-07-27"** — that manual
correction produces *anchors*, not a global bias.
Measured against the code on 2026-07-29:
| §12 claim | in code |
|---|---|
| lux enters through `sensor_input` like proximity | **yes**`SensorSource::Light`, fed by `souveraine-sensord` |
| light health is tracked | **yes** — live on the phone, `"light": "live"` |
| `light_changing` carries evidence weight | **yes** — §4 arithmetic |
| auto-brightness leaves as an `Action` | **no** |
| suspended between `Dim` and `Restore` | **no** |
| hysteresis / ramp on lux | **no** |
| manual corrections stored as `(lux, brightness)` anchors | **no** |
| anchors persisted next to other settings | **no** |
`grep -rn "anchor" src/` returns nothing. `Action` is `Dim | Restore | Blank |
Lock` — there is no brightness verb at all. The evidence half of §12 is live;
the actuation half does not exist.
**This is the failure mode a settled-looking doc creates.** Nothing in §12 says
"unbuilt", and the section that sounds most finished — anchors, with the iOS
patent reasoning worked through — is the part with zero lines behind it.
## What exists to build on
- `sensord` already reports light with the asymmetric debounce taken from this
device's own Android RRO (`config_autoBrightnessBrighteningLightDebounce=2000`,
`DarkeningLightDebounce=4000`). That is the hysteresis input, already smoothed.
- `Action::Dim` / `Action::Restore` are the model to copy: computed in `tick()`,
executed by the executor table, one writer, one trail.
- `note_brightness_before_dim` / `take_brightness_before_dim` already establish
that sessiond owns the panel value and that a second writer makes the captured
value a lie. Auto-brightness must go through the same door.
## Acceptance
1. `Action::SetBrightness(u32)` exists, is computed in `tick()`, and is executed
by the same table as `Dim`. No second process writes `brightnessctl`.
2. It proposes only while undimmed and not `Locked`, per §12. Between `Dim` and
`Restore` it is suspended, not rate-limited — and `Restore` re-evaluates from
current lux rather than replaying the captured number.
3. A sustained lux delta is required before any move, and the move ramps. The
thresholds come off the trail, the way `PROXIMITY_NEAR_DEBOUNCE` did — not
out of §12 and not out of this file.
4. A manual brightness change while auto-brightness is on writes an anchor at
the *current* lux. Two to four anchors, interpolated between, `0%`/`100%`
pinned. Pinned at manual maximum, further ambient rise moves nothing.
5. Anchors persist across a reboot and can be deleted to get the old behaviour
back.
6. The settings page shows the current lux, the computed target, and the
anchors — TASK-19 owes the readout, and this is one of the things it reads.
## Connects to
`DEVICE-STATE-MACHINE.md` §12 (the whole design), TASK-40 (lux must reach the
machine without widening what the raw stream exposes — that task is the gate),
TASK-19 (the readout), TASK-08 (the machine itself).

View file

@ -0,0 +1,86 @@
# TASK 48 — The shell dies on any scene reload that owes a lock
**Status:** open, raised 2026-07-29, fully diagnosed and reproducible.
**Size:** one session, but it is on the lock path — read TASK-43 first.
**Repo:** `souveraine` (`modules/common/panels/lock/LockScreen.qml`).
## Symptom
```
FATAL: Tried to show lockscreen surfaces without active lock
ERROR: Quickshell has crashed under pid <n>
ERROR: Quickshell has been restarted.
```
Eleven crash directories under `~/.cache/quickshell/crashes/` going back to
2026-07-17, so this is long-standing and roughly every second or third day. Two
on 2026-07-29 alone (09:14:37, 10:41:11), both immediately after a scene reload.
## Mechanism
`LockScreen.qml:104` is
```qml
WlSessionLock {
locked: GlobalStates.screenLocked
surface: root.sessionLockSurface
}
```
On a **scene reload** the whole QML tree is rebuilt while the outgoing instance
is still alive and still holds the compositor's ext-session-lock. In the new
tree `GlobalStates.screenLocked` starts `false`. Then `SessiondBridge`
registers, sessiond answers `must_lock=true` (the session *is* locked), and
`registerWithAuthority()` sets `GlobalStates.screenLocked = true`.
The binding fires, quickshell asks for the lock, **the compositor denies it —
another client holds it** — and the surface component is instantiated anyway.
That is the FATAL.
The file already knows about this denial path. `onLockStateChanged` carries a
comment about ext-session-lock `finished` "denied because another client held
it", and resyncs `screenLocked` when it happens. But that handler runs *after*
surfaces were attempted, so the abort beats the guard to it every time.
## Why it surfaced now
It did not — it was always there. It was partly *masked*: before
souveraine `45fbbea`, a reload's `shell_ready` was refused ("already
registered") and the shell gave up permanently, so `must_lock` was never
applied and `screenLocked` was never set. The reload stayed silently
unregistered instead of crashing. Fixing the registration made the reload reach
the lock path, which is correct behaviour meeting a real bug.
So: **the fix for TASK-48 is not to undo `45fbbea`.** An unregistered shell is
strictly worse — sessiond believed there was no shell for 90 minutes.
## What the fix has to respect
- **Do not gate the surface on `secure`.** `secure` is the compositor's ack that
a surface is up; using it as the precondition for putting one up is circular.
- **Adopting beats re-requesting.** The phone and laptop both run
`misc:allow_session_lock_restore = true`, which is what lets a relaunched
`qs -c souveraine` take over an abandoned lock. A reload should land on that
same path rather than racing a second acquire against a live holder.
- **Never open the panel to close the race.** Failing closed here means staying
locked and crashing loudly, which is what it does today. Any fix that trades
the crash for a briefly unlocked panel is worse than the crash.
- TASK-43's note applies verbatim: *the two-client lock handoff is the thing not
to get wrong.* Another session is building viewtop's ext-session-lock
admission gate — sync with it before touching this.
## Acceptance
1. `touch` any file in the composed tree while the session is locked. The shell
reloads, re-registers, sends `locked_ack`, and does **not** crash.
2. `~/.cache/quickshell/crashes/` gains no new directory across ten such reloads.
3. The panel never becomes visible during the reload — verify with `grim`, not
by looking, since the window is under a second.
4. `{"op":"device_state"}` reports `shell_alive: true` and `phase: released`
afterwards.
## Connects to
TASK-43 (viewtop's lock admission gate — the other half of two-client lock),
TASK-02 (lockscreen as a Rust system), `LOCK-DPMS-LESSONS.md` §1,
souveraine `45fbbea` (the registration fix that unmasked this).

View file

@ -58,7 +58,9 @@ Four that are cheap relative to what they unblock:
| # | Task | What's left |
|---|------|-------------|
| 46 | [Camera: everything but the DT](46-camera-imx363.md) | CAMSS + imx363/imx355 drivers are in-tree and configured; blueline's DT declares no camera. Beryllium is the same-sensor reference. |
| 48 | [Shell dies on any reload that owes a lock](48-lock-surface-reload-fatal.md) | Fully diagnosed 07-29, 11 crashes since 07-17. A reload re-requests ext-session-lock while the outgoing instance still holds it → `FATAL: Tried to show lockscreen surfaces without active lock`. **Sync with TASK-43 before touching the lock path.** |
| 47 | [Auto-brightness: anchors, none of it built](47-auto-brightness-anchors.md) | §12 reads as settled and has **zero** code behind its actuation half — no brightness `Action`, no anchors, `grep anchor src/` is empty. The evidence half (lux in, health tracked) is live. |
| 46 | [Camera: the rear sensor was the gap](46-camera-imx363.md) | Front IMX355 pair was already declared; rear IMX363 added 07-28 (kernel `c7784b716`, branch `mic-race-fix`). Not yet built or run — no camera has ever produced a frame on this device. |
| 44 | [Fingerprint (FPC1020)](44-fingerprint-fpc1020.md) | Piece 1 (wake source / input) is a session — DT + shim already extracted. Piece 2 (biometrics) is a TrustZone question, answer it before scheduling it. |
| 45 | [Haptics (CS40L20)](45-haptics-cs40l20.md) | **First: which chip actually drives the motor**`pmi8998_haptics` is what the dial's detents use today. Then port `cs40l2x` from Kirisakura; firmware blobs are committed. |
| 41 | [Producers must be attested](41-attested-producers.md) | Design first. Demonstrated live 07-28 — a squeeze and a dial-open both accepted from an SSH shell. |