# PROP-019 — VibeVM Version Manager (VVM) {#root}

@status:impl/done

[p01] @fact:status-line **Status: IMPLEMENTED** (specified 2026-06-17 in an owner-requested design
session and verified against the tree 2026-07-25 by the spec-actualization
campaign — the vvm ships across the whole verb set, with dense specmap
`implements` / `verifies` coverage); **revised
to v2 the same day** after the owner found two architectural flaws in v1
(see §9): (a) making `$VIBEVM_HOME` the single source of truth forced a
console reload on every switch/reinstall, and (b) replacing the running
distribution locks its files (the `.exe`, and any future DLLs). v2 keeps the
v1 command surface but reworks the internals: a live `current` pointer
file + `current_exe()` ground truth (env demoted to advisory), the *whole
distribution directory* as the immutable unit of install/switch, content-
cheap diff-copy between instances, and a new `vibe vars` reconciliation
command. **v3 landed 2026-09-11:** source builds still hold developer/managed
checkouts by reference, while a binary release owns the exact clean source
snapshot that produced it; `vibe` + essential `vibe-index` ship and activate
together; mutable remote version labels create immutable local `#N`
generations. §9 records the resulting decisions. @status:impl/done

[p02] @fact:related **Related:** [PROP-018](PROP-018-agentic-standalone-modes.xml) (VVM is a
second *standalone-mode* citizen after `vibe skill` — pure algorithm, no
LLM; §2.1), [PROP-016](PROP-016-source-mirrors.xml) (the source mirrors VVM
clones from when run outside a source tree), [PROP-000 §7](PROP-000.xml#registry)
and [PROP-000 §20](PROP-000.xml#token-secrecy) (the publish token VVM never
touches), [`VIBEVM-SPEC.md`](../../VIBEVM-SPEC.md) (CLI-first posture), and
the repo's `rust-toolchain.toml` (the pin VVM honours when building). @status:spec/done

## 1. Motivation {#motivation}

### 1.1 The problem — vibevm cannot install itself {#problem}

- [p03] @fact:problem-assumes-binary Every other capability of vibevm assumes a `vibe` binary already exists. @status:spec/done
- @fact:problem-manual Getting it there is, today, an unspecified manual act: clone, `cargo build`,
  find the artifact, put it on `PATH`. @status:spec/done
- @fact:problem-no-story There is no story for *which* version you built, switching between versions,
  reclaiming the disk a Rust build tree eats, or doing any of it on a clean
  machine. @status:spec/done

[p04] @fact:forces-lead Two further forces shaped v2 (§9): @status:spec/done

- [p05] @fact:force-no-reload the owner iterates fast and must not have to **reload the console** after
  each `self install`/`use`; @status:spec/done
- @fact:force-multi-file and a distribution is **more than one file** (`vibe`, `vibe-index`, exact
  source, manifest and future assets), any executable member of which may lock
  while running. @status:impl/done

### 1.2 What VVM is — a self-distribution manager {#what}

- [p06] @fact:VVM-COMMAND-GROUP VVM is a command group, `vibe self`, described as
  **"VibeVM Version Manager / VVM"**. @status:spec/done
- @fact:VVM-CAPABILITIES It either builds a selected version from git or installs a verified native
  release, places the whole `vibe` + `vibe-index` distribution under a managed
  prefix, exposes both commands through stable shims, tracks the exact source
  tree, and lets the user update, switch, inspect, roll back, garbage-collect,
  and remove immutable local instances. @status:impl/done
- @fact:VVM-PLATFORMS It runs on Windows, macOS, and Linux, across the shells those platforms use. @status:spec/done

### 1.3 What this is NOT — not `vibe install` {#not-install}

[p07] @fact:req-not-install `req r1` @status:spec/done

- [p08] @fact:INSTALL-IS-PACKAGES `vibe install` is the **package** manager (PROP-003 / PROP-017): it resolves
  packages a *project* depends on into that project. @status:spec/done
- @fact:VVM-IS-TOOL VVM manages *the vibevm tool itself* on *the machine* — a user-global
  prefix, not a project's `vibedeps/`. @status:spec/done
- @fact:NO-SHARED-PATHS The two never share code paths; `vibe self` is its own command group so the
  package verbs stay uncontaminated. @status:spec/done

## 2. Decisions {#decisions}

### 2.1 VVM is a standalone, algorithmic capability {#standalone}

[p09] @fact:req-standalone `req r1` @status:spec/done

- [p10] @fact:NO-LLM-NO-AGENT VVM needs no LLM and no host agent. @status:spec/done
- @fact:STANDALONE-CLASS In PROP-018's terms it is a **standalone-mode** capability — the second
  after `vibe skill` — behaving identically with or without an agent. @status:spec/done
- @fact:FULLY-SCRIPTABLE It is fully scriptable: every interactive prompt has a non-interactive flag
  equivalent, so VVM works from a bare terminal, CI, or an agent transcript. @status:spec/done

### 2.2 Command surface — `vibe self` (+ `vibe vars`) {#surface}

[p11] @fact:req-surface `req r5` @status:spec/done

[p12] @fact:SELF-NAMING `vibe self` — named after rustup's `self` (a tool that manages its own
versions), and unambiguous where `man` collided with the Unix manual page: @status:spec/done

- [p13] @fact:CMD-INSTALL `self install <selector>` — from a source execution, build the selected
  ref; from a managed binary execution, an explicit `X.Y.Z` installs that
  native release. Flags: `--release` / `--profile <debug|release>` (source
  lane); `--mirror <gitverse|github>` (force the source lane); `--force`
  (always allocate a fresh immutable `#N`). @status:impl/done
- @fact:CMD-UPDATE `self update` follows the running origin, and moves versions FORWARD (owner
  ruling 2026-09-14, replacing the v1 rule that a binary execution refreshes its
  own number and never jumps). A source/worktree execution rebuilds its exact
  source root at `latest`. A binary execution reads the release channel's
  newest-release aggregate manifest and goes to the **newest published
  release**: newer than the running version installs and activates it by the
  same verified path an explicit `X.Y.Z` takes. The SAME version is not a no-op
  — a release may be rebuilt under its own number, so the manifest's bundle
  digest decides: changed bytes install a fresh immutable `#N`, unchanged bytes
  reuse the instance already held and cost no bundle download at all. A newest
  release OLDER than the running one (withdrawn on the far side) never walks the
  machine backwards. `--force` installs a fresh `#N` whatever the digest says.
  `self install stable` enters this same path, since `stable` IS the newest
  release (§2.3). @status:impl/done
- @fact:CMD-REINSTALL `self reinstall` refreshes the version that is RUNNING, without changing which
  version that is: a binary execution refetches its own release's manifest and
  bundle, a source/worktree execution rebuilds its exact source root — either
  way landing a fresh immutable `#N`. It takes no `--force`, because a fresh
  instance is the whole verb; `--profile` / `--release` steer the source lane
  exactly as on `update`. The pair is deliberate (owner ruling 2026-09-14):
  refetching the current version and moving to a new release are two acts, so
  they carry two names. @status:impl/done
