<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">PROP-030 — The embedded registry</title>
  <status stage="impl" state="done" comment="C 2026-07-25: the embedded registry ships wholesale; motivation and decision facts stay spec-stage; fact grain 2026-07-24"/>
  <p p="1"><fact id="self-uri" status="spec/done">`spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-030`</fact></p>
  <p p="2"><fact id="status-line" status="impl/done">**Status: IMPLEMENTED** (specified 2026-07-13; verified against the tree
2026-07-25 by the spec-actualization campaign). The embedded registry is this
repository's normal resolution mode: the origin seam (§3.2) and the
project-local rules (§3.3) are live in
[`crates/vibe-cli/src/registry.rs`](../../../crates/vibe-cli/src/registry.rs) —
the R-001 sanctioned constructor site — every §4 knob is in
`vibe install --help`, §5's CI-off gate split is standing WAL law, and
`source_kind = "embedded"` lockfiles are produced here daily.</fact></p>
  <p p="3"><fact id="depends-on" status="spec/done">**Depends on:** [PROP-002](PROP-002-decentralized-registry.xml) (registry walk,
`source_kind`), [PROP-019](../../common/PROP-019-version-manager.xml) (VVM install
records, `origin`, `source_path`), [PROP-009](../vibe-workspace/PROP-009-loading-model.xml)
(boot assembly consuming the resolution).</fact></p>
  <section id="motivation" title="1. Motivation">
    <list ordered="false" p="4">
      <item><fact id="in-tree-packages" status="spec/done">vibevm ships its own packages in-tree, under `packages/`.</fact></item>
      <item><fact id="friction" status="spec/done">When `vibe` is built
  and installed **from that source tree** (`vibe self install` / `self update`,
  recorded with `origin = "external"` and a `source_path`), those packages are
  sitting on disk right next to the binary — yet a consuming project still has to
  name them explicitly with `--registry &lt;path&gt;` on every command, or hand-write a
  `[[registry]]` block.</fact></item>
      <item><fact id="unpublished-state" status="spec/done">With the packages **not published to any network registry**
  (the current state), that friction is the *only* way to use them at all.</fact></item>
    </list>
    <p p="5"><fact id="AMBIENT-DEFAULT" status="impl/done" action="continue" actionstage="doc" audience="user">This PROP makes the in-tree `packages/` of a source-installed `vibe` an
