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:
parent
fccee4ba3d
commit
cd3d57959b
3 changed files with 141 additions and 17 deletions
|
|
@ -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.3−0.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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue