lpm.lock format
TOML lockfile schema — what every field means and why it's there.
lpm.lock is the human-readable, git-diffable lockfile written next to package.json for a standalone project or at the root of a workspace. It pins every transitive dependency by exact version, source identity, SRI integrity hash, platform metadata, registry-signature evidence, and any cryptographically verified provenance evidence, so the next install can reproduce the same graph without trusting ambient registry state.
The current lockfile is TOML-only because the binary format cannot represent exact package instances. LPM CLI removes a stale lpm.lockb.
File location
Standalone project: <project-root>/lpm.lock
Workspace: <workspace-root>/lpm.lock. Member commands read their importer projection from this file; successful recursive migration removes legacy member lockfiles.
Top-level shape
[metadata]
lockfile-version = 13
resolved-with = "greedy-fusion"
# Only present when the default linker auto-switched to isolated
# after detecting incompatible peer requirements.
auto-isolated-peer-conflicts = true
[importers.".".dependencies]
react = "^19.0.0"
[importers.".".dev-dependencies]
vite = "^6.0.0"
[patches."left-pad@1.3.0"]
path = "patches/left-pad@1.3.0.patch"
sha256 = "sha256-..."
original-integrity = "sha512-..."
[provenance."react@19.0.0#npm-..."]
subject-name = "pkg:npm/react@19.0.0"
subject-sha512 = "..."
integrated-time-secs = 1740000000
log-id = "..."
log-index = 123456
bundle-sha256 = "sha256-..."
[provenance."react@19.0.0#npm-...".snapshot]
present = true
publisher = "github:facebook/react"
workflowPath = ".github/workflows/publish.yml"
workflowRef = "refs/tags/v19.0.0"
attestation_cert_sha256 = "sha256-..."
[[packages]]
instance-id = "d7d8fbc52d7d998935c243be78e563594fcb08459d8b71dfdf96c60ed307a4e8"
name = "react"
version = "19.0.0"
source = "registry+https://registry.npmjs.org"
integrity = "sha512-..."
dependencies = ["scheduler@0.25.0"]
tarball = "https://registry.npmjs.org/react/-/react-19.0.0.tgz"
registry-published-at = "2025-01-01T00:00:00.000Z"
node-engine = ">=18"
[[packages.registry-signatures]]
keyid = "SHA256:..."
sig = "MEUCIQ..."
[packages.dependency-targets]
scheduler = "58b7a5e0c3f25598fdbd56da146a1841b4ce2b78f359414cd782817437512560"
[[packages]]
instance-id = "58b7a5e0c3f25598fdbd56da146a1841b4ce2b78f359414cd782817437512560"
name = "scheduler"
version = "0.25.0"
source = "registry+https://registry.npmjs.org"
integrity = "sha512-..."
os = ["darwin", "linux"]
cpu = ["arm64", "x64"]
libc = ["glibc"]
optional = true
[[packages]]
instance-id = "8d7fd69910cda17e7de89f87e3eed10a3f182ecf34ae46d2592046ae553f5296"
name = "wa-sqlite"
version = "1.0.0"
source = "git+https://github.com/rhashimoto/wa-sqlite.git#779219540f66cecaa159da32b3b8936697ba10a7"
integrity = "sha512-..."
# Only present when at least one root dep uses npm:<target>@<range>
[root-aliases]
my-react-alias = "react"
# Only present when the eager peer-drain pass synthesized installs to
# satisfy unmet peerDependencies (default-on under autoInstallPeers).
ambient-peer-installs = ["loose-envify"]The example above shows the standalone package table. A workspace union replaces [[packages]] with content-addressed rows and gives every importer an explicit projection:
[metadata]
lockfile-version = 13
resolved-with = "greedy-fusion"
[importers."."]
locked-packages = ["sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
[importers."packages/web"]
locked-packages = [
"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
]
[importers."packages/web".dependencies]
react = "^19.0.0"
[workspace-packages."sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
instance-id = "d7d8fbc52d7d998935c243be78e563594fcb08459d8b71dfdf96c60ed307a4e8"
name = "react"
version = "19.0.0"
source = "registry+https://registry.npmjs.org"
integrity = "sha512-..."[metadata]
| Field | Type | Notes |
|---|---|---|
lockfile-version | u32 | Schema version. Current: 13. Version 11 added local manifest fingerprints. Version 12 added structured peer edges. Version 13 added exact package-instance identities for rows, edges, and roots. |
resolved-with | string | Which resolver produced this file (e.g. "greedy-fusion", "pubgrub"). Informational. |
auto-isolated-peer-conflicts | bool | Present only when a default-hoisted install detected incompatible peer requirements and auto-switched the project to isolated layout. Warm installs read this before the resolver runs so the install hash and linker stay on the peer-preserving layout. Explicit linker config ignores this flag. |
[importers]
[importers.".".dependencies]
react = "^19.0.0"
[importers.".".dev-dependencies]
vite = "^6.0.0"
[importers.".".lpm-overrides]
"left-pad" = "1.3.0"
[importers.".".catalogs.default]
react = "^19.0.0"
[importers."."]
patches-fingerprint = "sha256-..."
peer-dependency-rules-fingerprint = "sha256-..."
auto-install-peers = trueImporter snapshots record the manifest inputs that must match before a frozen install can replay the lockfile. A standalone lockfile uses ".". A workspace union also uses member-relative keys such as "packages/web"; paths must remain safely below the lockfile root.
| Field | Type | Notes |
|---|---|---|
dependencies | table | package.json > dependencies exactly as declared. |
dev-dependencies | table | package.json > devDependencies exactly as declared. |
optional-dependencies | table | package.json > optionalDependencies exactly as declared. |
peer-dependencies | table | package.json > peerDependencies exactly as declared. |
lpm-overrides | table | package.json > lpm.overrides after catalog references are resolved. |
overrides | table | npm-style package.json > overrides after catalog references are resolved. |
resolutions | table | yarn-style package.json > resolutions after catalog references are resolved. |
catalogs | nested table | Catalog ranges visible to this importer. |
patches-fingerprint | string | Hash of lpm.patchedDependencies; absent when no patches are declared. |
peer-dependency-rules-fingerprint | string | Hash of lpm.peerDependencyRules; absent when default/empty. |
auto-install-peers | bool | Effective autoInstallPeers value captured for frozen replay. |
workspace-root-peer-providers-fingerprint | string | Exact workspace-root provider graph used when satisfying this importer's peers. |
locked-packages | string[] | Sorted SHA-256 IDs in workspace-packages that form this importer's package closure. Workspace unions only. |
root-aliases | table | Importer-local npm alias links. |
root-resolutions | table | Importer-local exact root package selections. |
ambient-peer-installs | string[] | Importer-local peers surfaced at its node_modules root. |
patches | table | Importer-local patch evidence. |
catalog_resolutions | nested table | Importer-local resolved catalog snapshot. |
provenance | table | Importer-local verified provenance evidence. |
auto-isolated-peer-conflicts | bool | Whether this importer automatically selected isolated linking for peer conflicts. |
[workspace-packages]
Workspace lockfiles store the union graph as tables keyed by sha256:<hex>. The digest includes each exact package instance and its exact edge targets.
Two rows can share one package name, version, and source. They remain separate when their dependency or peer contexts are different.
Every importers.<path>.locked-packages entry must reference an existing row. The reader rejects unsafe importer paths, duplicate IDs inside one importer, unreferenced union rows, content-address mismatches, and ambiguous package identities inside a projection. These checks run before commands consume the graph.
Projecting an importer rebuilds the standalone view expected by install and read-only commands: its package rows, root aliases and selections, ambient peers, patches, catalogs, provenance, and automatic linker state. Other importers' rows remain invisible.
[patches]
[patches."left-pad@1.3.0"]
path = "patches/left-pad@1.3.0.patch"
sha256 = "sha256-..."
original-integrity = "sha512-..."Patch records bind package.json > lpm.patchedDependencies to the exact patch file bytes LPM CLI is allowed to replay. The table key is the exact patched package selector (<name>@<version>).
| Field | Type | Notes |
|---|---|---|
path | string | Patch file path relative to the project root. |
sha256 | string | SHA-256 digest of the patch file contents. Install hard-errors if the current file hash differs from this value. |
original-integrity | string | SRI integrity of the pristine package bytes the patch was authored against. Install also verifies this before applying the patch. |
[patches] is TOML-only metadata. Projects with patch records skip lpm.lockb and remove any stale binary companion.
[[packages]] and workspace package rows
Standalone lockfiles use one [[packages]] entry per resolved package. Workspace unions use the same fields below each [workspace-packages."sha256:..."] key. Standalone entries are sorted by package identity; workspace rows are sorted by content address for deterministic diffs and minimal merge conflicts.
GitHub packages use a commit-pinned source value and SHA-512 integrity. The user-written branch, tag, shorthand, or commit remains in the importer snapshot, while the package entry always records the resolved lowercase 40-character commit. This separation lets frozen and offline installs verify the manifest is unchanged while replaying immutable package bytes.
Root replay checks the manifest source kind and identity. A registry range cannot select a GitHub row with the same name and version.
Direct remote-tarball URLs must match exactly. Direct local paths must identify the same package or remain reachable through the declared local-source graph.
| Field | Type | Notes |
|---|---|---|
instance-id | string | Required 64-character lowercase hexadecimal ID for this exact graph instance. |
name | string | Package name (e.g. react, @lpm.dev/owner.pkg) |
version | string | Exact resolved version |
source | string | absent | Typed source identity, such as registry+https://registry.npmjs.org, tarball+https://…, directory+…, link+…, or commit-pinned git+https://github.com/…#<commit>. Absent for packages without a known source. |
integrity | string | absent | SRI integrity hash (sha512-…). Populated from registry metadata or computed for supported remote sources. Required and verified when replaying registry, remote tarball, and GitHub packages. |
registry-signatures | table[] | npm-compatible dist.signatures entries persisted as nested [[packages.registry-signatures]] tables. Skipped when absent. |
registry-published-at | string | absent | Publish timestamp used to verify npm registry signing-key expiry without rehydrating package metadata. |
os | string[] | package.json > os restrictions for this version. Skipped when empty. |
cpu | string[] | package.json > cpu restrictions for this version. Skipped when empty. |
libc | string[] | package.json > libc restrictions for this version. Skipped when empty. |
node-engine | string | absent | The package's package.json > engines.node constraint. Warm and frozen installs revalidate it against the effective Node.js version and engine-strict setting. |
optional | bool | Present as true when the package is reachable only through optional dependency edges. Used with platform filtering so warm installs can skip incompatible optional packages and fail incompatible required packages. |
dependencies | string[] | Direct deps as <local_name>@<version> entries. Skipped from output when empty. |
dependency-targets | table | Maps each dependency-local name to one exact instance-id. The keys must match dependencies. |
alias-dependencies | [string, string][] | npm-alias edges as [local_name, target_canonical_name] pairs. Only present when the package uses npm:<target>@<range> aliases. |
peer-edges | table[] | Structured peer edges with local name, target name, version, and optional source wrapper ID. |
peer-targets | table | Maps each peer-local name to one exact instance-id. The keys must match peer-edges. |
tarball | string | absent | Tarball URL hint cached from resolve time. Speeds up the warm-install fast path by letting it skip the per-package metadata round-trip. Only valid for Source::Registry packages — pairing this hint with a non-Registry source is rejected at parse time. Absent on lockfiles produced before this hint shipped. |
dependencies format
dependencies = ["scheduler@0.25.0", "loose-envify@1.4.0"]<local_name>@<version> — the local-name is what the depending package writes in its own dependencies map. For non-aliased deps the local name equals the canonical registry name. For npm-alias deps (npm:react@^19.0.0 masquerading as react-canary), the local name diverges from the target — see alias-dependencies below.
For a local, tarball, or Git target, the value after @ can be its canonical source ID. The matching dependency-targets entry remains authoritative.
Exact targets
[packages.dependency-targets]
scheduler = "58b7a5e0c3f25598fdbd56da146a1841b4ce2b78f359414cd782817437512560"
[[packages.peer-edges]]
local-name = "react"
target-name = "react"
target-version = "19.0.0"
[packages.peer-targets]
react = "d7d8fbc52d7d998935c243be78e563594fcb08459d8b71dfdf96c60ed307a4e8"Each exact target must reference a package row in the same standalone graph or importer projection. LPM CLI rejects missing, ambiguous, or inconsistent targets.
Root selections also contain an instance-id. This field prevents a root link from selecting another row with the same package coordinates.
Lockfile versions 1–12 do not contain these exact targets. Upgrade a standalone project with lpm install before offline or frozen use.
For a workspace, run lpm install --recursive from the workspace root. This command creates exact projections for every retained importer.
alias-dependencies format
alias-dependencies = [["my-react", "react"]]Each [local_name, target_canonical_name] records that the package depends on my-react@<version> (in dependencies) but the actual target is react. Used to compute the right .lpm/<target>@<version>/ store path on link.
tarball field
tarball = "https://registry.npmjs.org/react/-/react-19.0.0.tgz"A dist-URL hint cache for Source::Registry packages. Lets the warm-install fast path skip the per-package metadata round-trip. For non-Registry sources (Source::Tarball, Source::Git), the URL is part of source identity (lives inside the source variant) — pairing them with this field is rejected at parse time.
Platform fields
os = ["darwin", "linux"]
cpu = ["arm64", "x64"]
libc = ["glibc"]
optional = trueLPM CLI records os, cpu, and libc restrictions from the package version's manifest. Empty arrays are omitted. Warm installs replay the same host filtering as fresh installs: incompatible optional packages are skipped; incompatible required packages fail loudly.
The optional = true bit means this package is only reachable through optional dependency edges. It is what lets the lockfile fast path distinguish "safe to skip on this host" from "required package cannot run here."
Registry signatures
registry-published-at = "2025-01-01T00:00:00.000Z"
[[packages.registry-signatures]]
keyid = "SHA256:..."
sig = "MEUCIQ..."When npm metadata includes dist.signatures, LPM CLI persists the signature payloads and the version publish timestamp. Install-time signature verification can then run from the lockfile fast path without fetching the full package metadata again. The verifier still checks the package integrity hash and registry signing keys; the lockfile stores evidence, not a cached pass/fail verdict.
[provenance]
[provenance."axios@1.14.0#npm-036b43b16792c643"]
subject-name = "pkg:npm/axios@1.14.0"
subject-sha512 = "dd8f32ae..."
integrated-time-secs = 1774638099
log-id = "wNI9atQG..."
log-index = 1189268120
bundle-sha256 = "sha256-75564031..."
[provenance."axios@1.14.0#npm-036b43b16792c643".snapshot]
present = true
publisher = "github:axios/axios"
workflowPath = ".github/workflows/publish.yml"
workflowRef = "refs/tags/v1.14.0"
attestation_cert_sha256 = "sha256-9a9a6a..."The table key is <name>@<version>#<source-id>, so evidence is attached to one exact package version from one exact source. Only successfully verified evidence is written.
| Field | Type | Notes |
|---|---|---|
subject-name | string | Exact npm package URL signed by the attestation, pkg:npm/<name>@<version>. |
subject-sha512 | string | Lowercase hexadecimal SHA-512 digest of the signed tarball subject. It must match the package entry's SHA-512 SRI integrity. |
integrated-time-secs | u64 | Rekor integrated timestamp from the verified transparency-log entry. |
log-id | string | Identity of the transparency log that recorded the attestation. |
log-index | i64 | Verified transparency-log index. |
bundle-sha256 | string | SHA-256 digest of the original Sigstore bundle bytes. |
snapshot.present | bool | Must be true; absent or unverified provenance is never locked as evidence. |
snapshot.publisher | string | absent | Publisher identity extracted from the verified certificate, such as github:axios/axios. |
snapshot.workflowPath | string | absent | Trusted publishing workflow path. |
snapshot.workflowRef | string | absent | Git ref used for this publication. |
snapshot.attestation_cert_sha256 | string | absent | SHA-256 digest of the verified leaf certificate. |
The reader rejects orphaned evidence, empty required fields, malformed digests, non-npm subjects, subject name/version mismatches, source mismatches, and subject digests that do not match the locked package integrity. A frozen install can therefore replay internally consistent evidence without fetching the attestation again.
lpm.lock is trusted project input. It stores derived evidence, not the original Sigstore bundle, so frozen replay does not rerun the certificate-chain, transparency-log, or signed-statement verification and cannot independently detect a sophisticated edit that changes all related derived fields consistently. Protect and review lockfile changes like source code. Online attestation-cache hits do rerun verification because that cache stores the original bundle bytes.
[root-aliases]
[root-aliases]
my-alias = "actual-package"Maps root-level npm-alias edges so warm installs reproduce the original node_modules/<local>/ layout without re-resolving. Empty for projects without root-level aliases — and omitted entirely from output in that case (backwards-compatible with older lockfiles that pre-date alias support).
ambient-peer-installs
ambient-peer-installs = ["loose-envify", "scheduler"]Canonical names the resolver auto-installed at root scope to satisfy unmet peerDependencies (the eager peer-drain pass, on by default via autoInstallPeers = true). These packages are already in [[packages]] (they were resolved and extracted like any other dep) — this list carries the orthogonal signal "surface them at node_modules/<peer>/ even though they aren't in pkg.dependencies." Symmetric with [root-aliases] — both are project-side install-orchestration metadata, not per-package state.
Empty and omitted from output on the common no-auto-install path (project's dependencies block already covers every declared peer). See Resolver for the auto-install contract + toggle.
Determinism
The on-disk file is deterministic by construction:
[[packages]]entries are sorted by name[workspace-packages]rows are sorted by content address- importer package IDs are sorted and deduplicated
dependenciesarrays inside each package are sorted[provenance]entries are sorted by their full package/source key- Optional fields are omitted when empty /
Nonerather than written as null
This makes git diff lpm.lock actionable: a new dep adds entries, a version bump rewrites a single package's fields, and you can spot supply-chain surprises without parsing.
Schema versioning
lockfile-version is schema-versioned, not tool-versioned. A version changes only when the file structure changes. Older clients reject a newer schema.
See also
lpm.lockbformat — companion binary lockfile- Lockfile concept — design overview, when each file is read
lpm install --offline— installs entirely from the lockfilelpm install --strict-integrity— disallow trust-on-first-use for tarball-URL deps