Compare commits

..
Author SHA1 Message Date
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
dank 689ada428f Merge pull request 'Quarantine what the gate could not see; delete what the repo only pretended to own' (#41) from fix/40-dotfiles-hygiene into main 2026-10-20 17:02:53 -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
8 changed files with 167 additions and 98 deletions

No files matched your search

+17 -6
View File
@@ -80,10 +80,21 @@ wt/
agent/memories/--home-blank-.omp-wt-*
# --- theme media: content the pipeline consumes, not configuration ---------
# 23 wallpapers, 80 MiB. Tracked until 2026-10-20; every clone of a config repo
# was carrying a library no diff can review. The files stay on disk because
# ~/.local/share/noctalia/wallpapers symlinks into here, and every filename
# carries its wallhaven ID, so a fresh box restores them with
# `curl -L -o <name> https://wallhaven.cc/w/<id>` + deploy.sh.
# Law: omp-docs/tools/dotfiles-deploy.md
# 23 wallpapers, 80 MiB. Tracked until 2026-10-20: every clone of a config repo was
# carrying a library no diff can review. The live library at
# ~/.local/share/noctalia/wallpapers is a real directory of real files (measured
# 2026-10-20), so this path in the repo was a duplicate of it, not its source — and
# note that untracking deletes the copy in every other checkout regardless of
# `git rm --cached` on the branch. A fresh box starts with an empty library; every
# filename carries its wallhaven ID, so `curl -L -o <name> https://wallhaven.cc/w/<id>`
# puts it back. Law: omp-docs/tools/dotfiles-deploy.md
dotfiles/noctalia/.local/share/noctalia/wallpapers/
# --- generated theme output: machine-written, never reviewable, never deployed ---
# noctalia/matugen rewrite these in place; a tracked copy is a stale snapshot that
# deploy.sh would push back over a live palette. Patterns match the exclusion list in
# scripts/deploy.sh. Law: omp-docs/inventory/home-config-census.md, Refs #42
dotfiles/**/themes/noctalia*
dotfiles/**/colors/noctalia*
dotfiles/**/noctalia.*
dotfiles/**/noctalia-*
@@ -1,21 +0,0 @@
// ────────────── Output Configuration ──────────────
output "Samsung Electric Company Odyssey G75F HNTY601056" {
mode "3840x2160@60"
scale 1
transform "normal"
position x=0 y=0
}
output "DP-1" {
transform "90"
position x=0 y=0
}
output "DP-3" {
transform "270"
position x=1200 y=0
}
output "eDP-1" {
// no position — auto-placed to the right of DP-* outputs
}
@@ -1,29 +0,0 @@
layout {
gaps 16 // Gap between windows
center-focused-column "never" // Don’t auto-center focused column
background-color "transparent" // <- needed for noctalia-shell to set wallpaper
preset-column-widths {
proportion 0.25 // 1/4
proportion 0.3333 // 1/3
proportion 0.5 // 1/2
}
preset-window-heights {
proportion 0.25 // 1/4
proportion 0.5 // 1/2
}
struts {
}
shadow
focus-ring {
width 2
active-color "#e01b24"
}
border {
off
}
}
@@ -1,32 +0,0 @@
layout {
focus-ring {
active-color "#ff8c00"
inactive-color "#181818"
urgent-color "#ff8c00"
}
border {
active-color "#f43841"
inactive-color "#181818"
urgent-color "#f43841"
}
tab-indicator {
active-color "#f43841"
inactive-color "#74050b"
urgent-color "#f43841"
}
insert-hint {
color "#f4384180"
}
}
recent-windows {
highlight {
active-color "#f43841"
urgent-color "#f43841"
}
}
+3 -2
View File
@@ -83,8 +83,8 @@ by category, not file-by-file.
| micro | 0 | 154 | adopt: `settings.json`, `bindings.json`; generated: `colorschemes/noctalia.micro`; bundled/state: `syntax/*` (146), `buffers/history` |
| mpv | 0 | 0 | — (empty) |
| nautilus | 0 | 0 | — (empty) |
| niri | 12 | 1 | `noctalia.kdl` (generated) |
| noctalia | 2 | 0 | — |
| niri | 8 | 2 | 8 live KDL includes (5 from `dotfiles/niri`, 3 overridden by the `zz-blankpad` tier, whose own 7 niri files are counted in that tier — the stale `layout.kdl` duplicate and generated `noctalia.kdl` were removed by #43) + see *Never track* for the relic. Box-only: `noctalia.kdl` (generated include, refused by `deploy.sh`) and the retired copy |
| noctalia | 3 | 2672 | Tracked and live-linked: `config.toml`, `palettes/Gruber Red.json`, `state/noctalia/settings.toml`. Everything else is app state plus noctalia's downloaded catalog: `community-palettes/` 66, `community-templates/` 80, `wallhaven/thumbs`, `clipboard/`, `plugins/`, `state.toml`, `usage_counts.json`. The app's wallpaper library is `~/.local/share/noctalia/wallpapers` — real files it owns; the repo's duplicate copy of 23 JPEGs was untracked by #41 and deleted from this checkout by the merge, which is what untracking does in every checkout |
| nvim | 0 | 0 | — (empty) |
| pywalfox | 0 | 1 | `config.json` — program absent, prune |
| qt5ct | 0 | 1 | `colors/noctalia.conf` (generated) — program absent, prune |
@@ -138,6 +138,7 @@ configs).
| **App state / profiles** | `mozilla` (5471), `unity3d` RimWorld state (235), `nirimod` (116), `obsidian` (99), `FreeTube` (56), `google-chrome-for-testing` (5); plus per-program state inside tracked dirs: `dconf/user`, `fish/fish_variables`, `micro/syntax/*`+`micro/buffers/history`, `gtk-4.0/servers`, `cachyos/cachyos-hello.log`. |
| **Skills-CLI installs** | `crush` (37), `devin` (37), `goose` (37), `kimchi/harness` (37) skill trees — provisioned by the skills CLI, owned by the store, not this repo. See [skills/store-wiring](/skills/store-wiring.md). |
| **Secret tier** | `~/.config/secrets/` (`gitea`, `hf_token`, `hf_token_write`, `kagi_api_key` — names only), top-level `frank-job-hunt.env`, `containers/systemd/postgres.env`. Never tracked, values never printed. |
| **Trash committed inside the tree** | `dotfiles/niri/.config/niri/cfg/.AGENTTRASH/display.kdl.superseded` — a retired output config committed 2026-10-09, five days before `.gitignore:54` gained `.AGENTTRASH/` (2026-10-14). Tracked-then-ignored: an ignore rule never reaches an already-tracked path, so the only repair is `git rm --cached`, done by #41. The rule does match at depth — `git check-ignore --no-index -v dotfiles/niri/.config/niri/cfg/.AGENTTRASH/x` resolves to `.gitignore:54` — so a fresh in-tree trash dir is ignored from birth and this does not recur on its own. |
## Owned-elsewhere (do not adopt here)
+63 -8
View File
@@ -32,7 +32,7 @@ instead of crash-looping.
**The tracked unit is the file, not the directory.** A live directory can be a real
directory and still be fully tracked, because every file inside it is a symlink into the
repo (`niri` is 12 tracked / 1 box-only; `gtk-3.0` is 3/0). Any audit that classifies by
repo (`niri` is 9 tracked / 1 box-only; `gtk-3.0` is 3/0). Any audit that classifies by
directory mislabels the tree — classify per file with `readlink -f`. The live census and
its classifier command are in [home-config-census](/inventory/home-config-census.md).
@@ -42,14 +42,18 @@ Wallpapers live at `dotfiles/noctalia/.local/share/noctalia/wallpapers/` and are
gitignored. They were tracked until 2026-10-20 — 23 files, 80 MiB — which meant every
clone of a *configuration* repo carried a media library no diff can review, and the
payload only ever grows because deleting a tracked blob from the tree does not delete it
from history. [Noctalia](noctalia-shell.md) reads
`~/.local/share/noctalia/wallpapers/`, which is a set of symlinks into that directory, so
the files stay on disk and untracking them broke nothing on this box.
from history. The topology is not what one would guess: [Noctalia](noctalia-shell.md)
keeps its own library at `~/.local/share/noctalia/wallpapers/` as a **real directory of
real files**, so the tracked tree was a duplicate of it and never its source — measured
2026-10-20, when the merge of #41 deleted the repo copies and the desktop did not
flicker. Two consequences to know before untracking media: the copy that survives belongs
to the application, not to the repo; and untracking deletes the file in every other
checkout, `git rm --cached` on the branch notwithstanding.
Restore on a fresh box: every filename is `wallhaven_<id>.<ext>` and the ID is the whole
address — `curl -L -o <name> "https://wallhaven.cc/w/<id>"` puts the library back, then
`deploy.sh` relinks it. Losing the set is an acceptable outcome; 80 MB per clone forever
is not.
Restore on a fresh box: the library lives in `~/.local/share/noctalia/wallpapers/`, a path
`deploy.sh` does not manage. Every filename is `wallhaven_<id>.<ext>` and the ID is the
whole address — `curl -L -o <name> "https://wallhaven.cc/w/<id>"` into that directory puts
the set back. Losing it is an acceptable outcome; 80 MB per clone forever is not.
## Law: never `systemctl --user enable` a unit from a worktree
@@ -70,3 +74,54 @@ ls -l ~/.config/systemd/user/default.target.wants/
Fix is to relink into the deployment checkout (`~/.omp/dotfiles/…`), `systemctl --user
daemon-reload`, then confirm with `is-active` — `is-enabled` proves nothing.
## Law: a generated file must never be a required include
`~/.config/niri/config.kdl` ends with `include "noctalia.kdl"` — no `optional=true`.
That file is noctalia's palette output (`#ff8c00`, `#f43841`, one re-stamp
`2026-10-18 17:36:27`), so it is machine state and correctly untracked. But on a fresh
box `deploy.sh` links `config.kdl` first, and niri then refuses to start until noctalia
has run once and written the file — the compositor is bricked by the order of operations,
not by a bad config. Close it at the include, which survives someone skipping the theme
pipeline: mark generated includes `optional=true`, as the `display.kdl` and
`switch-events.kdl` lines in the same file already are.
## Desk choice is live state, never config
`~/.config/niri/cfg/display.kdl` is a relative symlink to whichever profile is active
(`desk-1-home.kdl`, `desk-2-portable.kdl`), written by `desk.sh`. Both profiles are
tracked in `dotfiles/zz-blankpad/`; the pointer is not, because which desk the box sits on
is not a fact about the repo. A fresh box therefore starts with no output configuration —
`config.kdl` includes `display.kdl` with `optional=true`, so niri comes up usable but with
default monitors until `desk.sh` is run once.
## Law: generated theme output is never deployed
noctalia rewrites palette output in place — `noctalia.*`, `themes/noctalia*`,
`colors/noctalia*`, `noctalia-*` (the classes the [census](/inventory/home-config-census.md)
proves by same-millisecond mtime). A tracked copy of one is a stale snapshot of a colour
scheme, and the failure is quiet: `dotfiles/zz-blankpad/niri/.config/niri/noctalia.kdl`
(commit `b3d3df4`, 2026-10-18) sat byte-identical to the live file for two days, harmless,
while `--check` cheerfully proposed `ADOPT … noctalia.kdl`. The moment the palette changed,
the next sync would have quarantined the live file and restored 2026-10-18's colours —
a tool silently reverting a user-visible theme choice.
`deploy.sh` now refuses the class (`SKIP … (generated theme output)`), `.gitignore` carries
the matching patterns so it cannot be re-added, and `tests/deploy.bats` pins both
directions: generated output is never adopted, and an ordinary identical config still is.
Untracked in #43, Refs #42.
## Law: a `zz` override older than the file it overrides is a time bomb
Stow order means `zz-$HOST` wins, and when both packages carry the same path, sync relinks
it *silently* — the script's own comment: "managed file changing owners between packages:
silent relink, nothing to quarantine". `dotfiles/zz-blankpad/niri/.config/niri/cfg/layout.kdl`
held focus ring `width 2` / `active-color "#e01b24"` from `7b7dbf5` (2026-10-17) while the
base package absorbed `b3d3df4` (2026-10-18): `width 4`, `"#ff8c00"`. Live was linked to
base, the newer intent, so `--check` printed `RELINK .config/niri/cfg/layout.kdl` on every
run and one unattended `deploy.sh` would have reverted the amber focus ring with no
quarantine and no warning. The duplicate is deleted in #43.
Rule: a `zz` file exists only to differ. When base absorbs the change, delete the `zz` copy
in the same commit — and never read `RELINK` lines from `--check` as cosmetic noise; they
are the sound of a stale override waiting for someone to run the tool for real.
+8
View File
@@ -44,6 +44,14 @@ actions=0; quarantined=0; skipped=0
deploy_file() { # $1 = repo file path, $2 = home-relative target path
local src="$1" tgt="$HOME/$2"
# Generated theme output is never deployment state: noctalia rewrites these files in
# place, so a tracked copy is a stale snapshot and syncing one reverts a live palette to
# the colours of the day it was committed. Classes are those the census measures by
# mtime — see omp-docs/inventory/home-config-census.md, "Generated palette expansion".
case "$2" in
*/themes/noctalia*|*/colors/noctalia*|*/noctalia.*|*/noctalia-*|*/Noctalia*)
echo "SKIP $2 (generated theme output)"; skipped=$((skipped+1)); return 0 ;;
esac
if [ -L "$tgt" ]; then
if [ "$(realpath "$tgt")" = "$(realpath "$src")" ]; then return 0; fi # already deployed
case "$MODE" in
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bats
# tests/deploy.bats — black-box tests for scripts/deploy.sh (Refs #42)
#
# Contract (see script header): the repo checkout IS the deployment; sync adopts
# identicals, links new files, quarantines-and-replaces divergents, and stows the
# zz-$(hostname) package last. One thing it must NEVER do is deploy generated theme
# output: noctalia rewrites `noctalia.*` / `themes/noctalia*` in place, so a tracked
# copy is a stale snapshot, and a sync would revert a live palette to whatever colour
# scheme was current on the day it was committed.
#
# Nothing is observed except exit status, stdout, the file/symlink shape under $HOME,
# and the quarantine directory.
setup() {
TEST_TMP="$(mktemp -d)"
REPO="$TEST_TMP/repo"
DEPLOY="$BATS_TEST_DIRNAME/../scripts/deploy.sh"
mkdir -p "$REPO/scripts" "$REPO/dotfiles"
# A git repo, because the deployment checkout IS one and deploy.sh arms hooksPath.
git init -q -b main "$REPO"
cp "$DEPLOY" "$REPO/scripts/deploy.sh"
chmod +x "$REPO/scripts/deploy.sh"
export HOME="$TEST_TMP/home"
mkdir -p "$HOME"
export DEPLOY_REPO="$REPO" DEPLOY_HOST=box
}
teardown() { rm -rf "$TEST_TMP"; }
# repo_file <pkg-relative path> <content>
repo_file() { mkdir -p "$(dirname "$REPO/dotfiles/$1")"; printf '%s\n' "$2" > "$REPO/dotfiles/$1"; }
# live_file <absolute path under $HOME> <content>
live_file() { mkdir -p "$(dirname "$1")"; printf '%s\n' "$2" > "$1"; }
@test "generated theme output is never adopted into a symlink" {
repo_file "ghostty/.config/ghostty/themes/noctalia" 'background = "#f43841"'
live_file "$HOME/.config/ghostty/themes/noctalia" 'background = "#f43841"'
run "$REPO/scripts/deploy.sh"
[ "$status" -eq 0 ]
[[ "$output" == *"SKIP"*".config/ghostty/themes/noctalia"* ]]
[ -f "$HOME/.config/ghostty/themes/noctalia" ]
[ ! -L "$HOME/.config/ghostty/themes/noctalia" ]
}
@test "a diverged live palette is never quarantined and reverted to a stale copy" {
# The dangerous shape: the tracked file matches today's palette, the theme changes
# tomorrow, and sync would quarantine the live file and restore the stale one.
repo_file "niri/.config/niri/noctalia.kdl" 'active-color "#e01b24"'
live_file "$HOME/.config/niri/noctalia.kdl" 'active-color "#ff8c00"'
run "$REPO/scripts/deploy.sh"
[ "$status" -eq 0 ]
grep -q 'active-color "#ff8c00"' "$HOME/.config/niri/noctalia.kdl"
[ ! -L "$HOME/.config/niri/noctalia.kdl" ]
[ ! -d "$REPO/.quarantine" ]
}
@test "an ordinary identical config is still adopted" {
# Control against an exclusion so broad the tool stops deploying anything.
repo_file "ghostty/.config/ghostty/config.ghostty" 'theme = "gruber"'
live_file "$HOME/.config/ghostty/config.ghostty" 'theme = "gruber"'
run "$REPO/scripts/deploy.sh"
[ "$status" -eq 0 ]
[ -L "$HOME/.config/ghostty/config.ghostty" ]
[ "$(realpath "$HOME/.config/ghostty/config.ghostty")" = "$(realpath "$REPO/dotfiles/ghostty/.config/ghostty/config.ghostty")" ]
}
@test "--check reports and writes nothing" {
repo_file "ghostty/.config/ghostty/config.ghostty" 'theme = "gruber"'
live_file "$HOME/.config/ghostty/config.ghostty" 'theme = "tokyo-night"'
run "$REPO/scripts/deploy.sh" --check
[ "$status" -eq 0 ]
[[ "$output" == *"DRIFT"* || "$output" == *"QUAR"* ]]
[ ! -L "$HOME/.config/ghostty/config.ghostty" ]
grep -q 'theme = "tokyo-night"' "$HOME/.config/ghostty/config.ghostty"
[ ! -d "$REPO/.quarantine" ]
}