Watch
1
0
Fork
You've already forked SouveraineOS
0

docs: source health is §10; correct the TASK-26 retirement

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.
This commit is contained in:
Fimeg 2026-07-26 08:38:35 -04:00
commit cd3d57959b
3 changed files with 141 additions and 17 deletions

View file

@ -30,10 +30,10 @@ does (LOCK-DPMS-LESSONS §1 + doctrine §8). hypridle's 300 s lock and 600 s
screen-off listeners are **deleted**, not disabled: they were the competing
writer that made §1 hold only because 300 < 600.
Still open: the sensor feeder (`sensor_input` has senders now via
`blueline-proximity-lock`, but SLPI health gating is not built), doze tiers
(TASK-15, blocked on slice separation), and both confidence gates are still
computed and logged without being branched on.
Still open: doze tiers (TASK-15, blocked on slice separation), and both
confidence gates are still computed and logged without being branched on.
Source health landed 2026-07-26 (§10) — the machine now knows when a sensor is
absent rather than merely quiet.
## 0. Audit, 2026-07-26 — what the running machine actually did
@ -74,13 +74,14 @@ Two divergences to settle when wiring, code-first per doc precedence:
suppressed; the code computes 0.4+0.30.2 = 0.5 ≥ 0.3 and suppresses it
anyway (its own test asserts the suppression).
Not in this doc, and needed before any of it can be trusted: **evidence-source
health is device state.** On 2026-07-25 SLPI took a CHRE fatal, remoteproc
recovered it, `blueline-hexagonrpcd-sdsp` exited "successfully" and was never
restarted — every sensor was dead for hours and nothing noticed, because a
consumer of a dead sensor looks identical to one whose sensor is quiet. See
`Pixel3Arch/PAF/slpi.md`. "No evidence" and "evidence says nothing is
happening" must not be the same state.
**FIXED 2026-07-26 — evidence-source health is device state.** On 2026-07-25
SLPI took a CHRE fatal, remoteproc recovered it, `blueline-hexagonrpcd-sdsp`
exited "successfully" and was never restarted — every sensor was dead for hours
and nothing noticed, because a consumer of a dead sensor looks identical to one
whose sensor is quiet. See `Pixel3Arch/PAF/slpi.md`. "No evidence" and "evidence
says nothing is happening" must not be the same state. They no longer are —
see §10.
**Supersedes:** the scattered state machines in IdleCoordinator.qml (7 states),
sessiond server.rs Phase (4 states), blueline-screen-toggle (2 states), and
blueline-proximity-lock (2 states). Those are projections of this model, not
@ -616,3 +617,98 @@ drop detection as a power state machine input.
before declaring "far"). Our design should adopt this — proximity-near
should suppress DPMS immediately, but proximity-far should wait 250ms
before re-enabling it (prevents flicker when the sensor is noisy).
---
## 10. Evidence-source health (built 2026-07-26)
§4 says what a reading is worth. This section says whether there is a reading
at all, which §4 assumed and never checked.
### Why staleness was not enough
`EVIDENCE_TTL` (30 s) already expired old readings, and its comment already
named the SLPI outage. It still could not have caught it. `expire_stale_evidence`
only acts on a source whose flag is currently `true` — it exists to stop a
30-second-old "near" from suppressing a wake. A proximity sensor resting at
`far` has a flag of `false`: nothing to expire, nothing anomalous about the
silence, no entry in any log. That is the shape of the outage. Four hours of
dead sensors produced exactly zero lines.
The two questions are different and both are needed:
| | question | threshold | what it protects |
|---|---|---|---|
| `evidence_fresh` | may this *reading* be believed? | `EVIDENCE_TTL` 30 s | a rule acting on a stale value |
| `sensor_health` | is this *source* there? | `SOURCE_DOWN_AFTER` 90 s | a rule acting on absence as if it were a negative |
### Three states, and why `Unknown` is not a failure
`Unknown` → never heard from. `Live` → reported within the window. `Down`
reported once and then stopped.
Only a source that spoke and then went silent has failed. Accel, light and touch
have no reporters on this device today, so they sit at `Unknown` forever, which
is correct: a signal that fires for hardware nobody wired up is a signal nobody
reads. Down is rare by construction, and therefore worth reading.
### Health is not read from `net.hadess.SensorProxy`
`HasProximity` / `HasAccelerometer` is the obvious source of truth and it is
wrong. It lies in both directions, both measured (`PAF/slpi.md`):
- 2026-07-25 12:19 — stack dead, proxy answered `HasProximity: true` for hours,
and the first client to claim a sensor segfaulted it.
- 06:04 incident — proxy answered `false` after remoteproc had already
recovered SLPI.
A property that is wrong in both directions is not an authority. What the
machine can actually trust is its own experience: whether readings arrive. This
is the same move as `lockhint.rs` making logind the lock truth — read the thing
that cannot be wrong about itself, not the thing that summarises it.
### Silence had to be made meaningful
`monitor-sensor` emits only on change, so a phone on a table is silent for hours
and is byte-for-byte indistinguishable from a CHRE fatal. Without fixing that,
the only options were never detecting a dead stack or crying wolf every quiet
afternoon.
So reporters heartbeat: `blueline-proximity-lock` re-sends its last reading
every 30 s, well inside the 90 s threshold, and seeds that value from the
startup probe banner so a stack that was dead from boot is still visible. As
long as the reporter and the sensor behind it live, sessiond hears from
proximity every 30 s. If it stops hearing, something is actually wrong.
The repeats are idempotent — the machine re-derives identical evidence — and
they do **not** append to the trail, or proximity alone would add ~2,900 lines a
day saying nothing changed to a log that §0 records as unbounded and on tmpfs.
The dedupe skips the trail write only, never the evaluation: an unchanged
reading can still change the answer, because proximity held `near` across a lock
flips `should_be_observed` with no change in the evidence at all.
### What the trail can now answer
- `error-security` `source-down` on the outage edge, once, not per tick.
- `source-recovered` on the way back, which is what *bounds* the window. An
outage with no closing entry tells you when the sensors died and never when
they returned.
- `sensors_degraded` on **every** forensic snapshot. This is the part that makes
the log diagnostic rather than merely complete: a `panel-off` recorded during
four dead hours no longer reads like a healthy one. Reconstruction after the
fact can ask "what was the machine deciding on?" and not just "what did it do?"
### Delivery order matters, and it is TASK-28's case exactly
The two halves ship by different routes — sessiond in the `souveraine` package,
the reporter in `rootfs-overlay/`. New sessiond with the old reporter would
declare proximity down every 90 s, forever, because nothing heartbeats. The
reporter went to the device first, deliberately. That asymmetry is the argument
TASK-28 is made of.
### Still not covered
Health answers "is the source there". It does not answer "is the source telling
the truth" — a proximity sensor stuck reporting `near` heartbeats perfectly and
reads as `Live`. §4's cross-sensor disagreement machinery is where that would
live, and it is still computed and logged without being branched on.

