Correct the lock-watcher law: the trigger is the session restart, and suspend was covered

Two statements in the law added by #48 need measurement rather than inference, and one of them was
wrong.

**Attributed.** `niri` and `noctalia` both started `Mon Oct 12 16:22:31 2026` (`ps -o lstart=`);
swayidle's last state change was `16:22:23`. The compositor session restarted and the watcher died
with it — eight seconds of agreement, which is what `PartOf=graphical-session.target` is for. The
fix that #48 justified by reasoning is now justified by the event itself. The user journal holds no
entries for that window, so the evidence is process ages, not logs.

**Corrected.** #48 said closing the lid suspended blankpad unlocked, because `before-sleep` lived in
the dead unit. `systemd-inhibit --list` says otherwise: `noctalia … sleep "Lock before sleep" delay`
— the shell holds its own sleep inhibitor and locks on suspend independently of swayidle, so the
suspend path held all along. What was dead for eight days is the **idle** lock and `after-resume`.
The method error is worth keeping: `pgrep` for lockers came back empty and I read that as "no lock
path", when a locker can be a hook inside a running shell. The inhibitor table is the instrument.

Added with them, because it answers a question people will ask: while an `omp` session runs, swayidle
logs `Not enabling timeouts: idle inhibitor found` and arms no idle timeout (`omp … idle "omp agent
session" block`). A box with an agent working does not lock on inactivity, by harness design.

Docs only; the unit file from #48 is unchanged and the live watcher is `active` with `NRestarts=1`
after the SIGTERM proof. `kb-lint.sh omp-docs` 0 violations, `kb-coverage.py` 0 issues, `bats tests/`
71/71. Refs #47.
This commit is contained in:
Frank
2026-10-20 17:52:59 -07:00
parent d013795922
commit 4666d0dce0
+24 -6
View File
@@ -210,12 +210,23 @@ Refs #44.
`swayidle-lock.service` was `enabled`, present in `default.target.wants/`, and **dead for eight
days**: `Result=success`, `NRestarts=0`, gone since 2026-10-12 16:22 after 2d 21h of uptime.
`Restart=on-failure` cannot see a clean exit, and `swayidle -w` exits 0 on SIGTERM — so the
watcher that owns the 5-minute idle lock *and* the `before-sleep` lock quietly stopped existing,
and nothing noticed. With it dead and logind at `HandleLidSwitch=suspend`, closing the lid
suspended the machine **unlocked**: the lock hook lived inside the thing that had died. No
alternative existed — `pgrep -c -u blankpad 'swayidle|swaylock|hyprlock|gtklock'` = 0, and neither
`autostart.kdl` launches a locker.
`Restart=on-failure` cannot see a clean exit, and `swayidle -w` exits 0 on SIGTERM. The trigger is
attributable to eight seconds: **`niri` and `noctalia` both started `Mon Oct 12 16:22:31 2026`**
(`ps -o lstart=`) while swayidle's last state change was `16:22:23` — the compositor session
restarted, the watcher went with it, and because it was wanted by `default.target` rather than tied
to the session target, nothing brought it back. The user journal keeps no entries for that window,
so the attribution rests on process ages rather than logs, which is its own lesson: diagnose a
lapse within the retention you actually have.
**The severity claim in this page's first draft was wrong, and is corrected here.** It said closing
the lid suspended the machine unlocked, because the `before-sleep` hook lived in the dead unit. But
`systemd-inhibit --list` shows `noctalia … sleep "Lock before sleep" delay` — the shell holds its
own sleep inhibitor and locks on suspend independently of swayidle, so the suspend path was covered
the whole time. What was actually dead for eight days is the **idle** lock: five minutes of
inactivity locked nothing, and `after-resume` never fired. `pgrep -c -u blankpad
'swayidle|swaylock|hyprlock|gtklock'` = 0 remains true; it simply did not mean *no lock path at
all*, because a locker can be a hook inside a running shell rather than a name on the process list.
Read the inhibitor table, not only `pgrep`.
Three settings, each with a job: `Restart=always`, because a supervisor must not assume it can
only die badly; `StartLimitIntervalSec=0`, because a fast loop must never wedge it into `failed` —
@@ -232,6 +243,13 @@ kill -TERM "$(systemctl --user show swayidle-lock.service -p MainPID --value)"
sleep 8 && systemctl --user show swayidle-lock.service -p NRestarts -p MainPID -p ActiveState
```
An idle inhibitor also disarms the timeout outright, which surprises people: while an `omp` agent
session is running, swayidle logs `Not enabling timeouts: idle inhibitor found` and arms no idle
timeout at all — `systemd-inhibit --list` shows `omp … idle "omp agent session" block`. A box with
an agent working therefore does not lock on inactivity, by harness design, while `before-sleep` and
`after-resume` keep working. Anyone asking "why did the screen never lock" should read the inhibitor
table before suspecting the unit.
The general shape: `deploy.sh` only ever *enables*. Nothing on any box re-checks that a unit the
system depends on is still running, so a security posture can lapse silently and stay lapsed for a
week. A liveness check for this tier belongs in the heartbeat, not in a human's memory. Refs #47.