From 4666d0dce000c92e13a724b88707ef3a50cc5984 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 20 Oct 2026 17:52:59 -0700 Subject: [PATCH] Correct the lock-watcher law: the trigger is the session restart, and suspend was covered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- omp-docs/tools/dotfiles-deploy.md | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/omp-docs/tools/dotfiles-deploy.md b/omp-docs/tools/dotfiles-deploy.md index 4ff526d..e558953 100644 --- a/omp-docs/tools/dotfiles-deploy.md +++ b/omp-docs/tools/dotfiles-deploy.md @@ -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. -- 2.54.0