View file

@ -26,6 +26,31 @@ 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

View file

@ -55,12 +55,15 @@ proximity-blanks-locked-screen needs sensors back (SLPI, TASK-09 neighbours),
and the shell handshake needs a cold boot — restarting sessiond mid-session
drops `shell_alive` (that gap is TASK-28).
**TASK-26 was never written and no longer needs to be.** Its subject was
`blueline-proximity-lock` running as a second authority over proximity. That is
resolved: as of 2026-07-25 it reports to sessiond and decides nothing, and its
one remaining loose end (unpackaged) belongs to TASK-27. The dangling references
in `STATE.md`, TASK-25 and TASK-27 now say so instead of pointing at a file that
does not exist.
**TASK-26 was never written and no longer needs to be.** It had two subjects
and both are now closed. `blueline-proximity-lock` running as a second authority
over proximity: resolved 2026-07-25, it reports to sessiond and decides nothing
(its remaining loose end, unpackaged, belongs to TASK-27). Evidence-source health
as device state — the half `PAF/slpi.md` was actually pointing at, and which the
first retirement note read past: built 2026-07-26,
`DEVICE-STATE-MACHINE.md` §10. The dangling references in `STATE.md`, TASK-25,
TASK-27 and `PAF/slpi.md` now say so instead of pointing at a file that does not
exist.
## Conventions