VibeVM
Contents
On this page
en
Publisher
org.vibevm.core
Version
1.0.0latest
Audiences
user, author
Reading time
6 min
Rendered
Read aloud
never

PROP-022 — Materialization modes

01Status: IMPLEMENTED (specified 2026-06-24 in an owner-requested design session; verified against the tree 2026-07-25 by the spec-actualization campaign). The Materialization enum ships with Copy as the default (wire copy; named Snapshot/snapshot until the 2026-08-13 terminology ruling), InPlace beside it and a doctested is_in_place (vibe-core package.rs); the hardlink / in-place machinery runs through vibe-install (plan.rs / fetched.rs / apply.rs, the materialise_in_place seam); submodule snapshot-embedding lands in git_package_registry/fetch.rs; and the destructive guard sits in commands/uninstall.rs. R1 successor 6d606ef2/1cf4f189 reconciles copy/hardlink refresh through the strict slot record without touching unrecorded outputs. One of four orthogonal specs from the bridge-packages design (siblings: PROP-020 install hooks, PROP-021 submodule sources, PROP-023 bridge packages). Materialization mode is a property of any package; a huge git package wants in-place with no bridge in sight.

02Related: PROP-009 (the materialise step into vibedeps/), PROP-007 (vibedeps/ layout), PROP-010 (the live-git cache + .git-stripped content tree the copy modes draw from), PROP-019 §2.15 (the VVM placer diff-copy/hardlink — direct prior art for hardlink), PROP-019 §2.16 (VVM "sources by reference" — prior art for in-place), PROP-020 §2.1 (hook-edit reset rides on the mode).

1. Motivation

1.1 The problem — one materialisation policy does not fit every package

  • 03Today every package is materialised the same way: clone into the live-git cache, strip .git into a content tree, then full recursive copy of that tree into the vibedeps/<group>.<name>/<version>/ slot (identity-keyed — owner ruling 2026-08-13; the slot carried <kind>-<name> before that ruling, which collided same-named packages of different groups and moved a package on a kind change).
  • This is right for ordinary packages and gives the lockfile a stable content_hash and a committable, offline-reproducible vendored slot.

04It fails at two different scales of "big":

  1. 05Big in bytes — a package with a few large binary assets pays a full byte copy per install/update of data that did not change.
  2. Big in file count — a package with millions of small files (think the Chromium source tree) is killed not by bytes but by per-file syscalls: copying — or even hardlinking — five million files takes hours, barely faster than re-fetching over the network. The full-tree walk (content_hash reads every file too) is itself the cost.

06The fix is to make materialisation a declared mode on the package, and to borrow the two cost-avoidance primitives VVM already proved (PROP-019 §2.15/§2.16).

2. Decisions

2.1 Three modes, declared in the descriptor

07req r1

08[package].materialization selects how the package lands on disk:

