a280c29d2ca3a0c97dc60dca2563fdac4ade4795
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f3ce42eb80 |
fix(deploy): report what enable actually did, and arm the tier's timers
Two lies in the unit loop, both reported as success: - `systemctl --user enable live-sync.service` fails on every deploy — the file has no [Install] section, because live-sync.timer is what arms it. The result was swallowed by `|| true` and the tool printed ENABLE anyway, so the line an operator reads after a rebuild was false on every box, on the repo's own heartbeat. The [Install] section is readable from the file being deployed, so the tool now checks it and prints INERT with the reason instead of making a call that cannot succeed; a real enable failure prints INERT too. - The loop walked *.service only. live-sync.timer was never enabled by anything, so a rebuilt box got the heartbeat and no schedule for it. Timers are walked now; a timer has no ExecStart, so the "does this unit belong on this box" test runs against the service it activates. The systemctl stub models systemd's refusal (no [Install] section -> non-zero), which is what makes the new tests red against the old loop; both new tests were run red first, and the five existing unit tests still pin #44's semantics. Refs #60 |
||
|
|
671222b881 |
docs(60): point-of-use corrections from the 2026-10-20 review
Measured fixes, one line each, from the review of everything that landed on 2026-10-20 (issue #60). Each page keeps its own facts; nothing was moved. - inventory/index, index: the census blurb says what the page actually covers (tracked-vs-box-only per file), not "all userspace configuration" - dotfiles-deploy: the wallpaper re-fetch recipe resolves the CDN URL through the wallhaven API — /w/<id> answers text/html, so curl -L never yields a JPEG; description widened to the whole page, tools/index blurb matched - okf-maintenance: the link example and the mistake bullet now agree with okf-spec, which RECOMMENDS absolute bundle paths (they said the opposite) - decisions/index: the unlisted-page gate is kb-coverage.py, not kb-lint - AGENTS.md: the three skill-config subsections are pointers again; the pages own issue tracker, labels and domain docs - swayidle-lock.service: Description names what the unit does (arms the idle lock over noctalia-shell IPC); suspend belongs to noctalia's inhibitor - common.pkgs: git-lfs re-justified by the tracked .gitattributes filter contract; the swayidle comment drops its lid-policy claim |
||
|
|
4666d0dce0 |
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. |
||
|
|
2b7b0bc3cd |
Make the idle-lock watcher survive a clean exit, and outlive a session restart
`swayidle-lock.service` was `enabled`, in `default.target.wants/`, and **dead for eight days**:
Active: inactive (dead) since Mon 2026-10-12 16:22:23 PDT; 1 week 1 day ago
Duration: 2d 21h 57min Main PID: 3181 (code=exited, status=0/SUCCESS)
Result=success NRestarts=0
`Restart=on-failure` cannot see a clean exit, and `swayidle -w` exits 0 on SIGTERM. So the
watcher that owns both the 5-minute idle lock and the `before-sleep` lock stopped existing, and
nothing on the box noticed. The consequence is not cosmetic: with `HandleLidSwitch=suspend` and
`HandleLidSwitchExternalPower=""` (which falls back to it), **closing the lid suspended the
machine unlocked**, because the hook that locks on sleep lived inside the unit that had died. No
alternative was running: `pgrep -c -u blankpad 'swayidle|swaylock|hyprlock|gtklock'` = 0, and
neither `autostart.kdl` launches a locker.
Three settings, each with a job:
- `Restart=always` — a supervisor must not assume it can only die badly.
- `StartLimitIntervalSec=0` — with `always`, a fast loop must never wedge the unit into `failed`;
a lock that keeps trying beats a lock that stopped trying.
- `PartOf=graphical-session.target` — it is wanted by `default.target`, so a session restart
brought the target back without it; now the watcher is restarted with the session.
What triggered the 16:22 exit is **unattributed**: the user journal has no entries in that window,
so all that can be said is that swayidle exited with status 0 — precisely what the old policy was
blind to. The fix does not depend on knowing the trigger, which is the point.
Verification is behavioural, per the law recorded with it: start the unit, `kill -TERM` the main
PID, and watch `NRestarts` increment with a new `MainPID` — the October event, survived. Not
performed here: starting it arms a 5-minute idle lock on a machine that has gone a week without
one, and that is Frank's call. `systemd-analyze --user verify` clean, `bats tests/` 71/71,
`kb-lint.sh omp-docs` 0, `kb-coverage.py` 0.
Refs #47
|
||
|
|
e278e4b863 |
Expand systemd specifiers before calling a unit foreign
The first real `deploy.sh` run on blankpad printed:
SKIP live-sync.service (ExecStart '%h/.omp/scripts/live-sync.sh' absent on this host)
while `/home/blank/.omp/scripts/live-sync.sh` sits right there. `%h` is expanded by systemd,
never by the shell, so the literal string was handed to `command -v`, failed, and the unit was
declared to belong to another machine — through the same code path whose whole purpose is to
keep a *foreign* unit inert, and silently. The victim was this repo's own heartbeat, and the
verdict was the same on every box that ever deployed it, because `%h` is exactly where the
checkout lives.
Nothing broke tonight only by luck of install state: `live-sync.service` is `linked`, driven
by its timer, so the heartbeat kept running while the deploy lied about it. A unit whose only
install route was this loop would simply never have been enabled anywhere.
`%h`, `%u` and `%U` are now expanded before resolution; an unrecognised specifier is left
alone so it fails loudly rather than being guessed at. Pinned by a test that writes a
`%h/.local/bin/helper` unit into the scratch home — red before (`SKIP`), green after
(`already linked; not started`), which also keeps the #45 rule intact: resolving a unit is not
licence to start it. Refs #44.
Verification: `bats tests/` 71/71, `kb-lint.sh omp-docs` 0 violations, `kb-coverage.py` 0
issues, `bash -n` clean.
|
||
|
|
ecc089c6ef |
deploy.sh no longer starts units a live session already runs
`sync` on a box with a live session on it was unsafe in two independent ways, found by preflighting the run rather than performing it. Refs #44. **`enable --now` would have armed a screen lock mid-session.** The unit loop started every `systemd-user/` unit whose `ExecStart` resolves. `swayidle-lock.service` on blankpad is `enabled`/`inactive` — nothing started it, no swayidle process exists, `autostart.kdl` does not launch one — so one `deploy.sh` would have begun blanking and locking the monitors while the operator was mid-sentence. `live-sync.service` is `linked`, driven by its timer, and `--now` forces a heartbeat commit at an arbitrary moment. A unit is now started only when systemd reports it as nothing-yet (`""`/`disabled`), which is precisely a fresh box; anything already installed is merely `enable`d. The state has to come from the **printed state**: `is-enabled` exits 1 for `linked` exactly as for `disabled`, so exit status cannot distinguish "installed, timer-driven" from "never installed" — the first version of this fix got that wrong and the test caught it. **`--check` output was being read as more than it can mean.** It prints `RELINK` for every path the `zz` tier carries, even when the live link already points at the override (`rules.kdl`, `keybinds.kdl`, `misc.kdl`, `autostart.kdl`, `.zshrc` all did on blankpad), because the walk visits the base package first and reports the intermediate owner change. The sentence committed in #43 — RELINK noise is "the sound of a stale override waiting" — is too strong and is corrected: the line proves a pending change only when `readlink` shows the live target is not already the `zz-$HOST` file, and `actions=0` is the honest summary. The stale-override hazard #43 removed was and is real; the signal for it is the target, not the line. **The dconf dump carried six of its 24 keys from another box**, none of them preferences: a Firefox file-chooser `last-folder-path` of `/home/dank/Downloads`, a nautilus `window-state` section, and `picture-uri`/`picture-uri-dark` pointing at `file:///home/dank/.config/background`. The last five showed no conflict because blankpad's live tree *already* held those `/home/dank` values — an earlier deploy wrote them here — and a foreign value that agrees with live is invisible to any diff while still being wrong. They are dropped; `picture-options='zoom'` stays, because it is a preference. The live wallpaper URIs are cleared on the box as part of landing this (niri draws the wallpaper, noctalia owns it, nothing reads a dead path). `tests/deploy.bats` grows by four, over a stub `systemctl` — the real one on this box is exactly what must not be invoked from a test. Red before: an already-`enabled` unit and a `linked` unit both get `--now`. Green after, including the two directions that keep the change honest: a `disabled` unit is still `enable --now`'d so a fresh box comes up, and a unit whose `ExecStart` is absent here is never touched at all. Verification: `bats tests/` 70/70, `kb-lint.sh omp-docs` 0, `kb-coverage.py` 0. The dump now reconciles against live state with **0 conflicts and no `/home/` values in 18 keys**, proven by key-by-key comparison rather than `diff` — `dconf load` writes only the keys present, so a diff overstates the damage one way and hides it the other. |
||
|
|
3022277167 |
Stop the deploy tier from reverting live theme state
Three findings from verifying the #41 merge against the live box with `deploy.sh --check`. One commit because they share the deploy tier, its documentation, and the first test file that tier ever had. **1. Generated palette output was tracked, and sync would have restored it over a live theme.** `dotfiles/zz-blankpad/niri/.config/niri/noctalia.kdl` (`b3d3df4`, 2026-10-18) sat byte-identical to the live file — harmless while the palette held, and `--check` was already proposing `ADOPT … noctalia.kdl`. The moment the theme changed, the next sync would have quarantined the live file and reinstated 2026-10-18's colours. `deploy.sh` now refuses the class outright (`SKIP … (generated theme output)`), `.gitignore` carries the matching patterns so it cannot be re-added, and the file is untracked. **2. A `zz` override older than the file it overrides.** `dotfiles/zz-blankpad/niri/…/ layout.kdl` held focus ring `width 2` / `#e01b24` (`7b7dbf5`, 2026-10-17) while base absorbed `b3d3df4` (2026-10-18): `width 4`, `#ff8c00`. Stow order makes `zz-*` win, and a cross-package owner change relinks *silently* — no quarantine, no warning. Live was linked to base, the newer intent, so `--check` printed `RELINK … layout.kdl` every run and one unattended `deploy.sh` would have reverted the amber focus ring. The duplicate is deleted; base owns the file. **3. Two topologies I had asserted and not measured.** The media law and the `.gitignore` comment both said `~/.local/share/noctalia/wallpapers` symlinks into the repo, so deleting the tracked copies would blank the desktop. Measured: it is a real directory of real files that Noctalia owns; the repo held a duplicate, and the #41 merge deleted that duplicate without the desktop flickering. The correct general law replaces the false one — untracking a path deletes the file in every other checkout, `git rm --cached` on the branch notwithstanding — and the restore procedure now points at the app's directory, which `deploy.sh` does not manage. The guard is `tests/deploy.bats`, black-box over exit status, stdout, the symlink shape under `$HOME`, and the quarantine directory. Before the change: test 1 and test 2 fail, test 2 because the live palette really is quarantined and reverted. After: 4/4, including the control that an ordinary identical config is still adopted — an exclusion nobody can turn off is just a broken tool. Verification: `bats tests/` 66/66, `kb-lint.sh omp-docs` 0 violations, `kb-coverage.py` 0 issues, and live `deploy.sh --check` on blankpad reports `SKIP … (generated theme output)` with `actions=0` instead of `ADOPT … noctalia.kdl`. The real `deploy.sh` run that clears the remaining `RELINK` lines is deliberately left until this is in main — running it from a worktree would repoint live symlinks into a checkout the heartbeat reaps. Refs #42 |
||
|
|
9ebf5fe85f |
Untrack a retired niri config that was committed as trash; correct two census rows
The repo still tracked `dotfiles/niri/.config/niri/cfg/.AGENTTRASH/display.kdl.superseded` — a retired output config committed 2026-10-09, five days before `.gitignore` grew the `.AGENTTRASH/` rule. Tracked-then-ignored: an ignore rule never reaches an already-tracked path, so `git rm --cached` is the only repair. The working copy joins the rest of today's tracked debt. Found while answering "does this repo still track my noctalia and niri settings", which also falsified two rows of the census committed yesterday: niri is 9 tracked files, not 12; noctalia is 3 authored files against 2672 files of app state and downloaded catalog, not 2/0. The page's own classifier command reproduces the new numbers — it never reproduced the old ones, which is the only reason they were wrong and not merely different. Two laws recorded alongside, both from the same probe: `config.kdl`'s `include "noctalia.kdl"` is not `optional=true`, so a fresh box cannot start niri until the theme pipeline has run once; and `cfg/display.kdl -> desk-1-home.kdl` is desk state a fresh box does not receive — by design, but nowhere written down. Refs #40 |
||
|
|
411b32ff59 |
Stop tracking fiction, and stop carrying 80 MiB of wallpaper per clone
Three things were in history that should not have been, all invisible to the tool that deploys them: - `home/dank/.omp/dotfiles/**` — 3 files re-hosting `niri/cfg/autostart.kdl`, `niri/cfg/keybinds.kdl` and `systemd-user/…/swayidle-lock.service` under a prefix `scripts/deploy.sh` never reads (it walks `dotfiles/<pkg>/`). The live package is deployed and symlinked, so the copy asserted nothing; it only misled. Moved to `.AGENTTRASH/20261020-tracked-debt/`. - `.pre-pull-backup/**` — 2 managed-skill files, a local backup in history, which `.gitignore` itself declares must stay on disk only. Same destination. - `dotfiles/noctalia/.local/share/noctalia/wallpapers/` — 23 images, 80.4 MiB. Untracked, files left in place: `~/.local/share/noctalia/wallpapers` symlinks into this directory and the theme reads it, so deleting them would have gone black. Every filename carries its wallhaven ID, so a fresh box restores the set with one `curl` per file plus `deploy.sh`; losing the set is an acceptable outcome, 80 MiB per clone forever is not. New page `omp-docs/tools/dotfiles-deploy.md` owns the deploy contract (the `--check`/sync/`--adopt` modes, `zz-$(hostname)` override semantics, the dconf and systemd-user special cases) because nothing documented it, plus two laws: media the pipeline consumes is content not config, and `systemctl --user enable` snapshots an absolute path — so enabling from a worktree means the seven-day reap silently kills the unit while `is-enabled` keeps answering `enabled`. That second law is not theoretical: `default.target.wants/blankpad-desk-autoswitch.service` pointed into the reaped `wt/de-redesign-20261009` and desk auto-switching had been off since. It is relinked to `dotfiles/zz-blankpad/` and left inactive rather than started mid- session; starting it applies a desk profile. Refs #40 |