- @fact:CMD-OFFLINE Every `self` verb honours the offline posture the process resolved —
  `--offline`, `VIBE_OFFLINE` or `[net] offline`, layered as PROP-010
  `##OFFLINE-LAYERING` says. Under it a release-lane verb is refused before its
  first request, naming the verb and the address it would have needed; a
  source-lane rebuild runs without fetching when the checkout and the crates
  are already local, and is refused the same way otherwise. The domain never
  reads the environment for this: the composition root hands the resolved
  posture in. @status:impl/done
- @fact:CMD-IMPORT `self import <PATH> --tag <X.Y.Z>` — put a legacy READY-BUILT local `vibe`
  executable into the inventory as an immutable `tag:` instance, reusing the
  store/placer/lock/state machinery (built 2026-08-20 for the 1.0.0
  distributive; the install.ps1 of the zip calls it instead of duplicating
  `state.toml`). Local by construction: no network, no signature machinery —
  the file is streamed through SHA-256 once, and that digest drives
  idempotence: same tag + same digest reuses the instance; same mutable tag +
  different digest creates a new local `#N` beside the preserved old one.
  `--replace-candidate` remains compatibility spelling. Optional `--commit`, `--profile`
  (default `release`); inactive by default — `--use` runs the same complete
  shim/PATH/pointer/advisory-HOME activation as every other lane. @status:impl/done
- @fact:CMD-BOOTSTRAP `self bootstrap --manifest DISTRIBUTIONS.json --version X.Y.Z
  --release-base URL` is the narrow machine-facing cold-start verb. It verifies
  the temporary bootstrap against the aggregate, downloads one bounded native
  bundle anonymously, verifies/extracts it, and activates it; `--force` makes
  another generation. @status:impl/done
- @fact:CMD-USE `self use <selector>` — make a version active by repointing the live
  `current` file — **no console reload** (§2.5). `--eval` prints the shell
  line for an integrity-checked shell-local override instead of touching the
  durable environment. @status:impl/done
- @fact:CMD-ROLLBACK `self rollback` atomically swaps to the immediate `previous` instance;
  repeating it toggles the two pointers without deleting either generation. @status:impl/done
- @fact:CMD-LS `self ls` (alias `list`) prints copyable `<kind>:<id>#N` selectors and
  provenance. `*` marks the active pointer; `>` independently marks the
  actually running executable, including a direct developer-worktree binary. @status:impl/done
- @fact:CMD-CURRENT-WHICH `self current` shows active and running identities separately. `self which
  [vibe|vibe-index|source]` prints that member's absolute path; `self source`
  prints exactly the running/active source root for shell composition. @status:impl/done
- @fact:CMD-REMOVE `self remove <selector>` (aliases `rm`, `del`, `uninstall`) — safe by
  default (§2.9). @status:spec/done
- @fact:CMD-GC `self gc` — reclaim disk (§2.10). @status:spec/done
- @fact:CMD-DOCTOR `self doctor` (+ `--fix`) — verify the install and environment (§2.11). @status:spec/done
- @fact:CMD-ENV `self env` — print safely quoted `VIBEVM_SHELL_HOME` + advisory
  `VIBEVM_HOME` activation for a shell. @status:impl/done
- @fact:CMD-RELOCATE `self relocate <path>` — repoint source provenance to a moved checkout and
  clear the instances built from the abandoned tree (§2.17). Flags:
  `--from <old-path>` (override the inferred old location); `-y`/`--yes`
  (non-interactive); `--dry-run`. @status:spec/done

[p14] @fact:CMD-VARS Top-level **`vibe vars`** (§2.14) prints the runtime variable context —
the values vibevm *actually* uses (derived from `current_exe`) versus what
the environment says — so scripts never break on a stale `$VIBEVM_HOME`. @status:spec/done

### 2.3 Version selectors and resolution {#selectors}

[p15] @fact:req-selectors `req r2` @status:spec/done

[p16] @fact:SELECTOR-DEF A *selector* names what to install or use; resolution is deterministic: @status:spec/done

- [p17] @fact:SEL-LATEST `latest` → tip of branch `main`. @status:spec/done
- @fact:SEL-STABLE `stable` → highest semantic-version git tag (the newest release). @status:spec/done
- @fact:SEL-SEMVER `X.Y.Z` → a tag; tries `X.Y.Z` then `vX.Y.Z`, always peeling an
  annotated tag to its exact commit before recording/building. @status:impl/done
- @fact:SEL-COMMIT a hex commit-ish → a commit. @status:spec/done
- @fact:SEL-CANONICAL the canonical `<kind>:<id>` form (as `self ls` prints) → that exact id. @status:spec/done
- @fact:SEL-INSTANCE `<kind>:<id>#N` → one exact immutable local generation; the terminal
  `#N` disambiguates repeated content under the same mutable remote label. @status:impl/done
- @fact:SEL-BARE any other bare name → branch, then tag, then commit (hex commits and
  `X.Y.Z` tags are classified before this point). @status:spec/done

- [p18] @fact:SEL-FORCE-FLAGS `--tag` / `--branch` / `--commit` force interpretation, mapping to
  fully-qualified git refs so a name that is both never resolves by accident. @status:spec/done
- @fact:SEL-DEFAULTS No selector: `install` → `latest`; `remove` opens an interactive picker (or
  refuses without a TTY); `use` requires an explicit selector. @status:impl/done

### 2.4 On-disk layout — instances, the `current` pointer, manifests {#layout}

[p19] @fact:req-layout `req r2` @status:spec/done

- [p20] @fact:INSTANCE-UNIT The **unit of install and switch is a whole distribution directory**
  (an *instance*: the binary plus future DLLs/assets), not a single file
  (§9.3). @status:spec/done
- @fact:CANONICAL-ID A version has a **canonical id** `<kind>:<id>` (`kind ∈ {tag, branch,
  commit}`); each id may have several instances (one per install). @status:spec/done

[p21]
```
$VIBEVM_INSTALL_ROOT/            install base — default: ~/.vibe
└─ opt/
   ├─ bin/                       ← on PATH; stable shims
   │   ├─ vibe, vibe-index       POSIX shims
   │   └─ vibe.cmd, vibe-index.cmd   Windows shims
   └─ vibevm/
       ├─ current                ← live pointer: the active instance dir
       ├─ previous               ← immediate rollback pointer
       ├─ state.toml             inventory: every instance + its metadata
       ├─ versions/<kind>/<id>/<instance>/   immutable distribution dirs
       │       bin/{vibe[.exe],vibe-index[.exe]}
       │       source/                 binary lane: extracted exact source tree
       │       vibevm-source.zip       binary lane: retained verification source
       │       DISTRIBUTION.json       binary lane: component/source identity
       │       .vvm-manifest.toml      source-build diff-copy inventory
       ├─ build/                 shared cargo --target-dir (gc-able)
       └─ src/.mirror/           shared managed source clone; external developer
                                 worktrees stay outside and are referenced
```

- [p22] @fact:ROOT-DEFAULT `$VIBEVM_INSTALL_ROOT` (the install BASE) defaults to `~/.vibe` → root
  `~/.vibe/opt` in normal use (owner ruling 2026-08-20, the release-1.0
  install path; until then the default base was the bare home dir → `~/opt`).
  An explicit override keeps its meaning — `<override>/opt` — and one env var
  still relocates everything; tests pin it to a temp dir. A managed binary
  keeps recognising its own root by shape (the trailing `opt` component),
  so both generations of store keep working. @status:impl/done
