fix(pkg-inventory): state the orphan law, test the boundary it rests on, stop --help mid-code

Three things from the review (#60), one of them a finding of my own that did not
survive measurement:

- The reported defect — additions never subtract `pacman -Qtdq`, so an orphan can
  be emitted as a manifest line — is wrong. A package carries one install reason:
  `-Qtdq` selects installed-as-dependency, additions come from `-Qqe`, so the sets
  are disjoint (blankpad: 206 explicit, 3 orphans, none in common). A subtraction
  would be dead code that looks like a guard, so the header now says why not to
  add one. The packages page law was rewritten to the same effect.
- What the old test should have pinned: an implicit dependency installed AFTER
  the baseline. Only `-Qqe` keeps it out of drift — mutate that query to `-Qq` and
  the new test goes red (verified), where the old assertion passed no matter what.
- `--help` printed `set -euo pipefail` and the LC_ALL line as documentation: the
  hardcoded sed range ran past the comment block. It now stops at the first line
  of code, so the header can keep growing.

Refs #60
This commit is contained in:
Frank
2026-10-21 00:54:14 -07:00
parent 671222b881
commit 40b9725433
3 changed files with 60 additions and 19 deletions
+15 -7
View File
@@ -8,7 +8,7 @@ tags:
- provisioning
- fleet
- blankpad
verified: 2026-10-20
verified: 2026-10-21
timestamp: 2026-10-20
---
@@ -48,7 +48,7 @@ undeclared additions (declare these): N ← exit 1 when N > 0
aur (needs a helper): <names> → belongs in packages/aur.pkgs
installer-provided, undeclared: N + names → a reinstall supplies these; do not declare
declared, not installed: <names> → install.sh installs these
orphans ignored: <names> → pacman -Qtdq
orphans ignored: <names> → pacman -Qtdq, never drift (see the law below)
flatpak: <app ids> → reported only, install.sh installs no flatpaks
```
@@ -78,15 +78,23 @@ how a manifest becomes a pile of source builds.
Classify with `pacman -Sl` (world-readable). **`pacman -Sqq` requires root** — a tool that only
reads the machine must not need sudo to answer a question about it.
**Known gap:** `aur.pkgs` is read for *every* host, so a desktop-only AUR package (a niri module, a
GUI client) cannot be declared there without asking the headless servers to build it. Such a name is
still reported as an AUR addition; leave it undeclared until `install.sh` grows a per-host AUR
**Known gap:** `aur.pkgs` is read for *every* host, so a desktop-only AUR package — the niri
module `nirimod-git`, say, or a GUI client — cannot be declared there without asking the
headless servers to build it. Such a name is still reported as an AUR addition and stays
undeclared (see the note in `packages/blankpad.pkgs`) until `install.sh` grows a per-host AUR
manifest.
## Law: orphans never reach a manifest
`pacman -Qtdq` names are packages nothing depends on; emitting them would teach a fresh machine to
install the same dead weight. They are printed as `orphans ignored` and excluded from `--emit`.
Drift is `pacman -Qqe` — *explicitly* installed, therefore yours. `pacman -Qtdq` names packages
installed **as a dependency** that nothing requires any more, and a package carries exactly one
install reason, so the two sets cannot overlap (blankpad 2026-10-21: 206 explicit, 3 orphans, none
in common). That is why nothing in `pkg-inventory.sh` subtracts the orphan list from the additions:
the query is already the guard. Do not add such a subtraction — it cannot fire, and it would bury
the invariant that matters, *an implicit install is never drift no matter when it arrived*, which
`tests/pkg-inventory.bats` pins with a dependency installed after the baseline. What the
`orphans ignored` line is for is the other half: dead weight on the machine that a manifest must
never teach a fresh box to install.
## Two traps that produced this tool
+13 -6
View File
@@ -19,11 +19,19 @@
# aur (needs a helper): <names> → belongs in packages/aur.pkgs (yay)
# installer-provided, undeclared: N a reinstall supplies these; do not declare
# declared, not installed: N install.sh installs these
# orphans ignored: <names> pacman -Qtdq — never emitted
# orphans ignored: <names> pacman -Qtdq — never drift, never emitted
# flatpak: <app ids> reported only, install.sh installs no flatpaks
#
# --emit prints the additions as manifest lines, ready to fold into packages/. Names are bare
# tokens so install.sh's `sed 's/#.*//'` + first-field parser reads them unchanged.
# Drift is `pacman -Qqe`: explicitly installed, therefore something you asked for and a rebuild
# must declare. `pacman -Qtdq` orphans are packages installed AS A DEPENDENCY and no longer
# required, and a package carries exactly one install reason, so the two sets are disjoint by
# construction (blankpad, 2026-10-21: 206 explicit, 3 orphans, none in common). Nothing here
# subtracts the orphan list from the additions and nothing should — the query is the guard. Do not
# add such a subtraction: it cannot fire, and it would hide the real invariant (implicit installs
# are never drift, whenever they arrived) behind a line that looks like protection.
#
# Exit status: 0 clean · 1 drift (undeclared additions exist) · 2 usage · 3 cannot classify
# (pacman log unreadable or sync databases unavailable — worse to guess than to say so, because
@@ -42,23 +50,22 @@ MODE="report"
while [[ $# -gt 0 ]]; do
case "$1" in
--emit) MODE="emit"; shift ;;
-h | --help) sed -n '2,40p' "$0"; exit 0 ;;
-h | --help) awk '/^set /{exit} NR>1' "$0"; exit 0 ;;
*) echo "usage: pkg-inventory.sh [--emit]" >&2; exit 2 ;;
esac
done
HERE="$(dirname "$(realpath "$0")")"
REPO="${PKGINV_REPO:-}"
if [[ -z "$REPO" ]]; then
REPO="$(git -C "$(dirname "$(realpath "$0")")" rev-parse --show-toplevel 2>/dev/null || true)"
REPO="$(git -C "$HERE" rev-parse --show-toplevel 2>/dev/null || true)"
fi
[[ -n "$REPO" && -d "$REPO/packages" ]] || { echo "pkg-inventory: no packages/ under ${REPO:-<repo>}" >&2; exit 2; }
HOST="${PKGINV_HOST:-$(hostname)}"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
read_manifest() { # same parser install.sh uses, so a name means the same thing to both
sed 's/#.*//' "$1" | tr -d '\r' | awk '{if ($1) print $1}'
}
source "$HERE/lib/manifest.sh" # the same parser install.sh installs with
# ---- installed state ---------------------------------------------------------
# Four queries are the tool's whole interface to pacman. -Sl is the unprivileged sync listing:
# -Sqq asks for root, and a read of the machine must not need sudo. Field 2 of -Sl is the package
+32 -6
View File
@@ -39,15 +39,18 @@ EOF
[2026-03-04T09:00:00-0800] [ALPM] installed bun (1.2-1)
[2026-03-04T09:30:00-0800] [ALPM] upgraded filesystem (2026.01.01-1 -> 2026.03.01-1)
[2026-03-05T09:00:00-0800] [ALPM] installed nirimod-git (r7-1)
[2026-03-07T09:00:00-0800] [ALPM] installed libstray (1.0-1)
[2026-03-06T09:00:00-0800] [ALPM] removed base (3-1)
EOF
# --- installed state --------------------------------------------------------
# ghostty/zoxide/neovim/typora declared. libfoo: explicit, arrived with the installer.
# bun/nirimod-git: installed by hand afterwards, undeclared. python-hatchling: orphan.
printf '%s\n' base bun filesystem ghostty libfoo neovim nirimod-git typora zoxide > "$TEST_TMP/local-all"
# bun/nirimod-git: installed by hand afterwards, undeclared. python-hatchling and libstray:
# installed as dependencies and now unrequired (pacman -Qtdq). libstray arrived AFTER the
# baseline, so the date filter alone cannot keep it out of drift — only the explicit query can.
printf '%s\n' base bun filesystem ghostty libfoo libstray neovim nirimod-git typora zoxide > "$TEST_TMP/local-all"
printf '%s\n' bun ghostty libfoo neovim nirimod-git typora zoxide > "$TEST_TMP/local-explicit"
printf '%s\n' python-hatchling > "$TEST_TMP/orphans"
printf '%s\n' python-hatchling libstray > "$TEST_TMP/orphans"
# nirimod-git is deliberately ABSENT here: no sync db can serve it, which is the fact that
# routes it to aur.pkgs in the test below.
printf '%s\n' bun ghostty neovim typora zoxide libfoo vlc python-hatchling base filesystem > "$TEST_TMP/sync-all"
@@ -131,11 +134,34 @@ ADDITIONS() { section "^undeclared additions" "^installer-provided"; }
[[ "$(section "^declared," "^orphans")" == *vlc* ]]
}
@test "orphans are reported as ignored and never emitted" {
@test "orphans are named on their own line, not mixed into the drift answer" {
run "$SCRIPT"
[[ "$output" == *orphans*python-hatchling* ]]
[[ "$(section "^orphans" "^flatpak")" == *python-hatchling* ]]
[[ "$(ADDITIONS)" != *python-hatchling* ]]
}
@test "a dependency installed after the baseline is never drift, orphan or not" {
# Drift means `pacman -Qqe` — you asked for this one. libstray arrived as a dependency
# after install day and is now unrequired, so the baseline filter does not exclude it:
# only the explicit query does. Swap -Qqe for -Qq and every dependency on the machine,
# dead ones included, becomes a manifest line install.sh will reinstall on a rebuild.
run "$SCRIPT"
[[ "$(ADDITIONS)" != *libstray* ]]
[[ "$output" == *"undeclared additions (declare these): 2"* ]]
[[ "$(section "^orphans" "^flatpak")" == *libstray* ]]
run "$SCRIPT" --emit
[[ "$output" != *python-hatchling* ]]
[[ "$output" != *libstray* ]]
}
@test "--help prints the header and stops before the shell" {
# The header IS the contract (report shape, exit status, seams). A hardcoded line range
# that runs past the comment block hands the reader `set -euo pipefail` as if it were
# documentation.
run "$SCRIPT" --help
[ "$status" -eq 0 ]
[[ "$output" == *"Exit status"* ]]
[[ "$output" != *"pipefail"* ]]
[[ "$output" != *"LC_ALL"* ]]
}
@test "--baseline moves packages installed on that date into the installer set" {