feat(packages): installed-packages fact (source-namespaced record arrays)#29
Merged
Conversation
Adds the `packages` fact (ADR-0014): packages.<source> = an array of
{name, version, ...} records, one namespace per package database. Sources are
never merged, a source is omitted when its database is absent, and the whole
probe is a gated single-output resolver so `--disable packages` skips it.
Readers (one cheap read/query each, no spawn-per-package, no network):
- Linux: dpkg (install/hold state), rpm (epoch query, gpg-pubkey filtered,
DB-presence-gated), pacman, apk, snap, flatpak, nix (NixOS system profile).
- BSD/illumos: pkg (FreeBSD+DragonFly, absolute /usr/local/sbin/pkg),
openbsd_pkg, pkgsrc (PKG_DBDIR discovery), ips.
- macOS: receipts (primary), apps (secondary, never merged; plutil -p batch),
homebrew (auto-detected).
- Windows: registry (both HKLM hives, product_code+architecture), appx.
Per-platform readers live in packages_{bsd,mac,win,extra}.go with no GOOS
suffixes or build constraints (ADR-0010), so the pure parse functions stay
cross-platform testable. Commands outside the engine's trusted PATH use
absolute paths (pkgng, nix-store).
Schema, supported-facts pages, README, man page, and CHANGELOG updated.
Validated on the nlab fleet / local darwin, counts vs native tools: dpkg 324,
rpm 386, pacman 183, apk 200, pkg freebsd 2 / dragonfly 48, openbsd_pkg 18,
pkgsrc 19, ips 418, nix 70, receipts 10 / apps 61 / homebrew 88 — all exact.
Windows registry/appx are format- and empty-case-validated (bare guest);
snap/flatpak are format-only (no populated guest).
Reviewed with OCR + Codex + self-adversarial; all findings fixed (dpkg held
packages, homebrew cask-only, apps/registry empty-version invariants, plutil
multi-line value bug, nix digit-leading names, plutil argv chunking, total-order
sort, resolver-gating test).
The macOS and BSD readers build always-unix paths (/Applications, /var/db/pkg) but used path/filepath, whose separator is OS-specific. Because these pure parse functions are tested on every platform (ADR-0010, no GOOS suffix), the Windows CI runner produced backslash paths and failed TestAppsPackages / TestOpenbsdPackages. Switch to the path package (always '/'). Validated on the nlab Windows guest: the packages tests now PASS.
…idated Populated the nlab ubuntu2404 guest (snap hello-world; flatpak org.vim.Vim from flathub) and validated both readers end-to-end. Real data showed the deferred flatpak `branch` field is load-bearing, not decorative: the same application id (org.freedesktop.Platform.GL.default) installs twice with an identical version and arch, distinguishable only by branch (25.08 vs 25.08-extra) — without it the reader emitted two indistinguishable duplicate records. The reader now requests --columns=application,version,arch,branch and emits branch as an identity field; versionless extensions (codecs-extra) stay dropped by the name+version invariant. Guest validation: facts.snap = 3 = snap list; facts.flatpak = 4 = versioned flatpak rows; dpkg (740) coexists on the same host. Fixtures replaced with verbatim guest output.
…rouping Deep multi-lens review of the changeset surfaced 9 code defects and 4 doc drifts (each independently triaged against the code and design docs): - dpkg: keep trigger-state packages (install ok triggers-awaited/pending) — fully unpacked and configured; drop half-installed. Held already kept. - groups: add the Facts-native "packages" fact group (ADR-0014's deliberate parity divergence) so --list-block-groups names it as one disable unit. - pkgsrc: honor ADR-0014's "illumos/DragonFly secondary" — wired into both dispatches with the SmartOS dbdir candidates (/opt/local/pkgdb, /opt/local/pkg); inert on hosts without a pkgsrc db (omnios ips 418 and dragonfly pkg 48 unchanged, pkgsrc omitted). - nix: honor ADR-0014's "default profile AND NixOS system profile" — fall back to nix-env -q --profile /nix/var/nix/profiles/default on non-NixOS hosts (validated against a real daemon install on ubuntu2404: 3 records incl. the nix-manual-2.34.7-man output-suffix case); gate opens on either profile; also wired on darwin per the design. NixOS still prefers the system profile without a fallback spawn. - apps: scan the Utilities subfolders and match only *.app bundles — live darwin count 61 -> 80, exactly matching ls. - plutil pipeline: bisect failed chunks so one corrupt plist no longer drops the whole receipts/apps source (O(log n) recovery, pairing per-invocation by construction); track multi-line string values so brace-looking lines inside them cannot desync block boundaries; handle array-rooted plists so pairing survives them. - homebrew: add the prefix identity field — dual-prefix installs (/opt/homebrew + Rosetta /usr/local) previously yielded byte-identical duplicate records. - schema: correct six descriptions to match behavior (dpkg states, appx provisioned+collector union, flatpak system+collector-user, openbsd_pkg architecture, homebrew prefix, nix profiles) and pkgsrc platforms. ubuntu2404 now validates four coexisting sources end-to-end: dpkg 740, snap 3, flatpak 4, nix 3.
…verified) Windows (validated live on the lab guest before and after): - Map Get-AppxProvisionedPackage's raw DISM UInt32 architecture (0/5/9/11/12) to x86/arm/x64/neutral/arm64 — provisioned lines rendered numbers, producing wrong arch values and defeating cross-view dedup on every populated host. - Force [Console]::OutputEncoding=UTF8 in both scripts: redirected PowerShell stdout used the OEM codepage (CP437 turned o-umlaut into invalid UTF-8 and best-fit-mapped (R) to "r"), corrupting every non-ASCII DisplayName. - Terminate both scripts with ;exit 0: an error in the final statement exits 1 even under SilentlyContinue, and run() then discards all valid output (bare registry source on 32-bit Windows; lost provisioned lines under SYSTEM). - Derive the native-hive architecture from PROCESSOR_ARCHITECTURE instead of hardcoding x64 (wrong for the whole native hive on Windows-on-ARM). - Switch the registry delimiter to the unit separator so a '|' in free-text DisplayVersion/subkey values cannot shift the record columns. nix: - Collapse custom-output store paths (bind's dnsutils/host) onto their base record; a digitless version tail with no base sibling stays verbatim, so genuine 1.0-beta versions are never touched. - Read the system set through the default profile's nix-store when nix.enable=false (Determinate) keeps nix-store out of the system env. YAML formatter: - Quote plain scalars a YAML 1.1 resolver retypes — verified against Ruby 3.4 Psych: "43_1" parsed as Integer 431, "0x1F"/"0755" as hex/octal ints, and "2026-05-14" as Date (raising under safe_load). Package versions exercise every shape (homebrew revision/date versions, apps "36"); the whole packages tree now round-trips through Psych as strings. plutil pipeline: - Scalar-rooted plists emit an empty block so positional pairing advances. - An invocation is committed only when its block count matches its path count; any mismatch (parser desync from content plutil prints ambiguously) is distrusted and bisected, containing damage to the offending file. CI hygiene: the per-category gating test disables packages alongside each other category, keeping ~9 redundant full package probes out of every run.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the
packagesfact (ADR-0014): the installed packages on the host, namespaced by the package database they come from.packages.<source>is an array of{name, version, ...}records with per-source identity fields. Sources are never merged, a source is omitted when its database is absent, and the whole probe is a gated single-output resolver —--disable packagesskips collection, andpackagesis a named fact group in--list-block-groups(a deliberate Facts-native parity divergence).Sources (one cheap read/query each — no spawn-per-package, no network)
dpkg,rpm,pacman,apk,snap,flatpak,nixpkg(+pkgsrcsecondary on DragonFly)openbsd_pkg/pkgsrc/ips(+pkgsrcSmartOS secondary)receipts(primary),apps(secondary, never merged),homebrew,nixregistry(both HKLM hives),appxReaders live in
packages_{bsd,mac,win,extra}.gowith no GOOS suffixes or build constraints (ADR-0010); tools outside the engine's trusted PATH use absolute paths.Validation — every source against its real database
branchidentity proven by live same-version siblingsprefix)Plan 9 emits nothing. Remaining gap:
appxis format-validated with live-verified DISM numeric shapes + unit-tested (populating it needs a signed MSIX — disproportionate for a secondary source).Deep adversarial review (two multi-agent rounds, 10 lenses, per-finding verification)
Round 1 — 13 findings fixed: dpkg trigger-state packages kept;
pkgsrcillumos/DragonFly secondaries andnixdefault-profile support (ADR compliance); apps Utilities globs (61→80 live); plutil corrupt-chunk bisection, multiline-string tracking, array roots; homebrewprefixidentity (dual-brew duplicates);packagesfact group; 6 schema description drifts.Round 2 — 11 confirmed findings fixed (Windows ones verified live on the guest):
[Console]::OutputEncoding=UTF8.;exit 0.PROCESSOR_ARCHITECTUREinstead of hardcoded x64.|in free-text values.bind-…-dnsutils) collapse onto their base record; Determinate Nix (nix.enable=false) system set read via the default profile's nix-store.43_1→Integer 431,0755→493,2026-05-14→Date (which raises undersafe_load); verified against Ruby Psych, full round-trip now string-clean.Also fixed along the way: dpkg held packages, homebrew cask-only, apps/registry empty-version invariants, a plutil multi-line-value bug, nix digit-leading names, plutil ARG_MAX chunking, total-order record sort, resolver-gating test, and a
path-vs-filepathWindows CI catch.Deferred (documented): homebrew
tap, nixstore_path.Implements OpenSpec change
add-packages-fact.