Package Lock File
Overview
The package lock file (packages/package-lock.json) records the
fully-resolved identity of every fetched package. Unlike ivpm.yaml,
which describes what you want (e.g. branch: main), the lock file
records what you actually got (e.g. commit_resolved: a1b2c3d).
The lock file is written automatically as a side-effect of ivpm update
and ivpm sync. It is a local artifact — it lives inside the
packages/ directory alongside the fetched packages and is not committed
to version control by default.
When you need to reproduce an exact workspace — for archival, CI, or
debugging — copy the lock file to a stable location and pass it back to
ivpm update --lock-file.
Key Properties
Complete transitive closure — every package fetched, including transitive dependencies, is recorded. There is no need to scan sub-package
ivpm.yamlfiles during reproduction.Source-type aware — each entry records the fields relevant to its source type (git commit hash, GitHub release tag, HTTP ETag, pip version, etc.).
Platform-agnostic version — for GitHub Releases (
gh-rls), the resolved version tag (e.g.v2.3.1) is stored. The correct platform binary is resolved at fetch time, so the same lock file works across Linux, macOS, and Windows.Reproducibility flag — packages sourced from local paths (
dir,file) are still recorded but are marked"reproducible": falsebecause they cannot be restored on a different machine.Python package versions — the Python handler contributes the complete set of pip-installed package versions under the
python_packageskey, enabling full Python environment reproducibility. This works regardless of whetherpiporuvwas used to install.Integrity checksum — a SHA-256 checksum of the lock file body is embedded in the
sha256field. IVPM warns (but does not fail) if the checksum does not match, allowing you to detect accidental manual edits.
Lock File Format
{
"ivpm_lock_version": 1,
"generated": "2024-01-15T10:23:00+00:00",
"sha256": "...",
"packages": {
"my_git_lib": {
"src": "git",
"url": "https://github.com/org/my_git_lib.git",
"branch": "main",
"tag": null,
"commit_requested": null,
"commit_resolved": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
"cache": null,
"resolved_by": "root",
"dep_set": "default",
"reproducible": true
},
"my_tool": {
"src": "gh-rls",
"url": "https://github.com/org/my_tool",
"version_requested": "latest",
"version_resolved": "v2.3.1",
"cache": null,
"resolved_by": "root",
"dep_set": null,
"reproducible": true
},
"an_archive": {
"src": "http",
"url": "https://example.com/archive.tar.gz",
"etag": "abc123",
"last_modified": "Wed, 15 Jan 2024 00:00:00 GMT",
"cache": null,
"resolved_by": "my_git_lib",
"dep_set": null,
"reproducible": true
},
"requests": {
"src": "pypi",
"version_requested": ">=2.0",
"version_resolved": "2.31.0",
"resolved_by": "root",
"dep_set": null,
"reproducible": true
},
"local_lib": {
"src": "dir",
"path": "../../shared/local_lib",
"resolved_by": "root",
"dep_set": null,
"reproducible": false
}
},
"python_packages": {
"certifi": "2024.1.1",
"charset-normalizer": "3.3.2",
"idna": "3.6",
"requests": "2.31.0",
"urllib3": "2.1.0"
}
}
Fields common to all entries:
srcSource type:
git,gh-rls,http,pypi,dir,file, etc.resolved_byThe package that first introduced this dependency.
"root"means it came directly from the top-levelivpm.yaml. The first (highest- priority) specification wins; lower-level duplicates are ignored.dep_setThe dependency-set name used when loading sub-dependencies from this package.
reproducibletruefor packages that can be restored on any machine.falsefordirandfilepackages (local paths).
Change Detection
When ivpm update runs and a lock file already exists, IVPM compares the
user-specified fields in ivpm.yaml against the corresponding lock
entry for each package:
git:
url,branch,tag,commit,cachegh-rls:
url,versionhttp:
urlpypi:
version
If the specs match, the package is considered up to date and no network
calls are made. If the specs differ (e.g. you changed branch: main
to branch: dev), IVPM reports the differences but does not re-fetch
unless you also pass --refresh-all or --force.
# Reports differences, takes no action
$ ivpm update
# Re-fetches packages whose specs changed
$ ivpm update --refresh-all
# Re-fetches everything; suppresses safety errors
$ ivpm update --force
Reproduction Mode
Pass a lock file as input to ivpm update to reproduce an exact workspace:
$ ivpm update --lock-file ./ivpm.lock
In this mode:
ivpm.yamlis not read for packages — the lock file is the sole source of truth.Every package is fetched at its pinned resolved version:
git — shallow clone at
commit_resolved; falls back to full clone if the server does not support arbitrary commit fetch.gh-rls — fetches the exact
version_resolvedtag; platform binary selection still occurs at fetch time.http — fetches the same URL; warns if the ETag/Last-Modified header no longer matches.
pypi — installs
version_resolvedexactly.
Sub-package
ivpm.yamlfiles are not scanned; the lock file already encodes the complete transitive closure.The
packages/destination is still determined by the activeivpm.yaml(or the CLI default), not the lock file.Cache interaction is unchanged: if the pinned version is already in the IVPM cache, it is reused.
Note
The lock file does not encode absolute paths. deps_dir can differ
between the machine that generated the lock and the machine that
consumes it.
CI/CD Reproducibility Pattern
The recommended workflow for CI reproducibility:
On the developer workstation:
# 1. Fetch/update packages normally
$ ivpm update
# 2. Archive the lock file (packages/ may be .gitignore'd)
$ cp packages/package-lock.json ./ivpm.lock
# 3. Commit the lock file alongside ivpm.yaml
$ git add ivpm.lock
$ git commit -m "Update dependency lock file"
In CI:
# Reproduce the exact workspace from the committed lock file
$ ivpm update --lock-file ./ivpm.lock
GitHub Actions example:
- name: Install IVPM
run: pip install ivpm
- name: Reproduce workspace from lock
run: ivpm update --lock-file ./ivpm.lock
- name: Run tests
run: ivpm activate -c "pytest"
Syncing and the Lock File
ivpm sync pulls the latest upstream commits into all editable (writable)
git packages. After sync completes, the lock file is automatically
regenerated to reflect the new HEAD commit of each updated package.
This means packages/package-lock.json always represents the current
state of your packages directory, whether packages were fetched by
update or brought forward by sync.
$ ivpm sync
# ... merges upstream changes into editable packages ...
# Note: Updated package-lock.json after sync
Python Package Version Locking
After Python packages are installed into the IVPM-managed virtual
environment, the Python handler queries the venv for all installed package
versions using pip list. This works regardless of whether pip or
uv was used to install packages — both write to the same venv
site-packages directory.
The result is stored under the top-level python_packages key in the lock
file. In reproduction mode (--lock-file), PyPI packages are pinned to
their version_resolved value, which corresponds to the version recorded
in python_packages.
Note
python_packages records all packages installed in the venv,
including transitive pip dependencies that were not explicitly listed in
ivpm.yaml.
Format Versioning
The ivpm_lock_version field guards against schema changes. IVPM will
reject lock files with an unrecognised version number and emit a clear error:
ValueError: package-lock.json version 2 is not supported (expected 1).
Please regenerate the lock file with this version of ivpm.
See Also
Development Workflows — Reproducible build and CI workflows
Caching — How IVPM caches packages
Reference — Full
ivpm update/ivpm syncoption reference