Skip to content
docs
Arxo ↗

Dependencies, locks, and registries

For LLMs8 sections

Dependencies in the corpus resolve to exact pins, never to ranges. Three commands manage the closure, a fourth previews drift, and the lockfile remembers the outcome. All of them are available in the public release (S).

Environment: the public law-v0.1.1 release, run from the root of the lab bundle; the example needs the throwaway registry built in the lab’s versions exercise. Status letters follow the legend on the topic index.

The lab’s parcels-registry package imports the shared vocabulary labparcels.iface. The wiring replay works on a scratch copy of that package with its [dependencies] section and its law.lock removed, and then pins the vocabulary again from the lab registry:

Terminal
law add 'labparcels.iface@0.1.0' --project /tmp/lab-work/wire/reg --registry lab=/tmp/lab-work/registry --from lab

The law add part of the recorded output (excerpt: the first six lines; the full block is under Reproduction details):

Output
lab ← /tmp/lab-work/registry (--registry)
exports (7): Owner, Parcel, ParcelKind, commercial, garden, owns, residential
changed: .law/transport.json
changed: deps/labparcels.iface.lawir.json
changed: law.lock
changed: law.toml

Line by line: the first line names the registry address the command read. The second lists what the pinned package exports to the importer. The changed: lines name every file the command wrote:

  • law.toml gains the requested pin under [dependencies]: "labparcels.iface" = "0.1.0". This is the request: one exact version, no range.
  • law.lock is written from scratch. It records the resolved package (name, version, namespace, registry id lab, the resolver address registry://lab/labparcels.iface/0.1.0, and the content hash of its compiled bytes), the dependency edge from labparcels.registry to labparcels.iface with the requested and the resolved version, the semantics line law.core/0.2.0, the root package’s own content hash, and one resolution hash over all of it.
  • deps/labparcels.iface.lawir.json holds the compiled bytes that the lock’s content hash covers.
  • .law/transport.json is local service state of the command. It ships with no fixture.

The shipped fixture shows the same end state; read fixtures/parcels-registry/law.lock next to the list above.

What this result means: the package now has one exact, hash-checked version of its dependency, and every later check resolves through the lock. In the lab run the package’s four scenarios pass right after the pin, so the pin is ready to commit. A package that names two dependencies cannot be wired in one call; the wiring replay explains the one-at-a-time order.

  • add records a new dependency and resolves the closure over registry descriptors. It also accepts a container file in place of a package name; then it reads no registry at all.
  • install materializes the pinned closure into the project. It alone offers an offline mode and a recovery mode, and it restores from a container through its transfer flag.
  • update moves a pin forward. It can replay saved evaluations under the candidate version.
  • outdated previews newer stable versions of each pinned package and checks whether the pinned bytes still match. It leaves the lock untouched.

Only install runs offline. Adding and updating by name must read registry descriptors to build the closure. Registries are local directories, so offline here means no registry reads at all (rebuilding from the local cache alone), not merely no network. The container forms are the other way to move bytes without touching a registry. Available in the public release (S).

The preview flag belongs to the commands that write: add, install, and update accept a dry-run flag. outdated takes none, because it never writes; passing the flag to it is refused outright. Every command above accepts repeatable registry addresses. (S)

The usage lines below are a verbatim excerpt of the command listing:

Output
$ law --help
law add <name>[@<version>] [--project <dir>] [--from <registryId>]
[--registry <ID=LOCATION>]… [--dry-run] [--json]
law install [--project <dir>] [--registry <ID=LOCATION>]… [--offline]
[--dry-run] [--recover] [--json]
law update <name>[@<version>] [--project <dir>] [--registry <ID=LOCATION>]…
[--dry-run] [--replay <dir>] [--json]

Version selection: when no version is given, the newest stable dotted release wins. Prereleases and non-dotted releases are never selected automatically. The dotted grammar is a selection convenience, not a promise of compatible behavior. The full rules live on Versions.

Argument handling is strict. Called with no package, add refuses — verbatim error-stream output, exit code 1:

Output
$ law add
law: REFUSAL: law add: needs exactly one argument — <name>[@<version>]

The refusal changes no file. Name exactly one package, with or without @<version>, and run the command again.

The lockfile pins the whole closure: one exact version per package, content hashes for the compiled bytes, dependency edges, the effective semantics line, the canon profile block, and a resolution hash over all of it. Checks that run against a project resolve through the lock, not through the manifest wishes. Validation (pin coverage, hash agreement, profile agreement) is part of the same machinery. (S)

See Packages, cases, and worlds for how the locked closure becomes a linked world, and the Schema catalog for the lock format itself.

A registry is an address, not an authority. It is a directory layout of descriptors and blobs that the resolver reads. Descriptors name exact versions and content hashes. The downloaded bytes are verified against the promised hash before anything trusts them. Registry identity stays separate from mirror addresses, and a project may override addresses per registry id. (S)

There is no hosted registry service behind these pages. Directory registries that the commands consume and publish are supported surface. Anything beyond that is unconfirmed (U): this guide promises nothing about it.

A portable container carries pins plus bytes for offline transfer. add accepts a container file in place of a package name, and install restores from one through its transfer flag. Neither form reads any registry. The installed result is the same pinned closure a registry resolution would have produced. (S)

A canon profile pins a named set of packages (jurisdiction, discipline, or custom scope) with versions, namespaces, content hashes, calendars, and its own content hash. Three rules govern it:

  • The profile slot in the manifest is optional. A manifest that uses it names exactly one profile at the lock root.
  • The lock carries the profile block under the resolution hash.
  • Published profile descriptors repeat the full element list, so the graph is checkable before any download. (S)

Profiles, release lines, and upgrades are covered from the release side on Publishing a release, Compatibility: versions and language lines, and Upgrades and replay.

Toolchain, hashes, and full transcripts

The example comes from the lab’s wiring replay, run on the public law-v0.1.1 release; the toolchain block is on the topic index. The full recorded step, verbatim, with the preparation commands and the two follow-up checks:

Terminal
python3 -c "p='/tmp/lab-work/wire/reg/law.toml'; t=open(p).read(); open(p,'w').write(t.split('[dependencies]')[0].rstrip()+'\n')"
rm -f /tmp/lab-work/wire/reg/law.lock
law add 'labparcels.iface@0.1.0' --project /tmp/lab-work/wire/reg --registry lab=/tmp/lab-work/registry --from lab
law fix imports /tmp/lab-work/wire/reg --check
law test /tmp/lab-work/wire/reg | tail -n 1
Output
lab ← /tmp/lab-work/registry (--registry)
exports (7): Owner, Parcel, ParcelKind, commercial, garden, owns, residential
changed: .law/transport.json
changed: deps/labparcels.iface.lawir.json
changed: law.lock
changed: law.toml
/tmp/lab-work/wire/reg
`use self` blocks are canonical
total: 4 checked, 4 passed, 0 failed, 0 not run; code 0

Documentation for Arxo. Writings — blog.arxo.io.

Anonymous visit counts on stats.arxo.io, no cookies.