Markdown for LLMs
Dependencies, locks, and registries
The source Markdown for this article. Copy it into your assistant or download it as a text file.
# Dependencies, locks, and registries
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](/corpus/lab/#before-you-start); the example needs the
throwaway registry built in the lab's
[versions exercise](/corpus/lab/solutions/#versions-exercise). Status
letters follow the [legend on the topic index](/corpus/#how-this-topic-marks-confidence).
## Example: pin one dependency
The lab's `parcels-registry` package imports the shared vocabulary
`labparcels.iface`. The [wiring replay](/corpus/lab/solutions/#how-the-fixtures-were-wired)
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:
```shell
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](#reproduction-details)):
```text
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`](/corpus/lab/fixtures/parcels-registry)
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](/corpus/lab/solutions/#how-the-fixtures-were-wired)
explains the one-at-a-time order.
## Adding, installing, updating, previewing
- `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:
```text
$ 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](/protocols/versions/).
Argument handling is strict. Called with no package, `add` refuses —
verbatim error-stream output, exit code 1:
```text
$ 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 is the memory
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**](/corpus/model/) for how the locked
closure becomes a linked world, and the
[Schema catalog](/protocols/schemas/) for the lock format itself.
## Registries are directories
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.
## Moving a closure without a network
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)
## Canon profiles pin the jurisdiction set
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**](/corpus/releases/publishing/), [**Compatibility: versions and language lines**](/corpus/releases/compatibility/),
and [**Upgrades and replay**](/corpus/releases/replay-and-upgrades/).
## Reproduction details
<details>
<summary>Toolchain, hashes, and full transcripts</summary>
The example comes from the lab's
[wiring replay](/corpus/lab/solutions/#how-the-fixtures-were-wired),
run on the public `law-v0.1.1` release; the toolchain block is on the
[topic index](/corpus/#reproduction-details). The full recorded step,
verbatim, with the preparation commands and the two follow-up checks:
```shell
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
```
```text
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
```
</details>
## Where to go next
- [**Packages, cases, and worlds**](/corpus/model/) — what the closure is
for: the linked world.
- [**Package boundaries: what the corpus answers**](/corpus/package-boundaries/) — the import surface and
the limits of a corpus question.
- [Packages and cases](/cli/packages-cases/) — the command reference
this page summarizes.