**ambient default registry** — resolved automatically, with zero configuration
in the consuming project.</fact></p>
    <section id="audiences" title="1.1 Two audiences, opposite precedence">
      <p p="6"><fact id="central-fact-lead" status="impl/done">The central design fact — **normative, recorded here at the owner's request**:</fact></p>
      <list ordered="false" p="7">
        <item><fact id="AUDIENCE-DEVELOPER" status="impl/done">**The vibevm developer** runs a `vibe` built from a source tree and is
  *developing vibevm on vibevm*. Their embedded `packages/` is the **source of
  truth**: on a coordinate clash with a network package, **embedded wins**. You
  are testing your local edits; a published copy of the same `(group, name,
  version)` must not silently shadow them.</fact></item>
        <item><fact id="AUDIENCE-END-USER" status="impl/done">**The end user** (a future distribution of vibevm) is *consuming* vibevm.
  Any packages bundled with a distribution are a **fall-through**: declared /
  published registries win, and the bundle only fills gaps (offline defaults).</fact></item>
      </list>
      <list ordered="false" p="8">
        <item><fact id="ORIGIN-SELECTS" status="impl/done">The same mechanism, opposite precedence, selected by **who you are** — which
  `vibe` already knows from the active install's `origin`.</fact></item>
        <item><fact id="INVERSION-REASON" status="impl/done">This developer↔user
  inversion is the reason the precedence is not a single fixed rule.</fact></item>
      </list>
    </section>
  </section>
  <section id="registry" title="2. The embedded registry">
    <p p="9"><fact id="EMBEDDED-DEF" status="impl/done">An **embedded registry** is a local-directory registry ([PROP-002](PROP-002-decentralized-registry.xml#local),
the M0 `LocalRegistry` shape — `packages/&lt;group&gt;/&lt;name&gt;/&lt;version&gt;/`) that `vibe`
derives from its **own active install**, not from the consuming project:</fact></p>
    <list ordered="true" p="10">
      <item><fact id="DERIVE-READ-RECORD" status="impl/done">`vibe` reads its active VVM install record (`~/opt/vibevm/state.toml`, the
   record whose slot holds `current_exe`).</fact></item>
      <item><fact id="DERIVE-SOURCE-PATH" status="impl/done">If that record has `origin = "external"` and a `source_path`, and
   `&lt;source_path&gt;/packages` exists, that directory is the embedded registry.</fact></item>
      <item><fact id="DERIVE-INJECT" status="impl/done">It is injected into resolution for **every** project automatically. The
   project's `vibe.toml` is never read for it and never written — the default is
   ambient, carried by the `vibe` binary, not the project.</fact></item>
    </list>
    <list ordered="false" p="11">
      <item><fact id="ACTIVE-REPOINTS" status="impl/done">Because it is derived from the **active** install, `vibe self use &lt;instance&gt;`
  re-points the embedded registry at *that* instance's `source_path`.</fact></item>
      <item><fact id="PER-CHECKOUT" status="impl/done">Two source
  checkouts installed side by side each carry their own embedded registry;
  switching the active version switches the default.</fact></item>
    </list>
    <list ordered="false" p="12">
      <item><fact id="MANAGED-NO-EMBEDDED" status="impl/done">A `managed`-origin install (no source tree) has no embedded registry from a
  source path.</fact></item>
      <item><fact id="DISTRIBUTION-SEAM" status="spec/done">A future distribution that bundles packages names its bundle
  location through the same seam (§3.2), at end-user precedence.</fact></item>
    </list>
  </section>
  <section id="precedence" title="3. Precedence">
    <p p="13"><fact id="EXPLICIT-ABOVE" status="impl/done" action="continue" actionstage="doc" audience="user">Resolution keeps PROP-002's explicit-source short-circuits **above** the
embedded registry — an explicit per-dependency source or pin is always
deliberate and always wins:</fact></p>
    <fence p="14">[[override]]  &gt;  path-source  &gt;  git-source  &gt;  ⟨registry layer⟩</fence>
    <p p="15"><fact id="REGISTRY-LAYER-POSITION" status="impl/done">The embedded registry enters the **⟨registry layer⟩**, and its position there
is the developer↔user inversion:</fact></p>
    <list ordered="false" p="16">
      <item><fact id="PRECEDENCE-DEVELOPER" status="impl/done">**`origin = "external"` (developer) — embedded FIRST:**
  `embedded &gt; declared [[registry]] walk`. On a coordinate clash, the embedded
  copy wins. This is the vibevm-on-vibevm case (§1.1).</fact></item>
      <item><fact id="PRECEDENCE-END-USER" status="impl/done">**distribution / end-user — embedded LAST:**
  `declared [[registry]] walk &gt; embedded`. The bundle is a fall-through.</fact></item>
    </list>
    <p p="17"><fact id="NO-REGISTRY-LIFT" status="impl/done">If the project declares **no** `[[registry]]` at all and an embedded registry is
available, resolution uses the embedded registry instead of failing — the
`build_install_resolver` "no registry configured" bail (PROP-002) is lifted when
an embedded registry is present.</fact></p>
    <section id="knob" title="3.1 The knob">
      <list ordered="false" p="18">
        <item><fact id="KNOB-FLAGS" status="impl/done">`--prefer-embedded` / `--no-prefer-embedded` selects the position explicitly.</fact></item>
        <item><fact id="KNOB-DEFAULT" status="impl/done" action="continue" actionstage="doc" audience="user">Its **default follows the install origin**: on for `origin = "external"`, off for
  a distribution.</fact></item>
        <item><fact id="KNOB-SUPPRESS" status="impl/done" action="continue" actionstage="doc" audience="user">`--no-default-registry` (env `VIBE_NO_DEFAULT_REGISTRY=1`)
  suppresses the embedded registry entirely for a command.</fact></item>
        <item><fact id="KNOB-EXPLICIT-SHADOW" status="impl/done">An explicit
  `--registry &lt;path&gt;` still shadows everything (PROP-002 M0 exclusivity),
  unchanged.</fact></item>
      </list>
      <p p="19"><fact id="ENUM-NETWORK-DEFAULT" status="impl/done">**Enumeration reaches the network by default; two flags opt out.**</fact></p>
      <list ordered="false" p="20">
        <item><fact id="enum-precedence-vs-fetch" status="impl/done">Precedence
  (above) governs which side *wins* a coordinate and which side a package is
  *fetched* from — the fetch path is first-served (embedded-first stops at the
  embedded copy).</fact></item>
        <item><fact id="ENUM-UNION" status="impl/done" action="continue" actionstage="doc" audience="user">But **version enumeration** (the candidate set the solver picks
  from) **unions across embedded *and* declared** by default, so the solver can
  see a newer published version even for a package the embedded registry already
  carries.</fact></item>
        <item><fact id="ENUM-UNION-WHY" status="impl/done">That union is deliberate — it keeps a source developer from silently
  pinning stale versions — but it means a declared network `[[registry]]` is
  contacted (a `git ls-remote`) even when the embedded registry could answer
  alone.</fact></item>
      </list>
      <p p="21"><fact id="enum-flags-lead" status="impl/done">Two opt-in flags trade that freshness check for zero network:</fact></p>
      <list ordered="false" p="22">
        <item><fact id="FLAG-OFFLINE" status="impl/done">**`--offline`** — resolve strictly offline: the declared network walk is not
  opened at all, so the embedded registry (plus explicit `--registry` / path /
  git sources) answers alone. No git host is contacted; a coordinate absent
  locally fails **without a single network request** (and thus without any
  credential prompt). With no embedded registry and no `--registry`, the command
  bails with an actionable message rather than silently resolving nothing.</fact></item>
        <item><fact id="FLAG-EMBEDDED-SHORT-CIRCUIT" status="impl/done" action="continue" actionstage="doc" audience="user">**`--embedded-short-circuit`** — keep the declared walk available, but
  short-circuit version enumeration at the embedded registry for any coordinate
  it serves: the network is reached **only** for packages the embedded registry
  lacks. A fully-embedded dependency graph resolves with zero network access
  (no enumeration round-trip, no credential prompt), while a genuinely missing
  package is still fetched from the network. Implies embedded-first precedence;
  mutually exclusive with `--no-prefer-embedded`.</fact></item>
      </list>
      <list ordered="false" p="23">
        <item><fact id="FLAGS-NOT-DEFAULT" status="impl/done">Neither flag is the default: a bare `vibe install` still unions embedded with the
  declared walk.</fact></item>
        <item><fact id="PUBLIC-NO-PROMPT" status="impl/done">Note the interaction with PROP-002 §2.2.1 — a public
  (`auth = "none"`) registry now silences credential prompts unconditionally, so
  even the default union path never raises a login dialog for a missing public
  package; the two flags above additionally spare the network round-trip itself.</fact></item>
      </list>
    </section>
    <section id="terminology" title="3.2 Terminology — `embedded` vs `local`">
      <p p="24"><fact id="TERM-EMBEDDED" status="impl/done">**`embedded`** names packages that ship *inside vibevm itself* — the in-tree
`packages/` of a source build, or a distribution's bundle. The flag is
`--prefer-embedded`; the lock `source_kind` is `embedded` (§4).</fact></p>
      <p p="25"><fact id="TERM-LOCAL" status="impl/done">**`local`** names packages that ship *inside the current project* — the
in-tree `&lt;project_root&gt;/packages/` of any vibe project (§3.3). The flag is
`--prefer-local` / `--no-prefer-local`; the lock `source_kind` is `local`
(§4).</fact></p>
      <p p="26"><fact id="TERM-LOCAL-HISTORY" status="spec/done">Originally reserved (see §9 D2 historical text) for a broader
"user-own-repos" feature, the name now lands for the narrower
project-packages semantics first; arbitrary user-repos remain a future
expansion under a different name.</fact></p>
    </section>
    <section id="project-local" title="3.3 Project-local sources">
      <list ordered="false" p="27">
        <item><fact id="LOCAL-AUTO-OPEN" status="impl/done" action="continue" actionstage="doc" audience="user">REQ. A project carrying `&lt;project_root&gt;/packages/` (where `project_root` is
  the directory holding the project's `vibe.toml`, resolved by
  `resolve_project_root`) gets that directory auto-opened as a `LocalRegistry`
  and composed into the local-registry family alongside the vibe-embedded
  registry. No `[[registry]]` block, no `--registry &lt;path&gt;`, no
  `~/.vibe/registry.toml` machine entry needed.</fact></item>
        <item><fact id="LOCAL-ORIGIN-INDEPENDENT" status="impl/done">REQ. Project-local discovery is **independent of the running vibe's install
  origin**. Unlike vibe-embedded (§2), it works from a `cargo run`, a test
  binary, a distribution install, and a source install alike — every kind of
  `vibe` invocation that targets a project with a `packages/` directory
  discovers it. The feature exists for the downstream consumer's project, not
  for the tool.</fact></item>
        <item><fact id="LOCAL-FAMILY-ORDER" status="impl/done">REQ. The local family is ordered **project-local first**, then
  vibe-embedded, so a developer's own in-tree packages win a clash inside the
  family (the project is the source of truth for its own deps). This ordering
  is internal to the `LocalCompositeProvider`; against the declared
  `[[registry]]` walk, the family as a whole composes at the existing
  `EmbeddedPrecedence` (§3).</fact></item>
        <item><fact id="LOCAL-CI-GATE" status="impl/done">REQ. The CI-off gate (`CI` / `VIBE_NO_DEFAULT_REGISTRY`, §5) does **NOT**
  suppress project-local — it is per-project and portable (every checkout
  carries the same `packages/`), so a project-local lock is reproducible
  across machines and CI. The gate continues to suppress the vibe-embedded
  half (the machine-local one).</fact></item>
        <item><fact id="LOCAL-NO-PREFER-FLAG" status="impl/done" action="continue" actionstage="doc" audience="user">REQ. `--no-prefer-local` suppresses project-packages discovery for one
  command (use when a project's `packages/` is stale, broken, or deliberately
  bypassed). It does NOT suppress vibe-embedded — `--no-default-registry`
  remains the knob for that. `--prefer-local` is the explicit affirmation of
  the default (project-local wins the local family); mutually exclusive with
  `--no-prefer-local`.</fact></item>
        <item><fact id="LOCAL-SOURCE-KIND" status="impl/done" action="continue" actionstage="doc" audience="user">REQ. A package resolved from project-local records `source_kind = "local"`
  in `vibe.lock` (§4) — distinct from `embedded`. Unlike `embedded`, it is
  **portable** and the reproducibility guard (§5) does NOT warn on it: every
  checkout of the project resolves the same `packages/` to the same content.</fact></item>
      </list>
    </section>
  </section>
  <section id="lock" title="4. The lock">
    <p p="28"><fact id="LOCK-EMBEDDED" status="impl/done" action="continue" actionstage="doc" audience="user">A package resolved from the embedded registry records `source_kind = "embedded"`
in `vibe.lock` (a [PROP-002](PROP-002-decentralized-registry.xml) `SourceKind`
variant beside `registry` / `git` / `override` / `path`). Its `source_url` is the
`file://` path into `&lt;source_path&gt;/packages`.</fact></p>
    <p p="29"><fact id="LOCK-LOCAL" status="impl/done">A package resolved from project-local (§3.3) records `source_kind = "local"`
(another `SourceKind` variant). Its `source_url` is the `file://` path into
`&lt;project_root&gt;/packages`.</fact></p>
    <list ordered="false" p="30">
      <item><fact id="LOCK-GUARD-KEY" status="impl/done">`source_kind = "embedded"` is the marker the reproducibility guard keys on (§5):
  it says "this entry resolved from a machine-local, source-install-derived
  directory," which a different machine — a teammate, CI — cannot reproduce.</fact></item>
      <item><fact id="LOCK-LOCAL-PORTABLE" status="impl/done">`source_kind = "local"` is portable (per-project, §3.3) and the guard does NOT
  key on it.</fact></item>
    </list>
  </section>
  <section id="guard" title="5. Reproducibility guard">
    <list ordered="false" p="31">
      <item><fact id="machine-local-problem" status="impl/done">A `file://&lt;source_path&gt;/packages/...` entry (the vibe-embedded registry, §2) is
  **machine-local**: a checkout on another box, or CI, has no such path.</fact></item>
      <item><fact id="unguarded-risk" status="impl/done">Left
  unguarded, an embedded-resolved lock committed to a shared repo breaks for
  everyone else.</fact></item>
    </list>
    <p p="32"><fact id="guard-strength-lead" status="impl/done">The guard, at the **warn + CI-off** strength the owner chose:</fact></p>
    <list ordered="false" p="33">
      <item><fact id="GUARD-CI-OFF" status="impl/done" action="continue" actionstage="doc" audience="user">**CI-off.** In `--frozen` (and any non-interactive CI resolution), the
  vibe-embedded registry is **disabled by default** — CI must resolve from
  declared registries (and, since §3.3, project-local), so a machine-local lock
  cannot silently pass there. Project-local is NOT suppressed by this gate — it
  is per-project and portable.</fact></item>
      <item><fact id="GUARD-WARN" status="impl/done" action="continue" actionstage="doc" audience="user">**Warn.** `vibe check` **warns** (does not fail) when the lock carries any
  `source_kind = "embedded"` entry: "this lockfile depends on the embedded
  registry of a source install and is not portable; publish or vendor these
  packages before sharing the lock." A `source_kind = "local"` entry is
  portable and does NOT warn.</fact></item>
    </list>
    <list ordered="false" p="34">
      <item><fact id="GUARD-SCOPE" status="impl/done">This keeps the embedded-registry convenience strictly a **developer-machine**
  affordance and stops a non-portable lock from leaking into a shared commit
  unnoticed.</fact></item>
      <item><fact id="LOCAL-PORTABLE-COUNTERPART" status="impl/done">Project-local (§3.3) is the *portable* counterpart — it has the same
  convenience without the portability caveat.</fact></item>
    </list>
  </section>
  <section id="doctor" title="6. Discoverability">
    <p p="35"><fact id="AMBIENT-VISIBLE" status="impl/done">The embedded registry is ambient but never silent:</fact></p>
    <list ordered="false" p="36">
      <item><fact id="DOCTOR-REPORTS" status="impl/done">`vibe doctor` reports it: `embedded registry: &lt;source_path&gt;/packages (active
  install #&lt;n&gt;, origin external, precedence first)` — or `none` for a
  distribution / managed install.</fact></item>
      <item><fact id="RESOLUTION-NAMES" status="impl/done">Resolution output names the source when a package resolves from it ("resolved
  `org.vibevm.world/wal` from the embedded registry").</fact></item>
    </list>
  </section>
  <section id="impl" title="7. Implementation hooks">
    <p p="37"><fact id="impl-hooks-lead" status="impl/done">Grounded in the current tree:</fact></p>
    <list ordered="false" p="38">
      <item><fact id="HOOK-ORIGIN-STORE" status="impl/done">**Origin + source path:** `~/opt/vibevm/state.toml` already records each
  install's `origin` and `source_path`; the active record is `store.active()`
  (`crates/vibe-cli/src/commands/vvm/mod.rs`). Reuse that store to discover the
  embedded path.</fact></item>
      <item><fact id="HOOK-INJECTION" status="impl/done">**Injection point:** `build_install_resolver`
  (`crates/vibe-cli/src/commands/install/resolver.rs`). After the explicit
  `--registry` branch, resolve the embedded registry (unless suppressed) and
  compose it with the declared `MultiRegistryResolver` at the origin-selected
  precedence; lift the empty-`[[registry]]` bail when embedded is present.</fact></item>
      <item><fact id="HOOK-LOCK" status="impl/done">**Lock:** add `SourceKind::Embedded`
  (`crates/vibe-core/src/manifest/lockfile.rs`); tag embedded-resolved entries.</fact></item>
      <item><fact id="HOOK-GUARD" status="impl/done">**Guard:** thread a `frozen` flag into resolver construction to drop the
  embedded registry in CI; add the `vibe check` warning on `Embedded` lock
  entries.</fact></item>
      <item><fact id="HOOK-SHARED-RESOLVER" status="impl/done">The same composition serves `vibe install`, `update`, and `outdated` (they
  share `MultiRegistryResolver::open`).</fact></item>
    </list>
  </section>
  <section id="edges" title="8. Edge cases">
    <list ordered="false" p="39">
      <item><fact id="EDGE-SOURCE-DELETED" status="impl/done">**Source tree deleted / moved:** `&lt;source_path&gt;/packages` missing → the
  embedded registry is skipped with a `vibe doctor` warning; resolution falls
  back to declared registries.</fact></item>
      <item><fact id="EDGE-NON-VIBEVM" status="impl/done">**Non-vibevm packages:** the embedded registry only answers for coordinates it
  actually contains; anything else falls through to declared registries as
  usual, regardless of precedence.</fact></item>
      <item><fact id="EDGE-HOST-SELF" status="impl/done">**The host vibevm project itself:** with an embedded registry active, the host
  can drop `--registry packages/` from its own dev workflow — its own `packages/`
  becomes the embedded default (full self-dogfood).</fact></item>
    </list>
  </section>
  <section id="decisions" title="9. Decisions">
    <list ordered="false" p="40">
      <item><fact id="D1-ORIGIN-SELECTED" status="spec/done">**D1 — precedence is origin-selected, not fixed.** *Rejected:* one global
  precedence. It cannot serve both the developer (embedded must win, to test
  local edits) and the end user (bundle must not shadow published packages). The
  install `origin` already encodes which case applies.</fact></item>
      <item><fact id="D2-EMBEDDED-NAME" status="spec/done">**D2 — `embedded`, not `local`, in name and `source_kind`.** *Rejected:*
  `--prefer-local` / `source_kind = "local"`. "Local" is reserved for a future
  user-owned-repository feature (§3.2); using it here would collide.
  **D2 revised (§3.3 amendment):** the reserved `--prefer-local` /
  `source_kind = "local"` name now **lands for the narrower project-packages
  semantics** — a project's own in-tree `&lt;project_root&gt;/packages/`. The
  narrower scope (a single well-known directory per project, portable across
  checkouts) is distinct from the original "arbitrary user-owned-repos"
  concern, which remains a future expansion under a different name. The
  reservation was the right call to avoid colliding the two; the §3.3
  feature takes the name now that the narrow semantics is shipped and the
  broader one is still unscoped.</fact></item>
      <item><fact id="D3-EXPLICIT-ABOVE" status="spec/done">**D3 — explicit sources stay above embedded.** *Rejected:* embedded above
  overrides / path / git. Those are deliberate per-dependency choices; an ambient
  default must not override a decision the author wrote down.</fact></item>
      <item><fact id="D4-WARN-CI-OFF" status="spec/done">**D4 — warn + CI-off, not hard-error.** *Rejected:* hard-error in CI on an
  embedded lock. The owner chose the lighter guard: CI simply does not use the
  embedded registry (so a machine-local lock fails to *resolve* there rather than
  being *rejected*), and `vibe check` warns the developer. Revisit if leaks
  recur.</fact></item>
      <item><fact id="D5-AMBIENT-VISIBLE" status="spec/done">**D5 — ambient but visible.** *Rejected:* a purely silent default. `vibe
  doctor` and resolution output always surface the embedded registry, so the
  behaviour is discoverable, not magic.</fact></item>
    </list>
  </section>
</spec>
