Commit Graph
101 Commits
Author SHA1 Message Date
Frank 22d2f3bcd8 chore(merge): land origin/main (PR #61, PR #63) in the deployment checkout
Two conflicts, both resolved to trunk, both proven content-free for the running
machine before the merge was committed:

- `agent/APPEND_SYSTEM.md` — the checkout's dirty copy and trunk differ in one line
  only: #61's trim of the duplicated one-log reasoning, replaced by the owner pointer.
  The doctrine text itself is byte-identical on both sides.
- `desk-1-home.kdl` — non-comment lines are byte-identical (`diff` on the stripped
  file: no output), so the deployed modes are unchanged: `3440x1440@180.000` ×3 and
  `1920x1080@60.001`. What changes is the comment: trunk's carries the measured mode
  list (`3440x1440@239.998` exists), the reason 180 is deliberate, and the verbatim
  mode-string law — the live copy still asserted "no 240 Hz mode exists", which the
  panel contradicts. `niri validate` on the assembled config: valid.

Deployment state after this: the checkout is main, and `agent/memories/**` +
`agent/managed-skills/**` were committed by the heartbeat commit underneath. Refs #60 #62
2026-10-21 01:45:33 -07:00
Frank 8af40c402b chore(agent): commit the machine tier and the doctrine text the checkout was already running
Landing blocker for #61 and #63: the deployment checkout is 12 commits behind and
`merge --ff-only` refuses while tracked paths are dirty. Discard is not available here
(workspace policy: no `checkout`/`stash` on shared state), so the dirt is committed.

- `agent/APPEND_SYSTEM.md`: the point-of-use / one-work-log / PR-link doctrine, already
  byte-identical to `origin/main` (`cmp` against the trunk blob: no difference). No new
  prose — the commit exists so the trunk merge can proceed.
- `agent/config.yml` and `agent/managed-skills/**`: the machine-live tier the heartbeat
  owns (AGENTS.md § Live-sync tiers).

`ALLOW_SECRETS=1` was used, deliberately and after auditing the staged diff by hand: the
only credential-shaped strings in it are two copies of `AKIA0000000000000EXAMPLE` — the
inert all-zero AWS example id — and they appear inside the two skill pages that teach
that exact placeholder shape. This checkout is still running the pre-#61 hook; the merged
gate (value-scoped placeholder filter, `71601f7`) exempts this value, so the refusal
dies with this landing. No `--no-verify` was used: the gate ran and its verdict is
recorded here.

Refs #61
2026-10-21 01:44:51 -07:00
Frank 334e5d0ee0 docs(niri): the ultrawide's refresh ceiling was a typo, not a limit
The page asserted the Sceptre M34's EDID tops out at 180 Hz. Measured on blankpad
after the merge, `niri msg outputs` lists 3440x1440@239.998 — the claim was false
in main, and it was load-bearing: it is the reason nobody questioned the profile
while the panel ran at 60 Hz for weeks. The real defect was one character: the
profile asked for 239.999, the panel offers 239.998, and niri matches mode strings
verbatim, falling back to *preferred* without failing.

Corrected to the measured mode list, and the deployed 180 is now documented as a
deliberate choice (the link is trained at x8 32.0 GT/s against a max of x16 and the
display engine throws EINVAL), so the next reader neither "restores 240" blind nor
re-pins 180 as a hardware ceiling. The kdl profile comment carries the same facts at
the point of edit, because that is where the next change to this mode gets typed.

Refs #60
2026-10-21 01:06:04 -07:00
Frank 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
2026-10-21 00:54:06 -07:00
Frank 23372e96f5 fix(niri): pin the ultrawide to the 180 Hz mode its EDID actually offers
desk-1-home.kdl asked the Sceptre M34 for 3440x1440@239.999, a mode the panel
has never offered. niri does not reject an unavailable mode: it logs "configured
mode ... could not be found, falling back to preferred" and drives the EDID
preferred mode - 60 Hz - on every connector event, which is why the monitor
looked like it was dying rather than misconfigured. Read live with
`niri msg outputs`, the mode list tops out at 180.

Also destroys the copies of the phantom number: displays.md carried 239.999 in
its diagram, its panel table and its copy-paste pattern block, and its rule 4
claimed an impossible mode leaves the output dark. Rule 4 now states the real
failure mode, a new section separates config / DP-link / amdgpu-driver faults by
log signature, and wlr-randr is corrected to not-installed (three pages claimed
it; `pacman -Qs wlr` matches nothing).

Applied live with `niri msg output <panel> mode <mode>` - the temporary
per-output path, so no config reload and no logged-out session - verified at
3440x1440 @ 180.000 Hz with the compositor PID unchanged.

Refs #58
2026-10-21 00:05:17 -07:00
Frank 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
2026-10-20 17:24:32 -07:00
Frank 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.
2026-10-20 17:13:55 -07:00
Frank 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
2026-10-20 17:08:06 -07:00
Frank 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
2026-10-20 17:00:20 -07:00
Frank 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
2026-10-20 16:48:33 -07:00
Frank 0d43eb0948 live: heartbeat sync 2026-10-20T16:17:41-07:00 (Refs #11) 2026-10-20 16:17:41 -07:00
Frank 6af3714bd0 live: heartbeat sync 2026-10-20T15:15:41-07:00 (Refs #11) 2026-10-20 15:15:41 -07:00
Frank 495e2229f5 live: heartbeat sync 2026-10-20T14:12:41-07:00 (Refs #11) 2026-10-20 14:12:41 -07:00
Frank 37ef4897ae live: heartbeat sync 2026-10-20T13:10:41-07:00 (Refs #11) 2026-10-20 13:10:41 -07:00
Frank 06e549cb27 live: heartbeat sync 2026-10-20T12:07:41-07:00 (Refs #11) 2026-10-20 12:07:41 -07:00
Frank c96741794a live: heartbeat sync 2026-10-20T11:04:41-07:00 (Refs #11) 2026-10-20 11:04:41 -07:00
Frank e14d0906ea live: heartbeat sync 2026-10-20T10:02:41-07:00 (Refs #11) 2026-10-20 10:02:41 -07:00
Frank d5be16127b live: heartbeat sync 2026-10-20T08:59:41-07:00 (Refs #11) 2026-10-20 08:59:41 -07:00
Frank b7aef40266 live: heartbeat sync 2026-10-20T07:57:45-07:00 (Refs #11) 2026-10-20 07:57:45 -07:00
Frank fafdaa2617 live: heartbeat sync 2026-10-20T06:56:41-07:00 (Refs #11) 2026-10-20 06:56:41 -07:00
Frank 7213068d91 live: heartbeat sync 2026-10-20T05:55:41-07:00 (Refs #11) 2026-10-20 05:55:41 -07:00
Frank 05dbe8c5c1 live: heartbeat sync 2026-10-20T04:54:41-07:00 (Refs #11) 2026-10-20 04:54:41 -07:00
Frank 806352f8f6 live: heartbeat sync 2026-10-20T03:51:41-07:00 (Refs #11) 2026-10-20 03:51:41 -07:00
Frank 34a7af2847 live: heartbeat sync 2026-10-20T02:50:41-07:00 (Refs #11) 2026-10-20 02:50:41 -07:00
Frank 635c64d773 live: heartbeat sync 2026-10-20T01:48:41-07:00 (Refs #11) 2026-10-20 01:48:41 -07:00
Frank 64d9c54657 live: heartbeat sync 2026-10-20T00:45:41-07:00 (Refs #11) 2026-10-20 00:45:41 -07:00
Frank aec7f0f068 live: heartbeat sync 2026-10-19T23:44:41-07:00 (Refs #11) 2026-10-19 23:44:41 -07:00
Frank 4023b21365 live: heartbeat sync 2026-10-19T22:41:41-07:00 (Refs #11) 2026-10-19 22:41:41 -07:00
Frank e14fc884a3 live: heartbeat sync 2026-10-19T21:40:41-07:00 (Refs #11) 2026-10-19 21:40:41 -07:00
Frank b3a84c5bb8 live: heartbeat sync 2026-10-19T20:37:41-07:00 (Refs #11) 2026-10-19 20:37:41 -07:00
Frank 9e6254108a live: heartbeat sync 2026-10-19T19:34:41-07:00 (Refs #11) 2026-10-19 19:34:41 -07:00
Frank ce87958b77 live: heartbeat sync 2026-10-19T18:31:41-07:00 (Refs #11) 2026-10-19 18:31:41 -07:00
Frank 802014fea9 live: heartbeat sync 2026-10-19T17:29:41-07:00 (Refs #11) 2026-10-19 17:29:41 -07:00
Frank e22acf4913 live: heartbeat sync 2026-10-19T16:27:41-07:00 (Refs #11) 2026-10-19 16:27:41 -07:00
Frank c14c64d6c0 live: heartbeat sync 2026-10-19T15:24:41-07:00 (Refs #11) 2026-10-19 15:24:41 -07:00
Frank 6e3e5594eb live: heartbeat sync 2026-10-19T14:21:41-07:00 (Refs #11) 2026-10-19 14:21:41 -07:00
Frank ee292ffd10 live: heartbeat sync 2026-10-19T13:20:41-07:00 (Refs #11) 2026-10-19 13:20:41 -07:00
Frank 2faeab665a live: heartbeat sync 2026-10-19T12:19:36-07:00 (Refs #11) 2026-10-19 12:19:36 -07:00
Frank 62b3b92e69 live: heartbeat sync 2026-10-19T11:18:41-07:00 (Refs #11) 2026-10-19 11:18:41 -07:00
Frank ac5b377750 live: heartbeat sync 2026-10-19T10:17:52-07:00 (Refs #11) 2026-10-19 10:17:52 -07:00
Frank e4ecc93f29 live: heartbeat sync 2026-10-19T09:17:41-07:00 (Refs #11) 2026-10-19 09:17:41 -07:00
Frank d055eb1e7f live: heartbeat sync 2026-10-19T08:15:41-07:00 (Refs #11) 2026-10-19 08:15:41 -07:00
Frank 70346f3d57 live: heartbeat sync 2026-10-19T07:12:41-07:00 (Refs #11) 2026-10-19 07:12:41 -07:00
Frank 1251cfbd40 live: heartbeat sync 2026-10-19T06:09:41-07:00 (Refs #11) 2026-10-19 06:09:41 -07:00
Frank e67bbd0de0 live: heartbeat sync 2026-10-19T05:07:41-07:00 (Refs #11) 2026-10-19 05:07:41 -07:00
Frank cd20d6ddfd live: heartbeat sync 2026-10-19T04:06:41-07:00 (Refs #11) 2026-10-19 04:06:41 -07:00
Frank d99c1c3c4f live: heartbeat sync 2026-10-19T03:04:41-07:00 (Refs #11) 2026-10-19 03:04:41 -07:00
Frank 01d9d3fbd5 live: heartbeat sync 2026-10-19T02:02:41-07:00 (Refs #11) 2026-10-19 02:02:41 -07:00
Frank edd7e0cf68 live: heartbeat sync 2026-10-19T01:00:41-07:00 (Refs #11) 2026-10-19 01:00:41 -07:00
Frank 54f15e2a58 live: heartbeat sync 2026-10-18T23:57:41-07:00 (Refs #11) 2026-10-18 23:57:41 -07:00