Skip to content

cli/manifest/inventory: tt package list and tt package uninstall - #1334

Open
bigbes wants to merge 3 commits into
v3from
bigbes/tntp-8614-list-uninstall
Open

cli/manifest/inventory: tt package list and tt package uninstall#1334
bigbes wants to merge 3 commits into
v3from
bigbes/tntp-8614-list-uninstall

Conversation

@bigbes

@bigbes bigbes commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Install writes the per-package metadata under manifests/<pkg>/, and list and uninstall read it back. The three have to agree on the layout down to the directory name, so keeping the scope resolution, the metadata file names and the installed-package reader inside the install package would mean the inventory commands either duplicate them or import a package named after the one operation they do not perform. They move into cli/manifest/state, and the two new commands are built on top of it.

  • cli/manifest/state: extract the on-disk install state
  • cli/manifest/inventory: tt package list and tt package uninstall

Closes TNTP-8614

bigbes added 3 commits July 23, 2026 18:36
Unpack a .tt package archive into a scope and bring the tree to a
runnable state. A with-deps archive in the project scope is a plain
offline extraction (tar xf, no network); a --without-deps archive
additionally refetches its dependency closure from the registry by the
lock's exact pins, reusing tt package fetch's install-with-DepsNone
loop. user and system scopes accept only --without-deps archives — a
with-deps archive there is refused from the archive header before
anything touches disk.

Joint resolution handles several packages sharing one project .rocks/
tree: a dependency they lock at different versions is reconciled to the
highest locked version that satisfies every package's declared
constraint, or the install fails with a breakdown of who pinned what.
The choice is made only among versions already locked — no registry is
consulted — over go-luarocks' version and constraint primitives.

Per-package metadata (manifest, lock, VERSION) is recorded under
.rocks/manifests/<pkg>/ for inventory and dependency refcounting.
--locked refuses an archive whose lock diverges from its manifest,
--upgrade installs only a strictly higher version, --force reinstalls
over a collision, --yes skips the reconciliation prompt. A single
archive surfaces its own failure code; a multi-archive run where some
succeed and some fail exits 3.

The archive reader is new: pack shipped a write-only tar+zstd writer, so
install carries its own reader, with a tar-slip guard on every entry.

Closes TNTP-8613
Install writes the per-package metadata under manifests/<pkg>/, and
list/uninstall read it back. The three have to agree on the layout down
to the directory name, so keeping the scope resolution, the metadata
file names and the installed-package reader inside the install package
would mean the inventory commands either duplicate them or import a
package named after the one operation they do not perform.

Move them into cli/manifest/state: Scope and ResolveLayout, the Package
reader over manifests/ plus the primary package, WriteMetadata and its
new inverse RemoveMetadata, and the rock path helpers install already
used to drop a stale version. install keeps Scope as an alias so the CLI
wiring is unchanged, and wraps the state errors so a bad --scope still
exits 1.

The ownership ledger uninstall will stand on is derived, not stored: a
dependency is owned by every installed package whose lock pins it, so
LockedDependencies over what is left under manifests/ is the whole
count. Nothing has to be kept in sync by hand.

Also read the primary package's VERSION file, which install had no use
for but list does, and note in .golangci.yml that depguard matches the
longest prefix — adding a sub-package to an allow list shadows the
broader entry for its siblings.

Part of TNTP-8614
Both commands work over what an install left on disk and nothing else:
the source of truth is the metadata under .rocks/manifests/<pkg>/, so
neither contacts a registry, runs a build, nor re-resolves anything.

list reports the packages present in a scope — in project the project's
own package plus every guest. -o picks the format, defaulting to a table
on a terminal and YAML otherwise, so piped output is parseable without
the caller remembering a flag. What it reports is what is on disk,
deliberately not what a manifest declares.

--tree renders the scope as one tree rooted at the project's own package.
Guests hang off it because they are installed into its .rocks/ and share
it, marked (guest) rather than presented as things the project requires.
Each rock sits under whatever pulled it in: a lock stores the dependency
closure flattened, with the parent discarded, but LuaRocks records every
installed rock's own requirements in the tree manifest, so the edges
survive there. Rocks the walk cannot root — a tree carrying no manifest,
or incomplete edges — are still listed, under a group that says the
parent is not recorded rather than implying the package asked for them.

uninstall removes a guest and the shared dependencies it was the last to
hold. Ownership is derived rather than counted: a dependency belongs to
every installed package whose lock pins it, so setting the target aside
and re-reading the remaining locks is the whole ledger, with no separate
counter to drift out of sync. Two consequences are worth naming.
Ownership covers the closure and not just declarations, so a rock no
manifest ever named still goes with its last owner. And a guest that
another package merely depends on is never swept away as a shared rock —
it has its own metadata, so only an uninstall naming it removes it.

Removal order is files first, metadata last, so an interrupted run can be
repeated: the package still reads as installed until its files are gone.
The package name is validated before anything is removed, since it
becomes a path.

Every rock in --tree is annotated with what an uninstall would do to it,
and a test asserts that prediction against a real removal, so the
annotation cannot drift from the refcounting it describes.

The machine output keys the rock index as "rocks" rather than
"dependencies": a package entry already carries a "dependencies" object,
and one name must not stand for two shapes.

Tested at both levels: Go tests over fixture trees for the ownership
rules, the refusals, the graph walk and the rendering, and a pytest slice
driving the real binary for the CLI surface. The system scope installs
into /usr, so its slice runs in a container against a disposable tree.

Closes TNTP-8614
Base automatically changed from bigbes/tntp-8613-tt-package-install to v3 July 28, 2026 12:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant