--- name: dotfiles-adoption-audit description: "Use when deciding which userspace configs on a Dank box belong in the command-center dotfiles tier — separates tracked intent from generated expansion, app state, secrets, and config owned by another repo. Not for deploying one known file." --- # Dotfiles adoption audit Decides, with evidence, which files under `~/.config` (and top-level dotfiles) belong in `dank/command-center`'s `dotfiles/` tier. The repo law being applied: **config in, state out**, and **one fact, one owner**. ## The four gates (all four must pass) 1. **Installed?** `command -v ` — config for software absent from the box is a prune candidate, never an adoption target. This eliminates most of the clutter. 2. **Authored, or the expansion of something else?** Generated files must stay out even though they look like config. 3. **Does another repo or installer already write it?** If a script or package owns the file, tracking it here creates a second owner that loses every argument. 4. **Could a secret ever live in it?** If yes: template + quarantine, or leave it out. ## Gate 2 is the one people get wrong — prove generation from mtime A generator walking the app list writes dozens of files in the same sub-second window. Compare timestamps across unrelated programs: ```bash cd ~/.config && stat -c '%y %n' */themes/noctalia* */noctalia.* */colors/noctalia* 2>/dev/null | sort ``` Identical seconds + microsecond progression across ten different programs = generated. Re-check one file days later: a new mtime with no human edit proves regeneration, so the tracked file must be the *source* (palette/config the generator reads), never its output. The correct pattern is a tracked config that *imports* the generated path. ## Classify the whole tree per file, not per directory The tracked unit is the **file symlink**, so a directory can be "real" while containing only tracked symlinks. Never classify by directory: ```bash cd ~/.config && for d in */; do d=${d%/}; tr=0; rl=0; rlfiles="" while IFS= read -r f; do case "$(readlink -f "$f" 2>/dev/null)" in */.omp/dotfiles/*) tr=$((tr+1));; *) rl=$((rl+1)); rlfiles="$rlfiles ${f#./}";; esac done < <(find "$d" -maxdepth 2 \( -type f -o -type l \) 2>/dev/null \ | grep -vE 'Cache|cache|Cookies|storage|sessions|Crashpad|Default|thumbnails|extensions') [ $((tr+rl)) -eq 0 ] && continue printf '%-22s tracked=%-3s box-only=%-3s %s\n' "$d" "$tr" "$rl" "$(echo $rlfiles | cut -c1-90)" done ``` Then read the headers of suspicious files — installers often confess ("Installed by `infra/setup-dev.sh` into `~/.config/containers/systemd/`") and unit files name another project's `WorkingDirectory`/`ExecStart`, which means that project owns them. ## Ownership before adoption ```bash git -C ~/.omp ls-files dotfiles # packages deploy.sh sees grep -rl "" ~/Projects/ # does a project repo own it? systemctl --user is-active # dead config is not load-bearing ``` ## Secret-gap check (do this before adopting anything env-shaped) The ignore list may cover only `.env`, so `frank-job-hunt.env` / `postgres.env` fall through, and a token-shape secret gate (`sk-`, `ghp_`, PEM, JWT) does not match a low-entropy `POSTGRES_PASSWORD=…`. Test both explicitly: ```bash cd ~/.omp && git check-ignore -v --no-index 'dotfiles/x/postgres.env' || echo "NOT IGNORED" sed -n 's/^patterns=//p' .githooks/pre-commit ``` Fix = add `*.env` to the quarantine block **and** a password-assignment pattern to the gate, with a red test that stages a fake `POSTGRES_PASSWORD=` file. ## Land it Packages are home-relative trees under `dotfiles//`; per-host overrides go in `zz-$(hostname)` (stowed last). `dconf` is loaded via `dconf load /`, `systemd-user` gets `daemon-reload` + conditional `enable --now`. Sequence: `scripts/deploy.sh --check` (dry run, reports divergents) → adopt → `deploy.sh` → confirm live paths are symlinks into the repo → `bats tests/` (includes the real-bundle `kb-lint`/coverage gate) → branch off `origin/main`, PR to `main`, never merge without Frank's OK.