cabin.lock Reference
Cabin uses a deterministic on-disk lockfile that records the package
versions chosen by the resolver. Artifact fetching reuses the same
schema and uses the recorded checksum to verify each fetched
source archive in cabin fetch and cabin build.
The lockfile is generated — Cabin owns its content. Tracking it in version control is recommended so collaborators and CI converge on the same resolution and on the same archive bytes.
Workflow
# First resolve: writes cabin.lock next to the root manifest.
cabin resolve --manifest-path app/cabin.toml --index-path index
# Subsequent resolves prefer the locked versions even if newer ones
# exist (as long as constraints still allow them).
cabin resolve --manifest-path app/cabin.toml --index-path index
# Refresh the lockfile to the newest compatible versions.
cabin update --manifest-path app/cabin.toml --index-path index
# Refresh just one package.
cabin update --package fmt --manifest-path app/cabin.toml --index-path index
# CI mode: require an up-to-date lockfile, never write.
cabin resolve --locked --manifest-path app/cabin.toml --index-path index
--locked and --frozen both forbid writing cabin.lock. The
distinction is intent: --frozen additionally forbids any other
state-writing side effect — most importantly, the artifact cache must
not be populated. Already-cached, already-extracted artifacts may be
reused.
File location
cabin.lock lives next to the root manifest:
cabin resolve --manifest-path app/cabin.tomlwritesapp/cabin.lock.- For workspace roots the lockfile sits beside the workspace root manifest, not inside any member directory.
- The lockfile is not placed inside
--build-dir.
Format
# This file is automatically generated by Cabin.
# Do not edit it manually.
version = 1
[[package]]
name = "fmt"
version = "10.2.1"
source = "index"
checksum = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
[[package]]
name = "spdlog"
version = "1.13.0"
source = "index"
checksum = "sha256:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
dependencies = ["fmt"]
| Field | Where | Required | Description |
|---|---|---|---|
version |
top level | yes | Schema version. Cabin supports only 1. |
[[package]] |
repeated | yes per package | One entry per resolved registry package. Sorted alphabetically by name. |
name |
package | yes | Package name. |
version |
package | yes | Resolved SemVer version. |
source |
package | yes | Where the package came from. Cabin supports "index" plus local provenance records for patches and vendoring. |
checksum |
package | no | sha256:<hex> digest of the source archive's bytes. In --locked mode the resolver fails on lockfile-vs-index disagreement; in cabin fetch / cabin build the same value is used to verify the archive's content as it is copied into the cache. |
dependencies |
package | no (only emitted when non-empty) | The package's transitive dependency names, sorted alphabetically. |
Local path packages are deliberately not recorded in
cabin.lock; they are resolved structurally on every load by
cabin-workspace.
[target.'cfg(...)'.<kind>] predicates are evaluated before
resolution against the host platform, so the lockfile records the
host-evaluated closure exactly as if the non-matching entries had
never been declared. The target field is therefore not stored
on locked packages — re-running on a host whose predicates
re-evaluate to a different set updates the lockfile via the
ordinary cabin update path. See
target-dependencies.md.
The format is strict:
- the parser uses
deny_unknown_fields; a typo or unsupported key is a clear error; version = 1is the only accepted schema version;- duplicate
[[package]]entries (same name) are rejected; - invalid SemVer in any
versionfield is rejected; - unknown
sourcevalues are rejected.
The writer guarantees:
- a stable header comment;
- alphabetical ordering of
[[package]]entries by name (then by version); - alphabetical ordering of each entry's
dependencieslist; - byte-identical output for the same in-memory
Lockfile; - atomic replacement of an existing
cabin.lock: the new bytes land in a sibling temporary file and only rename onto the destination after a successful write, so an interrupted run leaves the previous lockfile intact.
[[patch]] and [[source-replacement]] arrays
When the active local-policy layer (manifest [patch] table
plus config patches and source replacements) is non-empty, the
lockfile carries deterministic top-level arrays that record
exactly the policy applied:
[[patch]]
package = "fmt"
version = "10.2.1"
kind = "path"
provenance = "manifest"
path = "../fmt"
[[source-replacement]]
original = "https://example.com/index"
original-kind = "index-url"
replacement = "../mirror"
replacement-kind = "index-path"
provenance = "user-config"
Both arrays are optional: old lockfiles that omit them parse
unchanged, and Cabin only writes them when at least one entry
is active. Under --locked, if the recorded arrays differ from
the active policy, resolution surfaces a stale-lockfile error
(--locked cannot be used because active patch /
source-replacement policy differs from <lockfile>).
--no-patches empties both arrays for the current invocation.
The patch-state staleness check is independent of whether the
resolver itself runs. Even when the active patches cover every
versioned dependency (so the resolver has nothing to do and
prints (no versioned dependencies)), --locked still compares
the recorded arrays against the active policy: adding or
removing a patch since the lockfile was written makes the
lockfile stale, and --locked will refuse rather than silently
succeed. Comparison uses the same canonical (sorted) arrays the
renderer emits, so it is deterministic.
Full protocol in
patch-overrides.md.
Commands
cabin resolve
Default mode. Reads cabin.lock if it exists. Resolves with the
locked versions as preferences (newest compatible if the locked
version no longer satisfies). Writes the new lockfile if it differs
from the existing one.
cabin resolve --locked
Reads cabin.lock and requires it to be current:
- fails if the lockfile does not exist;
- restricts each candidate set during resolution to the locked version, so any deviation surfaces a precise error (missing package, missing version, yanked, constraint violation, checksum mismatch);
- never writes the lockfile.
Use this in CI to verify that contributors didn't forget to commit their lockfile updates.
cabin resolve --frozen
Same lockfile-strict semantics as --locked plus a stricter promise:
no generated state should be written. For cabin fetch and
cabin build, this also means the artifact cache must not be
populated — already-cached and already-extracted artifacts may still
be reused, but a cache miss in --frozen is a clear error rather
than an excuse to copy.
cabin update
Re-resolves everything, ignoring the locked map. Writes the new lockfile.
cabin update --package <name>
Drops just one entry from the locked map and re-resolves with the rest still preferred. Useful for refreshing one dependency without touching the rest of the graph.
--package validates that <name> is actually a versioned
dependency of the root package — otherwise it errors.
Yanked versions
The resolver always excludes yanked candidates in PreferLocked /
UpdateAll / UpdatePackage. In Locked mode (--locked /
--frozen), if the locked version is yanked, the resolver fails
with a clear error so the user is forced to run cabin update.
Pre-release versions
Pre-release versions (1.0.0-alpha, 2.0.0-rc.1, …) are excluded
from candidate selection by default, mirroring semver::VersionReq::matches.
A pre-release version is admitted only when one of the
comparators making up the requirement names the same
major.minor.patch with a non-empty pre tag — for example
fmt = "=1.0.0-alpha" (singleton opt-in) or
fmt = ">=1.0.0-alpha, <1.0.0" (range that explicitly opens the
1.0.0 pre-release window). Wide constraints such as
fmt = ">=1.0.0, <2.0.0" never pick a pre-release even if one is
the only candidate published in the index. A locked-in
pre-release survives a follow-up cabin resolve so existing
lockfile pins do not silently churn.
Resolver diagnostics
Dependency resolution failures are rendered through Cabin's
miette-based diagnostics layer. Every variant of ResolveError
carries the stable diagnostic code cabin::resolver::error along
with per-variant help text. Locked-mode errors remain specific so
users can tell whether to update the lockfile, fix constraints, or
investigate a checksum mismatch; conflict failures embed a
human-readable explanation derived from PubGrub's reporter output.
Limitations
The following are not part of the current lockfile contract:
- network registry access (HTTP / sparse index client)
- registry authentication
- a registry server
- package publishing (
cabin package,cabin publish) - OCI / GHCR or other remote-archive transports
- a binary artifact cache or remote build cache
- alternative
sourcevalues incabin.locksuch aspathorgit - locking of local path packages
- workspace-level
cabin.lockfor workspaces without a root[package] - a separate
cabin generate-lockfilecommand (cabin resolveandcabin fetchare the producers) - a public JSON Schema document for
cabin.lock