Design Goal
The main purpose of this format is to let inventory tools reliably remind system administrators about packages they explicitly installed.
Current package-manager databases and logs are not enough for this. Some package managers do not retain useful transaction history; some rotate or delete logs; some expose install timestamps but not a durable selection class such as "this package was requested by an administrator" versus "this package was pulled in as a dependency". The transaction log described here is intended to be small, append-only, durable, and implementable by all package managers.
Terminology
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT,
SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document
are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when,
they appear in all capitals.
Core concepts
The important distinctions are:
-
base: the package was seeded by an operating-system installer, image builder, package-manager bootstrap, global profile, or comparable initial system state. -
manual: the package was explicitly named by an administrator, manifest, profile, or global-install command. -
auto: the package was installed because the solver needed it to satisfy some other request.
An inventory program can then answer this question directly: which currently installed packages did an administrator explicitly request, excluding base packages unless they were later explicitly manual?
Everything else in this specification is in support of that operation.
Storage
The log MUST be JSON Lines: UTF-8 text, one complete JSON object per line. This keeps the format append-friendly, streamable, recoverable after partial writes, and easy to inspect with ordinary tools.
A package manager may keep one or more journals under persistent state. System-scope journals MUST live at:
/var/lib/pkg-transactions/*.jsonl
Recommended user-scope journals SHOULD live at:
${XDG_STATE_HOME:-~/.local/state}/pkg-transactions/*.jsonl
The directory is deliberately under persistent state, not an ordinary log
directory such as /var/log, because these records are package database
provenance, not disposable diagnostics.
Best practice is to have only a single file representing the entire timeline of transactions recorded by possibly multiple package managers. In that case package managers MUST write-lock appends for atomicity. The preferred name for the single timeline is "journal.jsonl".
When permissions issues make a shared journal impossible, package managers MAY create multiple journal files names in any convenient way. Filenames SHOULD NOT be semantically loaded; records should remain self-describing when journals are concatenated, exported, moved, or analyzed outside their original path.
Record Shape
Each package-state line MUST describe a committed package-manager state change. Managers MUST NOT write records for planned actions that did not change package state, failed package operations that left package state unchanged, or ordinary diagnostic events. If a transaction partially succeeds, the manager MUST write records only for packages whose state actually changed. If a rollback changes package state, the manager MUST write records describing the resulting state changes, not a failure event.
Each package-state line should be independently useful even if transaction begin or end records are missing. This section specifies package-state records; adding other record types later would be a backward-incompatible shape change requiring a new required field.
Example package record:
{
"time": "2026-05-18T15:04:05Z",
"txid": "01JVR6K2ZCFAT3RZ3Z2W0V6S1G",
"scope": "apt:/",
"operation": "install",
"selection": "manual",
"reason": "manual"
"package": {
"name": "vim",
"version": "2:9.1.001-1",
"id": "vim:amd64",
"repository": "debian-stable"
},
}
Compatibility And Evolution
There is no explicit schema-version field. Consumers recognize records by their shape: the required fields present, the types of values needed to interpret them, and the required-field values they understand.
Consumers MUST ignore records whose shapes they do not recognize. This includes records with missing required fields, unexpected value types, or required-field values the consumer does not understand. Consumers MUST NOT reject a journal merely because it contains unknown record shapes or extra fields. Consumers SHOULD ignore unknown optional fields in otherwise recognized records.
Each backward-incompatible change MUST be marked by either adding a new required
field or introducing a new value for an existing required field, such as
operation, selection, or reason. Adding a required record field to
distinguish package records from other record types would be one example of such
a shape change. This makes old consumers skip records they cannot safely
interpret rather than misreading them.
Required Fields
time-
UTC RFC 3339 timestamp, for example
2026-05-18T15:04:05Z. scope-
String identifying the package namespace and installation universe being modified. It MUST distinguish package namespaces that might otherwise collide in a shared journal. Consumers MUST compare scope strings byte-for-byte and SHOULD NOT infer semantics from them except for display or grouping. Examples include
apt:/,pacman:/,flatpak:system,flatpak:user,homebrew:/opt/homebrew,nix:profile:/nix/var/nix/profiles/system, andnpm:/usr/local. operation-
Committed operation that affected the package. Common values are
bootstrap,install,remove,purge,upgrade,downgrade,reinstall,mark,autoremove,refresh, andgc. package-
Object identifying the package. It MUST include
nameand SHOULD includeidwhen the package manager can provide a stable native package identity. Consumers SHOULD usepackage.idas the replay identity within the scope when present, falling back topackage.namewhen it is absent. If architecture, ABI, flavor, or another native qualifier is needed to distinguish co-installable packages, the manager SHOULD include that qualifier inpackage.id. selection-
String containing the current selection class after the operation. The value MUST be one of the values in the selection vocabulary below.
reason-
Shared reason code explaining why this package was touched.
Optional fields
txid-
Transaction identifier shared by all records produced by one package-manager operation. Not required in a singleton record.
held-
Boolean indicating that the package is under a persistent package-manager policy that prevents ordinary solver actions from upgrading, removing, or replacing it. This field is independent of
selection. It should be omitted when the package is not held, when the manager has no equivalent concept, or when hold state is unknown. package.repository-
Optional manager-defined repository, channel, tap, registry, or comparable source label. This field is descriptive package metadata. Consumers MUST NOT use it to identify a package unless the package manager also includes it in
package.id.
Selection Vocabulary
The selection field describes current install intent after the operation. It
is a state classification, not the reason this transaction touched the package.
manual-
The package is installed and was explicitly selected by an administrator, manifest, profile, or global-install command.
auto-
The package is installed because the package manager selected it to satisfy some other package or profile.
base-
The package is installed as part of the base system, initial image, package-manager bootstrap, or first profile, and has not later been explicitly selected.
absent-
The package is not installed after this operation. This is normally used in
remove,purge,autoremove, andgcrecords. unknown-
The package is installed, but the manager cannot classify it as
manual,auto, orbase.
Reason Vocabulary
The reason field should use a small shared vocabulary:
manual-
The package was explicitly named by an administrator, manifest, profile, or global-install command.
dependency-
The package was pulled in by dependency solving.
weak-dependency-
The package was installed as a recommendation, supplement, optional runtime, suggested package, or equivalent weak dependency.
build-dependency-
The package was installed in order to build another package.
base-
The package was seeded by an OS installer, image builder, package-manager bootstrap, system profile, or equivalent initial state.
replacement-
The package was installed as part of a rename, transition, obsoletion, or replacement.
cleanup-
The package was removed because it was orphaned, obsolete, garbage-collected, or autoremoved.
unknown-
The manager cannot classify the reason.
Base-System Handling
The format needs a first-class baseline transaction. During OS installation,
image creation, package-manager bootstrap, or first profile creation, the
manager SHOULD emit package records with operation set to bootstrap,
reason set to base, and selection set to base.
Example baseline package record:
{
"time": "2026-05-18T00:00:00Z",
"txid": "baseline-20260518",
"scope": "pacman:/",
"operation": "bootstrap",
"selection": "base",
"reason": "base"
"package": {
"name": "filesystem",
"version": "2025.05-1"
},
}
If an administrator later explicitly requests a base package, the manager
SHOULD emit a mark record or a no-op request record with
selection set to manual. This allows inventory tools to include the
package as an intentional administrator selection even though it originated in
the base system.
Dependency Example
The first record below is an explicit administrator request. The second is a dependency installed to satisfy that request.
{
"time": "2026-05-18T15:04:05Z",
"txid": "tx123",
"scope": "apt:/",
"operation": "install",
"package": {
"name": "vim",
"version": "2:9.1"
},
"selection": "manual",
"reason": "manual"
}
{
"time": "2026-05-18T15:04:06Z",
"txid": "tx123",
"scope": "apt:/",
"operation": "install",
"selection": "auto",
"reason": "dependency"
"package": {
"name": "vim-runtime",
"version": "2:9.1"
},
}
Replay Rules For Inventory
An inventory tool should replay records independently for each tuple of scope
and package identity.
-
Consume only recognized package-state records. The presence of a recognized record means the described state change was committed.
-
Treat
install,bootstrap,upgrade,downgrade,reinstall,refresh, andmarkas operations that update current package state. -
Treat
remove,purge,autoremove, andgcas operations that mark the package not currently installed. -
Consider a package an administrator reminder candidate if it is currently installed and
selectionismanual. -
Hide a package by default if it is currently installed and
selectionisbase. -
Treat a package as dependency noise if it is currently installed and
selectionisauto.
These rules intentionally make the current selection value authoritative. A
package can move from auto to manual, manual to auto, base to
manual, or manual to removed without requiring consumers to infer intent
from old command lines.
Package-Manager Mappings
apt-
Explicit command targets are
manual. Solver-installed dependencies areautowith reasondependency. Debian installer manifests or comparable bootstrap state arebase.apt-mark manualandapt-mark autoshould emitmarkrecords. pacman-
Explicit install targets are
manual. Packages installed with dependency semantics areauto.--asexplicitand--asdepsshould emitmarkrecords. Bootstrap packages or the installedbaseclosure can be logged asbase. dnf,zypper, andrpm-
High-level solvers should log manual packages and dependency reasons from their transaction plans. Low-level
rpm -ican log packages asmanualorunknownif no solver context exists. apk-
Entries explicitly added to the world file are
manual. Solver-installed dependencies areauto. Initial world or bootstrap packages arebase. emerge-
The world set is
manual. Packages installed only by dependency solving areauto. The system set isbase. xbpsandpkg-
Use native auto/manual flags where available. Package-manager bootstrap or initial image packages should be
base. homebrew-
Map
installed_on_requesttomanualandinstalled_as_dependencytoauto. Prefix creation or managed bootstrap packages may bebase. flatpakandsnap-
Application installs are usually
manual. Runtimes, bases, content snaps, and similar support packages should usually have selectionautoorbase, with reasondependencyorbaseas appropriate. pipxandcargo-
Top-level globally installed tools are normally
manual. gem,npm, andyarn-
Globally manual top-level packages are
manual. Visible installed dependencies should beautowith reasondependency. nix-
Profile elements are
manual. Store closure paths are dependencies if they are logged at all. System profile or image entries may bebase.