09[package]
materialization = "copy"       # default | "hardlink" | "in-place"
  • 10copy is the default and the only mode an ordinary package needs. (It was named snapshot until the owner's 2026-08-13 terminology ruling reserved that word for the unfrozen version — PROP-044 §2b; the legacy spelling is refused with the rename recipe, never aliased.)
  • The mode is published in the descriptor so a consumer sees, before installing, how a package will be placed.

2.2 copy — the vendored full copy (default)

11req r1

  • 12Initial placement is live-git cache → .git-stripped shippable tree → full recursive copy into the slot. Refresh is record-aware: the incoming tree is reconciled against .vibe-slot.toml rather than replacing the directory.
  • PROP-024 §2.2 extends the .git strip to build output (.vibe/, target/, node_modules/, .vibeignore globs) for code-bearing packages, so such a package vendors its source, never its build artifacts.
  • The slot is a self-contained tree, identified by content_hash (§2.5), vendored into the project's git (§2.7).
  • Submodule content is embedded into the copied tree (PROP-021 §2.3).
  • For copy and hardlink slots, update/reinstall restores the materialiser-owned payload by diffing the incoming shippable tree against .vibe-slot.toml: changed owned files are replaced, stale owned files are removed, equal files stay untouched, and unrecorded build output is preserved. Hooks rerun exactly when that materialisation diff is nonempty, per PROP-020 §2.1.

2.4 in-place — git-native, project-local, no copy

15req r1

16For packages big in file count (and incidentally bytes), where even one full tree walk is unacceptable. vibevm never walks the tree:

  • 17git clone --recurse-submodules lands directly in the slot, bypassing both the cache clone and the snapshot copy — one physical copy on the machine, not three (decisive when disk cannot hold several copies of a giant).
  • git manages it in place: update is git fetch + checkout (incremental, touches only changed objects/files); a hook's edits are reset with git clean -dfx in the slot.
  • Project-local, never shared. Each project gets its own clone in its own vibedeps/; there is deliberately no cross-project sharing, which removes the concurrent-mutation problem a shared global clone would create.
  • The slot path is not version-qualified. An in-place slot is vibedeps/<group>.<name>/ (no /<version>/): one working clone whose version is the current git ref. Versioning the path would mean two on-disk copies of the giant — the opposite of the goal.
  • Requires a git source. Incremental update and git clean reset both need git; a non-git source has no in-place story (§4).

2.5 Identity follows the mode

18req r1

  • 19copy / hardlink — source identity remains content_hash; .vibe-slot.toml independently records the owned destination footprint and exact per-file SHA-256 values used for reconciliation and verification.
  • in-placeresolved_commit, not content_hash. The slot is a mutable git working tree (hooks edit it), so a content hash is neither stable nor affordable to compute; the git commit is the identity, known in O(1). The lockfile already records resolved_commit, so no new field is needed.

2.6 Destructive operations on an in-place slot need confirmation

20req r1

  • 21An in-place slot may be a multi-hour download. Any destructive operation on it — uninstall, reinstall --force, a version switch that requires a re-clone, or slot removal — must be confirmed: interactively a y/n, and in a non-interactive run it requires an explicit flag (--force) or it aborts rather than silently deleting an expensive resource.
  • Hooks and their reset (git clean -dfx) are exempt — they are routine and trusted (the hook author is assumed competent, PROP-020).
  • The guard protects against accidental loss, not against the package's own lifecycle.

2.7 Vendoring differs by mode

22req r1

  • 23copy / hardlink are vendored — the slot is committed into the project's git and is offline-reproducible from it (a hardlink slot's bytes are materialised into git on git add like any file); a copy slot trivially so.
  • in-place is not vendored — the slot (a nested .git plus possibly millions of files) is .gitignored in the project; restoration is a re-clone at the lockfile's resolved_commit. The honest trade: in-place packages need the network to restore, where snapshot packages do not.

3. Rejected alternatives

  • 24Content-hashing an in-place package — rejected for the same reason VVM rejected hashing distributions (PROP-019 §9.2): hashing millions of files / gigabytes per operation is prohibitive, and the tree is mutable anyway. Identity is the commit (§2.5).
  • A shared global in-place clone (like the registry cache) — rejected: two projects mutating one giant working tree via hooks would collide; project-local clones make the problem disappear (§2.4).
  • Hardlink as the giant-repo answer — rejected: per-file syscalls still cost hours at millions of files; only in-place (no per-file work) solves the file-count axis (§2.4).
  • reflink / CoW placement — deferred (§4): not portable; hardlink is the portable byte-sharing primitive, matching VVM's choice.

4. Out of scope

  • 25reflink / CoW placement where the filesystem supports it — far-backlog, as in PROP-019 §6.
  • in-place for a non-git source — needs git for incremental update and git clean reset; a binary/path source has no in-place mode.
  • Automatic cache garbage collection for the live-git cache — owned by PROP-010.

5. Acceptance

  • 26[package].materialization parses to copy (default) / hardlink / in-place; an unknown value is a manifest error, and the legacy snapshot is refused with the rename recipe.
  • copy and hardlink reconcile their recorded footprint without touching unrecorded paths; hardlink may share initial files with copy fallback and replaces changed destinations without mutating source inodes. in-place clones once into an unversioned, .gitignored slot managed by git.
  • in-place identity is resolved_commit; no full-tree hash is computed.
  • A destructive op on an in-place slot confirms interactively / requires --force non-interactively; hooks and git clean are exempt.
  • Full self-check.sh green; conform 0/0/0; specmap clean.

For an agent

This page has a machine mirror. The citation carries the version rather than latest, so what an agent quotes does not move under it.

spec://org.vibevm.core/vibevm@1.0.0/modules/vibe-workspace/PROP-022-materialization-modes

.md.xmlllms.txt