- @fact:INSTANCE-COUNTER `<instance>` is a monotonic counter (§9.4) — never a hash of the payload
  (§9.2). @status:spec/done
- @fact:SHIM-STABLE The shim dir is stable; switching repoints `current`, never the shim. @status:spec/done
- @fact:SOURCES-BY-REF Development/managed checkouts are held **by reference** and their untracked
  caches are never copied. A binary instance instead owns a clean tracked-file
  snapshot under its own `source/`, because shipped skills may require the
  matching vibevm source even on a machine without a checkout (§2.16). @status:impl/done

### 2.5 Activation — live `current` file + `current_exe` truth {#activation}

[p23] @fact:req-activation `req r2` @status:spec/done

[p24] @fact:ACTIVATION-LAW **Switching must not reload the console and must not overwrite a running
file.** (v1's "env is truth" violated the first; see §9.1.) The model has
four layers: @status:spec/done

1. [p25] @fact:LAYER-CURRENT-EXE **`current_exe()` → the running process's truth.** A modern managed `vibe` lives
   at `…/opt/vibevm/versions/<kind>/<id>/<instance>/bin/vibe[.exe]`
   (legacy root-level binaries remain readable), so it
   derives its own version id, `VIBEVM_HOME` (= its instance dir), and
   `VIBEVM_INSTALL_ROOT` (walk up to `opt`) from its own path — no env var
   needed. Outside a managed location (dev `cargo run`, a bare copy), it
   falls back to env, then defaults. @status:impl/done
2. @fact:LAYER-SHELL-OVERRIDE **`VIBEVM_SHELL_HOME` → explicit shell-local selection.** `self use --eval`
   and `self env` emit it together with advisory HOME; stable shims prioritize
   this non-durable override so evaluation really switches both `vibe` and
   `vibe-index` even while durable `current` names another instance. @status:impl/done
3. @fact:LAYER-CURRENT-FILE **`current` file → the live active instance.** The shim reads
   `$shimdir/../vibevm/current` on **every** launch and execs that instance.
   `self use` rewrites `current` → the **next** `vibe` in the **same shell**
   uses it. No reload (the shim reads a file, not the shell's frozen env). @status:spec/done
4. @fact:LAYER-ENV-ADVISORY **`$VIBEVM_HOME` / `$VIBEVM_INSTALL_ROOT` (env) → advisory.** Still set
   durably for external `JAVA_HOME`-style tools, but no longer the source of
   truth. They may lag (new shells only); `vibe vars` (§2.14) reconciles,
   and a managed `vibe` whose `current_exe`-derived home disagrees with the
   env prints a one-line stderr warning at startup (suppressed outside a
   managed run). @status:spec/done

- [p26] @fact:SHIMS-MINIMAL The four shims (`bin/{vibe,vibe.cmd,vibe-index,vibe-index.cmd}`) are minimal:
  use explicit shell override, else resolve `current`, then fall back to
  `$VIBEVM_HOME`; exec the corresponding member or print "no active vibevm — run
  `vibe self use <selector>`". @status:impl/done
- @fact:SHIMS-BOTH-FORMS Both POSIX and `.cmd` forms exist for both commands (Git Bash
  won't resolve `.cmd`; cmd/PowerShell won't run an extensionless script). @status:impl/done

### 2.6 PATH and durable environment management {#path}

[p27] @fact:req-path `req r2` @status:spec/done

[p28] @fact:ENV-MGMT-LEAD VVM detects OS and shell and manages durable settings under strict rules: @status:spec/done

- [p29] @fact:PATH-SHIM-DIR **The shim dir on `PATH`** (stable; set once). @status:spec/done
- @fact:ENV-ADVISORY **`VIBEVM_HOME` / `VIBEVM_INSTALL_ROOT`** as *advisory* env (§2.5) —
  repointed on `self use` for external tools; truth lives in `current` +
  `current_exe`. @status:spec/done

[p30] @fact:env-rules-lead Rules: @status:spec/done

- [p31] @fact:RULE-IDEMPOTENT **idempotent** (a marker guards the edit; no duplicate lines/entries), @status:spec/done
- @fact:RULE-NEVER-CLOBBER **never clobber** (only our entry is added; the rest of `PATH` is
  preserved), @status:spec/done
- @fact:RULE-OS-AWARE **OS/shell-aware** (Windows: raw `HKCU\Environment` — read WITHOUT
  variable expansion, the value's registry kind preserved (`REG_SZ` stays
  `REG_SZ`, `REG_EXPAND_SZ` stays `REG_EXPAND_SZ`, so a PATH carrying
  `%USERPROFILE%\…` survives untouched), entries compared normalised
  (expand+full-path+case-insensitive) for dedup while unrelated raw entries
  are preserved byte-for-byte, and a `WM_SETTINGCHANGE` broadcast after the
  write; the earlier `[Environment]` API form silently expanded references
  and lost the kind. POSIX: a marked block in the detected shell's rc —
  bash/zsh/fish/`.profile`), @status:impl/done
- @fact:RULE-CONSENT and **consent + honesty** (interactive maintenance uses confirm / `-y` /
  `self doctor --fix`; executing the native bootstrap one-liner is itself the
  explicit install consent). Output says whether PATH was already live or only
  durable for new shells. Activation writes stable PATH first, flips `current`
  second, and updates advisory `VIBEVM_HOME` last, so a failure cannot point the
  durable version variable ahead of the live selector. @status:impl/done
- @fact:RULE-INJECTABLE-SEAM The durable writer is an injectable seam so tests exercise the POSIX rc
  path in a temp file and never mutate the real machine. @status:spec/done

### 2.7 Install pipelines — source builds and verified native bundles {#build}

[p32] @fact:req-build-pipeline `req r2` @status:spec/done

[p33] @fact:PIPELINE-OVERVIEW Both lanes end identically: stage a complete `vibe` + `vibe-index`
distribution, record a fresh immutable local instance, then flip `current`.
The source lane builds from a referenced checkout; the binary lane verifies a
bounded published bundle and installs its owned clean source snapshot. @status:impl/done

- [p34] @fact:STEP-LOCATE **Locate source (§2.16).** Source builds have two referenced origins: @status:impl/done
- @fact:LOC-EXTERNAL *external* — `self install` run inside a committer's own checkout
    (outside the install root): build it **in place**, never touch its git
    state, and record its canonical absolute path as provenance so a later
    `self install <id>` can rebuild from the remembered location (a *linked
    source*). @status:spec/done
- @fact:LOC-MANAGED *managed* — a shared clone vibevm owns under `src/.mirror`: created once,
    then updated **incrementally** with `git fetch`/`checkout` (or
    `pull`; stash first if dirty), never re-cloned, so a full rebuild
    (hours, in a large future) is avoided. @status:impl/done
- @fact:STEP-RESOLVE **Resolve.** The selector (§2.3) → a concrete commit, recorded. @status:spec/done
- @fact:STEP-BUILD **Build.** `cargo build [--release] -p vibe-cli -p vibe-index` into the **shared**
  `build/` target dir (§9.3 — never the source tree's `target/`; load-
  bearing on Windows and keeps the dev tree clean), honouring
  `rust-toolchain.toml`. @status:impl/done
- @fact:STEP-VIBE-ONLY **Build the two essential binaries only.** `vibe-index` is part of VibeVM's
  own distribution because on-premises registry owners require it. The
  terminal products (vibeterm,
  vibeframe) and the GUI launchers (vibe-launcher) used to be packaged
  into the instance alongside `vibe`; they have moved to a separate
  products repo (`vibevm-term`) and now publish themselves to `PATH`
  through their own version-manager (`<product> self install`). The
  install pipeline here builds `vibe` + `vibe-index` — `vibe term` / `vibe frame`
  resolve the terminal apps through `$VIBEVM_<APP>` → the active
  instance's packaged `<app>/` (back-compat) → `PATH`, with an in-place
  fallback for `vibe tree` when no terminal app is available
  (`spec://term-common/PROP-vvm#root` is the products-repo twin). @status:impl/done
- @fact:STEP-PLACE **Place a source build by diff-copy (§2.15).** Both binaries are placed under
  `instance/bin/`; unchanged files may be hardlinked from the prior source
  instance. The distribution is placed into a
  fresh instance dir, copying only files that changed versus the previous
  instance and hardlinking the rest — so a 2 GB distribution where only
  `vibe.exe` changed costs one file copy, never a full re-copy or a payload
  hash (§9.2). If nothing changed, no new instance is made. @status:impl/done
- @fact:STEP-BINARY-BUNDLE **Install a binary bundle.** Select one of the four fixed targets from
  `DISTRIBUTIONS.json`; enforce independent size limits; verify outer bundle,
  embedded manifest, both binaries, and `vibevm-source.zip` by SHA-256; reject
  traversal, collisions, symlinks, special files, extras, and decompression
  overflow; cap source files/materialized nodes/depth/path at
  200,000/400,000/256/4096; retain the source ZIP and extract it under
  `source/`. The inventory retains the authenticated manifest digest that
  anchors later `doctor`/reuse checks. @status:impl/done
- @fact:STEP-MUTABLE-IMMUTABLE **Remote labels are mutable; local instances are not.** A republished
  `v1.0.0` may have new bytes, but it becomes `tag:1.0.0#N+1`. A non-forced
  identical bundle reuses an instance only after re-verifying its manifest,
  binaries, retained source ZIP, and extracted source file set/content. @status:impl/done
- @fact:STEP-RECORD-FLIP **Record + activate.** `state.toml` gets the instance (id, instance,
  commit, toolchain, profile, time, origin, source_path). Every activating
  lane then uses one failure-ordered transaction: publish the stable shims,
  persist their generic PATH, journal/flip `current` + `previous`, and update
  advisory `VIBEVM_HOME` last. @status:impl/done

[p35] @fact:NO-OVERWRITE-EVER Because every install writes a **new** instance dir and switching is a
pointer flip, **no in-use file is ever overwritten** — the running process
keeps its instance dir intact; no lock, no reload, for the `.exe` or any
DLL, on any OS (§9.3). @status:spec/done

### 2.8 Required toolchain — a single source of truth {#tools}

[p36] @fact:req-tools `req r2` @status:spec/done

- [p37] @fact:TOOLS-LIST A from-source build needs **git**, a **Rust toolchain** (rustc + cargo,
  stable ≥ 1.93, edition 2024 — via rustup so the pin resolves), and a
  **system linker / C toolchain** (Windows: VS Build Tools; macOS: Xcode CLT;
  Linux: `build-essential`). A native binary install needs none of those;
  its bootstrap uses only the platform download/TLS facilities. @status:impl/done
- @fact:NO-OPENSSL OpenSSL is deliberately not required (rustls). @status:spec/done
- @fact:TOOLS-TABLE-SSOT The list lives once as a `REQUIRED_TOOLS` table — `(name, min_version,
  check_command, help_url)` — read by `self doctor` (§2.11) and asserted by a
  test; it is the runnable form of "how to update the stack" (§7). @status:spec/done
- @fact:TOKEN-NOT-TOOL The publish token is **never** in this set (§2.13). @status:spec/done

### 2.9 Removal — safe by default {#remove}

[p38] @fact:req-remove `req r2` @status:spec/done

[p39] @fact:REMOVE-SAFE `self remove` never silently wipes everything: @status:spec/done

- [p40] @fact:RM-SELECTOR `self remove <selector>` — remove that version or exact `#N`.
  For binary bundles, `--bin` removes only `bin/`, `--src` only the owned
  `source/` + retained source ZIP, and the default removes the whole instance.
  A scoped removal reports the deliberately partial, non-activatable result;
  external worktrees are never deleted and shared managed-source effects are
  reported separately. @status:impl/done
- @fact:RM-PICKER `self remove` with no selector — an **interactive picker**; a non-
  interactive context errors with a hint, never a wipe. @status:spec/done
- @fact:RM-ALL `self remove --all` — every version, behind the flag **and** a re-confirm. @status:spec/done
- @fact:RM-PROTECTED The **active** version and the **running** instance are protected:
  removing an active but non-running instance needs `--force`; the actually
  running instance is never removable, even with that flag. @status:impl/done

[p41] @fact:EXTERNAL-NEVER-REMOVED External sources (committer trees) are **never** removed — VVM only forgets
their provenance record; the managed `src/.mirror` clone is VVM's to drop. @status:spec/done

### 2.10 Garbage collection — `self gc` {#gc}

[p42] @fact:req-gc `req r2` @status:spec/done

[p43] @fact:GC-LEAD `self gc` reclaims disk: @status:spec/done

- [p44] @fact:GC-BUILD `--build` — clean the shared Rust build cache (`build/`); forces a rebuild
  next install but touches no installed instance. @status:spec/done
- @fact:GC-PRUNE-OTHERS `--prune-others` — remove every instance except the active and its immediate
  rollback target (plus referenced source), behind a re-confirm. @status:impl/done

- [p45] @fact:GC-BEST-EFFORT Instances are pruned **best-effort**: a dir still locked by a running
  process is skipped and collected on a later run (on POSIX the unlink
  succeeds and the inode lives until the process exits). @status:spec/done
- @fact:GC-REFCOUNT-SAFE Hardlinked files are refcount-safe — removing one instance never corrupts
  another that shares inodes (§2.15). @status:spec/done
- @fact:GC-AUTO-PRUNE-BINARY **No install silently erases rollback history.** Binary and source
  generations remain until explicit `remove` or `gc --prune-others`; the
  immediate `previous` target survives pruning. @status:impl/done
- @fact:GC-SCOPE `self gc` operates **only** inside the install root and **never** touches
  the shared `~/.cargo` caches. @status:spec/done

### 2.11 Introspection — `doctor`, `ls`, `current`, `which`, `env` {#introspection}

[p46] @fact:req-introspection `req r2` @status:spec/done

- [p47] @fact:DOCTOR-VERIFIES `self doctor` verifies end to end: both generated stable shims have their
  expected content/mode and their directory is on `PATH`; every source-built
  active/running instance still matches its placement manifest; source origins
  have their required build tools; and a modern binary origin does **not**
  require Rust/git but must pass the authenticated manifest-rooted full
  bundle/source integrity check. Source paths must still resolve to an actual
  vibevm root. A legacy
  single-binary import is labeled partial with an update recipe but its payload
  is still verified. It exits non-zero while problems remain; `--fix` repairs
  shims/PATH with consent and recomputes the result. @status:impl/done
- @fact:LS-READS-CURRENT `self ls` / `current` / `which` read the **`current` file** for the active
  selection and `current_exe()` for the independently running selection. A
  direct worktree executable wins over cwd and over an unrelated active binary. @status:impl/done
- @fact:ENV-PRINTS `self env` prints shell-specific activation lines. @status:spec/done

### 2.12 Cold-start (bootstrap) {#bootstrap}

[p48] @fact:req-bootstrap `req r2` @status:spec/done

- [p49] @fact:COLD-START-PATH Stable endpoints provide one-line native cold start: Bash for Linux/macOS/WSL
  and PowerShell for Windows. Each bounded script downloads
  `DISTRIBUTIONS.json`, verifies a small raw `vibe` bootstrap by size/SHA-256,
  then that Rust binary verifies and atomically installs the full bundle. @status:impl/done
- @fact:BOOTSTRAP-SCRIPT-LATER The scripts are shipped release assets `install.sh` and `install.ps1` and
  are exposed by stable `https://vibevm.org/install.*` redirects. Homebrew,
  WinGet, and CMD installers remain outside this slice. @status:impl/done

### 2.13 Security and trust {#security}

[p50] @fact:req-security `req r2` @status:spec/done

[p51] @fact:ACE-ACCEPTED Building an arbitrary ref is arbitrary code execution — inherent to a build
tool the user invokes deliberately, and accepted. @status:spec/done

[p52] @fact:sec-constraints-lead Constraints: @status:spec/done

- [p53] @fact:SEC-TLS host-key (SSH) / TLS verification never disabled on clone; @status:spec/done
- @fact:SEC-TOKEN binary install/update reads public releases anonymously; the publish token is
  **never** read by VVM nor shown by `vibe vars`; @status:impl/done
- @fact:SEC-SCOPE VVM operates only inside the install root and the declared, consented
  environment edits; @status:spec/done
- @fact:SEC-STORE-PATHS every store mutation is lexically contained and rejects symlink/reparse or
  special-file ancestors; `current`, `previous`, and recovery-journal paths
  must name exact inventoried instances before they can be applied; @status:impl/done
- @fact:SEC-TREE-UNTOUCHED the committer's own source tree is **never** mutated (§2.7, §2.16). @status:spec/done

### 2.14 `vibe vars` — reconciling actual vs environment {#vars}

[p54] @fact:req-vars `req r1` @status:spec/done

- [p55] @fact:VARS-PURPOSE Scripts must know the **real** runtime context even when `$VIBEVM_HOME` is
  stale (§9.1). @status:spec/done
- @fact:VARS-CONTENT `vibe vars` prints the project's env-configurable variables —
  `VIBEVM_INSTALL_ROOT`, `VIBEVM_HOME` (whose *actual* values are derived from
  `current_exe`, §2.5), plus `VIBE_INVOKED_BY`, `VIBE_UNATTENDED`, `VIBE_LOG`
  — in `NAME=VALUE` form. The publish token is deliberately excluded. @status:spec/done

- [p56] @fact:VARS-PLAIN `vibe vars` — **actual** values, one `NAME=VALUE` per line. @status:spec/done
- @fact:VARS-DIFF `vibe vars diff` — `NAME=VALUE [ENV_VALUE]`; the bracket appears only when
  the environment differs from the actual. @status:spec/done
- @fact:VARS-FULL `vibe vars full` — two tables, `# ACTUAL` then `# ENVIRONMENT`. @status:spec/done
- @fact:VARS-FULL-DIFF `vibe vars full diff` — both tables, differing names marked
  `NAME=VALUE [*]`. @status:spec/done

[p57] @fact:VARS-SEMANTICS "actual" for the VVM vars is the `current_exe`-derived value (falling back
to env/default outside a managed run); "environment" is the raw env. A
script reads `vibe vars` and knows exactly the context it runs in. @status:spec/done

### 2.15 Distribution instances and diff-copy {#instances}

[p58] @fact:req-instances `req r2` @status:spec/done

[p59] @fact:DIFF-COPY-LAW Source-built instances use the existing diff-copy optimization. Binary
bundles take the verification-first lane: their publisher digests are checked
on download and again before an identical local instance is reused. @status:impl/done

- [p60] @fact:MANIFEST-PER-INSTANCE Each instance carries `.vvm-manifest.toml`: per dist file `(rel, size,
  mtime, hash?)`. The build dir is **persistent** (shared `--target-dir`),
  so cargo preserves the mtime of unchanged outputs across builds. @status:spec/done
- @fact:COMPARE-RULES On install, for each dist file: compare to the previous instance's
  manifest entry — by **cheap content hash for small files** (≤ a
  threshold) and by **`(size, mtime)` for large files** (stat only, never
  read). Unchanged → **hardlink** the previous instance's file into the new
  one (zero copy). Changed/new → **copy** from the build output. Hardlink
  failure (cross-volume / unsupported) → copy. @status:spec/done
- @fact:DEDUP-SKIP If **every** file is unchanged, no new instance is made — `current` stays
  ("already up to date"). `--force` always makes a fresh instance. @status:spec/done
- @fact:STAGE-ATOMIC The complete new instance is staged then atomically renamed; stable PATH is
  ensured, `current` flips, and only then advisory `VIBEVM_HOME` updates. @status:impl/done
- @fact:GC-SAFE-IMMUTABLE gc is refcount-safe (§2.10); instances are immutable after publish. @status:spec/done

- [p61] @fact:SCALES-MULTI-GB This scales to a multi-GB distribution: an 80 GB asset that did not change
  is shared by hardlink; only the changed `vibe.exe` is copied (§9.2, §9.6). @status:spec/done
- @fact:VIBETERM-PARTICIPATES The `vibeterm/` subtree (~220 MB, ~3-4 k files) participates in the same
  diff-copy: small files hashed by the ≤16 MiB rule, the Electron binary by
  `(size, mtime)`; an unchanged vibeterm is hardlinked file-by-file free, and a
  rebuild that changed nothing dedup-skips the whole instance. @status:spec/done

### 2.16 Source provenance and linked sources {#provenance}

[p62] @fact:req-provenance `req r2` @status:spec/done

[p63] @fact:PROVENANCE-LEAD A developer checkout's caches are never copied. Source builds reference a
checkout; binary bundles carry a clean tracked snapshot. Each instance records
its **origin**, source path, commit, and payload identity: @status:impl/done

- [p64] @fact:PROV-MANAGED `managed` — the VVM-owned clone at `src/.mirror` (VVM updates it via git
  and may drop it on `remove`/`gc`). @status:impl/done
- @fact:PROV-EXTERNAL `external` — a committer's own checkout, identified by its **canonical
  absolute path** (`source_path`); VVM never modifies or removes it, only
  remembers where it is. @status:spec/done
- @fact:PROV-BINARY `binary` — a prebuilt artifact. Two roads enter this origin: the legacy LOCAL
  import (`self import`, shipped 2026-08-20) hashes the supplied file itself —
  SHA-256 computed on this machine, stored as `payload_sha256`, driving
  reuse/refusal. The fetched native release path is keyed by the publisher's bundle/component/source
  digests and owns its extracted clean source under the immutable instance.
  The two must not be conflated: local import verifies
  nothing about authorship, only integrity-identity of the bytes it was
  handed. @status:impl/done

- [p65] @fact:LINKED-SOURCE The remembered `source_path` makes an external source a **linked source**:
  `self install <id>` can rebuild from the recorded location from anywhere,
  without being in the checkout and without copying it. @status:spec/done
- @fact:INSTANCE-SELF-CONTAINED Every installed binary bundle is self-contained **with** its matching source
  because packaged skills may need it. `self source` returns
  `instance/source`; for source/worktree executions it returns the canonical
  external/managed git root. A missing owned source is a diagnosed broken
  bundle, not a cwd fallback. @status:impl/done

### 2.17 Relocate — repointing provenance after a checkout move {#relocate}

[p66] @fact:req-relocate `req r2` @status:spec/done

- [p67] @fact:relocate-motivation A committer's checkout is not pinned in place: it is cloned, moved, renamed,
  re-organised on disk. When it moves, every *external* instance's remembered
  `source_path` (§2.16) goes stale — a later linked-source rebuild would miss —
  and the pile of instances built from the abandoned tree clutters `self ls`. @status:spec/done
- @fact:RELOCATE-VERB `self relocate <new-path>` is the maintenance verb for that move. @status:spec/done

- [p68] @fact:REL-VALIDATE **Validate the new location.** `<new-path>` must resolve to a real vibevm
  source tree (the `find_source_root` shape — workspace `Cargo.toml` +
  `crates/vibe-cli`); a path that is not a checkout is refused before anything
  mutates. The new path is canonicalised and `\\?\`-stripped exactly as install
  records it (§2.16), so the rewritten `source_path` matches the form every
  other record carries. @status:spec/done
- @fact:REL-INFER-OLD **Infer the old location.** With no `--from`, the old path is the source
  provenance already recorded on the installed external instances (the common
  value when one checkout moved). `--from <old-path>` states it explicitly for
  an ambiguous inventory. There is nothing to relocate when no external
  instance records a source tree — the command says so and exits, never invents
  a move. @status:spec/done
- @fact:REL-REPOINT-PRUNE **Repoint, then prune.** Two effects, in one atomic `state.toml` rewrite:
  *(a)* every external instance whose `source_path` is the old location is
  **repointed** to the new one — so linked-source rebuilds (§2.16) resolve to
  the live tree; *(b)* the **built instance directories** sourced from the old
  tree are **removed** — they are provenance-stale artifacts of the abandoned
  checkout, and their records are forgotten. @status:spec/done
- @fact:REL-ACTIVE-KEPT **Active, actually running, and immediate rollback instances are never
  deleted.** Their directories remain, their matching provenance is repointed,
  and `current`/`previous` are repaired as one journaled transaction before
  any stale instance removal. Removing a protected version is `self remove`'s
  job (§2.9). @status:impl/done
- @fact:REL-CONSENT **Consent and scriptability.** Removing instances is irreversible, so the
  default is an **interactive warning** that lists what is repointed and what
  is removed, behind a confirm. `-y`/`--yes` (or `--unattended`) skips it for
  scripts and CI; a non-TTY run without `--yes` errors rather than silently
  applying (the same contract as `self remove`/`gc`, §2.9, §2.10). `--dry-run`
  prints the plan and changes nothing. `--json` emits the plan and the applied
  result. A no-op (old already equals new) is reported, not an error. @status:spec/done

[p69] @fact:RELOCATE-SCOPE Relocate touches only `state.toml` and the install root's own `versions/`
instance dirs. It never touches a committer's source tree (external sources are
held by reference, §2.16), never the shared `build/` cache (that is `self gc`,
§2.10), and never `~/.cargo`. @status:spec/done

### 2.18 Native release production — four hosts, one verified release {#release-production}

[p70] @fact:req-release-production `req r1` @status:impl/done

- [p71] @fact:RELEASE-TARGETS The closed native matrix is
  `x86_64-pc-windows-msvc`, `x86_64-unknown-linux-musl`,
  `x86_64-apple-darwin`, and `aarch64-apple-darwin`. Each host builds only its
  own native target; no successful cross-compile is passed off as host proof. @status:impl/done
- @fact:RELEASE-HOST-SCRIPTS Four dependency-light wrappers under `distribution/release/` drive
  `cargo xtask dist build`. Construction runs without tests by default;
  `--checks`, `--tests`, and `--self-check` are explicit opt-ins, and
  `--upload` remains the manual build-plus-upload convenience. The Linux
  wrapper installs the Rust musl target through rustup and fails early with an
  actionable message when the required `musl-gcc` is absent; it never invokes
  a package manager or sudo. @status:impl/done
- @fact:RELEASE-CLEAN-SNAPSHOT The producer resolves one immutable commit OID, reads every identity/file
  from that OID, and materializes it offline from the local Git object store.
  Raw `git archive` supplies committed bytes only; a deterministic ZIP encoder
  normalizes ordering, timestamp and modes. The nested source ZIP uses STORED
  entries so its bytes have no architecture-dependent compressor input; the
  outer platform ZIP still applies DEFLATE for transport. A fail-closed
  portability gate rejects tracked paths that cannot have one identity on all
  four targets (including non-ASCII/case/reserved-name collisions), and the
  same 200,000-file / 400,000-node / 256-component / 4096-byte path limits are
  enforced before emission and extraction. Optional gates run
  in a disposable source tree and separate Cargo target cache; release
  compilation starts from a fresh expansion. The user's checkout, untracked
  files, and caches are never cleaned, reset, or copied. @status:impl/done
- @fact:RELEASE-SOURCE-ARCHIVE Every platform ZIP embeds the same byte-identical
  `vibevm-source.zip`, its source tree OID and SHA-256, plus `vibe`,
  `vibe-index`, `DISTRIBUTION.json`, `LICENSE.md`, and `README.md`. The raw
  bootstrap asset is byte-identical to the bundle's `vibe` component. @status:impl/done
- @fact:RELEASE-MUTABLE **Every version label is mutable by default.** `dist prepare --version V`
  deletes any existing draft or published release for `V`, moves or creates
  `vV` at the exact source commit, and creates one fresh draft. Four hosts may
  independently replace their ZIP/bootstrap/fragment triplets. The exact
  direct `refs/tags/vV` → commit binding is verified after prepare and again
  immediately before publication. `dist finalize`
  requires and remotely verifies all twelve platform assets, publishes
  `DISTRIBUTIONS.json` + both installers, then publishes with GitHub
  `make_latest=legacy` so rebuilding an older SemVer cannot demote a newer one. @status:impl/done
- @fact:RELEASE-SECRETS Compilation, build scripts, tests, self-check, Git, and built-binary probes
  receive no publish credential. Host wrappers disable command tracing before
  retaining an upload credential. CI builds `xtask` without credentials and
  runs that already-built trusted binary only in narrow prepare/upload/finalize
  steps carrying `GITHUB_TOKEN`. @status:impl/done

## 3. Architecture — seams and cells {#architecture}

[p72] @fact:req-architecture `req r2` @status:spec/done

[p73] @fact:SEAMS-LEAD VVM is built from testable seams so the slow, machine-mutating parts are
mockable and unit tests never clone, build, or edit the real environment: @status:spec/done

- [p74] @fact:SEAM-VERSION-STORE `VersionStore` — the install-root layout (§2.4), instances, `current`,
  `state.toml`, manifests. @status:spec/done
- @fact:SEAM-SOURCE-PROVIDER `SourceProvider` — git: resolve a selector to a commit; clone/update a
  managed source; record external provenance. @status:spec/done
- @fact:SEAM-BUILDER `Builder` — runs `cargo` for a profile/toolchain; mocked in tests. @status:spec/done
- @fact:SEAM-PLACER `Placer` — the diff-copy of a distribution into a new instance (§2.15). @status:spec/done
- @fact:SEAM-ENV-PERSISTER `EnvPersister` — the durable `PATH`/env edits (§2.6), injectable. @status:spec/done
- @fact:SEAM-BUNDLE `release_manifest` + the VVM bundle reader — strict aggregate/fragment/bundle
  wire contracts, bounded anonymous download, safe ZIP extraction, and
  byte-level reusable-instance verification. @status:impl/done
- @fact:SEAM-TOOL-DOCTOR `ToolDoctor` — the `REQUIRED_TOOLS` table and checks (§2.8). @status:spec/done
- @fact:SEAM-VARS `vars` — the actual-vs-environment resolver (§2.14), `current_exe`-aware. @status:spec/done

- [p75] @fact:RESOLVE-ORDER A managed `vibe` resolves its root/active from `current_exe` + the `current`
  file; env is the fallback. @status:spec/done
- @fact:MODULE-PLACEMENT The command lives as `cli/vvm.rs` + `cli` for
  `vibe vars`, with logic under `commands/vvm/` (split across module-grain
  files to hold the file-length budget). conform and specmap stay green. @status:spec/done

## 4. MVP scope {#mvp}

- [p76] @fact:MVP-VERBS The full verb set on all three platforms: `self install` (external in-place +
  managed clone paths, debug + release, diff-copy into instances), `self use`
  (live `current`, no reload), `self ls`/`current`/`which`, `self remove`
  (safe + `--all`), `self gc` (build cache + prune), `self doctor` (+ `--fix`),
  `self env`, `self relocate` (§2.17), `self import` (legacy local ready-built
  payload), `self bootstrap` (native bundle), `self rollback`, `self source`,
  component-aware `self which`, and `vibe vars`. @status:impl/done
- @fact:MVP-RESOLUTION-ENV Selector resolution per §2.3; durable
  PATH/advisory-env per §2.6 across Windows (cmd/PowerShell/Git Bash), macOS
  (zsh/bash), Linux (bash/zsh/fish). @status:spec/done
- @fact:MVP-DIFF-COPY diff-copy with hardlink sharing is in scope (§2.15). @status:spec/done
- @fact:MVP-LINKED-SOURCES Linked sources (§2.16) are in scope (the `source_path`
  record + rebuild-from-remembered). @status:spec/done

## 5. Out of scope (now) {#out-of-scope}

- [p77] @fact:OOS-BINARY **Retired from out-of-scope:** fetched, publisher-digest-bound native bundles
  ship through `self bootstrap`, binary `self update`, and explicit SemVer
  `self install` (§2.7, §2.12). @status:impl/done
- @fact:OOS-OFFLINE offline / vendored builds; @status:spec/done
- @fact:OOS-SIGNATURES cryptographic signature verification; @status:spec/done
- @fact:OOS-REFLINK reflink/CoW placement (hardlink is the portable choice). @status:spec/done
- @fact:oos-pointer These are §6. @status:spec/done

## 6. Far backlog {#far-backlog}

- [p78] @fact:FB-BINARY-INSTALL **Shipped, no longer backlog:** binary refresh/install is origin-directed
  rather than a separate `--binary` switch; remote labels stay mutable and
  local counter instances immutable. @status:impl/done
- @fact:FB-BOOTSTRAP-SCRIPT **Shipped, no longer backlog:** bounded Bash and PowerShell one-line
  bootstrap assets (§2.12). @status:impl/done
- @fact:FB-OFFLINE Offline builds via vendoring or a registry mirror. @status:spec/done
- @fact:FB-REFLINK Reflink/CoW placement where the filesystem supports it (§2.15). @status:spec/done
- @fact:FB-SIGNATURES Signature/provenance verification of the resolved ref. @status:spec/done

## 7. Maintenance & evolution — updating the stack {#maintenance}

[p79] @fact:req-maintenance `req r1` @status:spec/done

[p80] @fact:runnable-lead Knowledge is runnable, so updates are mechanical: @status:spec/done

- [p81] @fact:MAINT-TOOLS-TABLE the **required tools** are the `REQUIRED_TOOLS` table (§2.8, asserted by a
  test); @status:spec/done
- @fact:MAINT-PROFILE-CONST the **default profile** is one constant (§2.2); @status:spec/done
- @fact:MAINT-RUST-PIN the **Rust pin** is `rust-toolchain.toml` (read, not hard-coded); @status:spec/done
- @fact:MAINT-MIRRORS the **clone mirrors** are PROP-016's `mirrors.toml`. @status:spec/done

## 8. Acceptance {#acceptance}

[p82] @fact:req-acceptance `req r2` @status:spec/done

- [p83] @fact:ACC-FRESH-CLONE From a fresh clone, the source lane builds both essential binaries. From a
  clean machine, the Bash/PowerShell bootstrap installs the verified native
  pair plus source and durably exposes stable shims; output states whether the
  current shell already sees PATH. @status:impl/done
- @fact:ACC-USE-NO-RELOAD `self use` switches the active version and the **next** `vibe` in the
  **same shell** is the new one — no reload (`current` file). @status:spec/done
- @fact:ACC-NO-INUSE-REPLACE Reinstalling the running version replaces no in-use file (new instance +
  pointer flip); the running process is unharmed. @status:spec/done
- @fact:ACC-DIFF-COPY A source-built distribution where only one essential binary changed copies
  that file; unchanged source-build members may be hardlinked. The binary
  lane instead verifies every bounded payload member and source before reuse. @status:impl/done
- @fact:ACC-VARS `vibe vars` reports actual vs environment; `vibe vars diff`/`full`/`full
  diff` per §2.14; the publish token never appears. @status:spec/done
- @fact:ACC-REMOVE-GC-SAFE `self remove` never wipes without `--all` + reconfirm; `self gc` never
  touches `~/.cargo`; external sources are never modified or removed. @status:spec/done
- @fact:ACC-RELOCATE `self relocate <new>` repoints external `source_path` records and removes the
  stale instance dirs built from the old tree, keeping the active instance; the
  active's source is repointed, not deleted. `--dry-run` changes nothing; a
  non-TTY run without `--yes` errors. @status:spec/done
- @fact:ACC-IMPORT `self import` is inactive by default (`--use` alone activates);
  importing the same tag with the same payload SHA-256 reuses the instance and
  allocates nothing; a different payload under the same mutable tag creates a
  new `#N` and preserves the previous instance; `--replace-candidate` remains
  compatibility spelling. @status:impl/done
- @fact:ACC-BINARY-BUNDLE A native bootstrap installs both essential binaries and the matching exact
  source tree under one immutable instance; `self source` returns its absolute
  path, normal same-digest refresh is a verified no-op, `--force` allocates the
  next `#N`, and rollback restores the previous generation. @status:impl/done
- @fact:ACC-RUNNING-PROVENANCE With two source worktrees and one binary install, `self ls/current/which`
  distinguish active `*` from running `>`; a directly executed worktree
  reports its own executable/root/commit and never borrows cwd or active
  binary provenance. @status:impl/done
- @fact:ACC-RELEASE-CONTOUR Each fixed host wrapper defaults to build-only/no tests; explicit gates are
  opt-in. All four fragments must bind the same commit/tree/source archive
  before mutable release finalization; build subprocesses receive no publish
  credentials. @status:impl/done
- @fact:ACC-FLOOR-GREEN Full `self-check.sh` green; conform 0/0/0; specmap clean. @status:spec/done

## 9. Design rationale & questions explored {#rationale}

[p84] @fact:rationale-lead The decisions above were reached by working through several sharp questions;
recording them so a cold reader sees *why*, not just *what*. @status:spec/done

### 9.1 Why `current` file + `current_exe`, not `$VIBEVM_HOME` (v1) {#rationale-truth}

- [p85] @fact:V1-ENV-TRUTH v1 made `$VIBEVM_HOME` the single source of truth for the active version.
  Environment variables are inherited at process start, so a shell's
  `$VIBEVM_HOME` is frozen until the shell is reloaded — every `self use`/
  reinstall forced "open a new terminal". @status:spec/done
- @fact:FIX-LIVE-FILE The fix: the **shim reads a live `current` file** each launch (filesystem
  is live → instant switch in the same shell). @status:spec/done
- @fact:FIX-CURRENT-EXE A running `vibe` derives its own identity from **`current_exe()`** (it *is*
  the binary, so it knows its path). @status:spec/done
- @fact:ENV-DEMOTED `$VIBEVM_HOME` stays only as an advisory/compat env for external tools,
  reconciled by `vibe vars` (§2.14) and a startup divergence warning. @status:spec/done
- @fact:V1-REVERSED This reverses v1's decision deliberately; env-as-truth was the cause of the
  reload friction. @status:spec/done

### 9.2 Why not content-hash the distribution {#rationale-no-hash}

- [p86] @fact:HASH-NATURAL A natural instance key is a content hash of the built distribution
  (dedup + self-describing). @status:spec/done
- @fact:HASH-DOESNT-SCALE It does not scale: a distribution may grow to gigabytes and ship as
  binaries (merged projects), and hashing 2 GB+ on every install would be
  prohibitive. @status:spec/done
- @fact:KEY-NEVER-READS So the instance key never reads the payload (§9.4), and change detection
  for diff-copy hashes only **small** files, trusting `(size, mtime)` for
  large ones (§2.15). @status:spec/done
- @fact:BINARY-PUBLISHER-DIGEST Fetched binary artifacts are keyed by the **publisher's** digest
  (computed once at publish), verified during download/extraction, and their
  installed manifest, binaries, retained source ZIP and extracted source are
  rechecked before reuse. The local `self
  import` path is deliberately outside this rule: it has no publisher, so it
  hashes the handed file once on entry — that digest is its identity, not a
  verification of anyone's claim. @status:impl/done

### 9.3 Why whole-directory instances, not in-place file replace {#rationale-instances}

- [p87] @fact:RENAME-ASIDE-IDEA The first idea for "reinstall over the running binary" was the Windows
  *rename-aside* trick (rename the running `.exe`, write the new one;
  empirically verified to work). @status:spec/done
- @fact:ONE-FILE-LIMIT It handles one file; a distribution is many (exe + DLLs + assets), all
  locked while running. @status:spec/done
- @fact:WHOLE-DIR-UNIT So the unit of install and switch became the **whole immutable instance
  directory**: each install writes a *new* dir and switching is a pointer
  flip, so **nothing in use is ever overwritten** — no lock for any file on
  any OS, and no reload. @status:spec/done
- @fact:RENAME-ASIDE-DROPPED rename-aside was dropped as unnecessary. @status:spec/done

### 9.4 Why a monotonic counter for the instance key {#rationale-counter}

- [p88] @fact:COUNTER-KEY With content-hash rejected (§9.2), the instance key is a monotonic counter:
  always unique, O(1), independent of distribution size. @status:spec/done
- @fact:CHANGE-VIA-MANIFEST "Did anything change" is answered cheaply by the diff-copy manifest
  (§2.15), which also yields the *dedup-skip* (no new instance when every
  file is unchanged) without hashing the payload. `--force` bypasses the
  skip. @status:spec/done

### 9.5 Why explicit prune preserves rollback {#rationale-prune}

- [p89] @fact:AUTO-PRUNE-BINARY-ONLY Install never silently prunes either origin: a same-label previous payload
  is the rollback evidence for mutable releases. Disk reclamation is an
  explicit `remove` / `gc --prune-others` decision. @status:impl/done
- @fact:SOURCE-KEEPS Pruning preserves the active and immediate previous instances. Source builds
  retain referenced checkout context; binary instances retain their exact
  source snapshot until explicitly pruned. @status:impl/done

### 9.6 Why developer sources are referenced but release source is copied {#rationale-sources}

- [p90] @fact:COPY-UNTENABLE Copying a checkout into the install root is untenable — a working tree's
  cargo `target/` is already tens of GB. @status:spec/done
- @fact:MANAGED-INCREMENTAL So managed sources are git clones VVM updates incrementally, and external
  (committer) sources are referenced by their absolute path and built **in
  place**, never touched. @status:spec/done
- @fact:LINKED-BONUS This also gives *linked sources* (§2.16): rebuild from a remembered
  location without being in it. @status:spec/done
- @fact:DIST-COPIED-NOT-SOURCE A developer working tree is never copied because its ignored build/cache
  surface may be enormous. A binary release deliberately embeds only the
  committed clean tree—normalized offline from one Git OID—because skills may
  need source and no external checkout exists. Its installed `source/` is part
  of the immutable distribution, not a development clone. @status:impl/done

### 9.7 Why the binary, not the shim, emits the divergence warning {#rationale-warning}

- [p91] @fact:BINARY-EMITS-WARNING When `current` and `$VIBEVM_HOME` disagree, the warning is emitted by the
  `vibe` **binary** at startup, not by the sh/cmd shim: the binary has
  `current_exe` ground truth and the `vibe vars` formatter, and keeps the
  shims trivial. @status:spec/done
- @fact:WARNING-SUPPRESSED The warning is suppressed outside a managed run (a dev `cargo run` has no
  managed location and should not be nagged). @status:spec/done

