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

PROP-021 — Submodule sources

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 git backend clones with --recurse-submodules and runs submodule update --init --recursive, snapshot embedding lands in git_package_registry/fetch.rs, the in-place native form rides the PROP-022 machinery, and resolved_commit carries lockfile reproducibility. One of four orthogonal specs from the bridge-packages design (siblings: PROP-020 install hooks, PROP-022 materialization modes, PROP-023 bridge packages). Submodules serve any package that wants to embed another repository — not only bridges.

02Related: PROP-001 (the git backend whose clone gains --recurse-submodules), PROP-002 (one git repo = one package — a submodule is not a second package), PROP-010 (the live-git cache the submodule is fetched into), PROP-022 §2 (how submodule content reaches the slot differs by mode), PROP-003 (the dependency grammar the dependency-declared form extends).

1. Motivation

1.1 The problem — embedded repos do not come along

  • 03A package may want to carry another repository inside it. Git offers two ways: a submodule (a reference — .gitmodules + a pinned gitlink commit) or a vendored copy ("git in git" — the foreign tree committed into the package).
  • The vendored case already works: it is just files in the package tree.
  • The submodule case does not — vibevm's clone is a bare git clone --branch <ref> (PROP-001) with no --recurse-submodules, and the .git-stripping materialise copies the empty submodule stub. The referenced content silently never arrives.
  • 04The forcing case is bridge packages (PROP-023), where a maintainer submodules the upstream repo they steward.
  • But the mechanism is general: any package may legitimately embed a pinned dependency repo.

1.2 What this is — submodule as a first-class embedded source

05vibevm fetches a package's submodules when it fetches the package, updates them when it updates the package, and makes the submodule content available wherever the package is materialised — embedded into the snapshot for the copy-based modes, or living natively for in-place (PROP-022).

2. Decisions

2.1 Fetch and update recurse into submodules

06req r1

07The git backend's bootstrap and update recurse:

  • 08Bootstrapgit clone --recurse-submodules --branch <ref> -- <url> <dest>. The clone lands in the live-git cache (PROP-010) with submodule working trees populated.
  • Update — after the existing fetch --prune --tags + reset --hard <ref>, run git submodule update --init --recursive so the gitlink commits the new superproject ref points at are checked out. (A removed submodule is pruned by the reset; a moved one re-inits.)

09This applies identically to the registry cache clone and to an in-place slot clone (PROP-022 §2.4).

10Publication boundary. Workspace/direct Git source acquisition may carry real submodules, but a per-package registry repository is a self-contained package snapshot. Publish inspects each populated gitlink before copying, requires its checkout to match the indexed commit and be clean, copies its files as ordinary payload, drops .git and .gitmodules, and reports the path plus exact vendored commit. Registry consumers therefore fetch no hidden second repository; resolved_commit identifies the published package repo, while the publication report preserves the flattened upstream evidence.

2.2 Embedded sources — git-native and dependency-declared

11req r1

12A submodule is modelled as an embedded source: content that lives at a subpath of the package and is resolved from elsewhere. There are two declaration forms:

  • 13git-native (.gitmodules) — vibevm reads no .gitmodules itself; git does, via §2.1.
  • dependency-declared — a package's manifest uses [[embedded_source]] to name an upstream Git source independently of the package repository. The declaration carries a portable name, public credential-free HTTPS URL, full immutable commit, expected sha256: source-tree hash, optional ref hint, and upstream licence provenance. This supports snapshot/binary-distributed bridge packages without copying the upstream tree into their repository.

14Either way, the embedded repo is not a second vibevm package: it is git content, never entered into the dependency resolver (PROP-002: one git repo = one package; the submodule is part of this package's content, not a node).

  • 15A dependency-declared source is authenticated twice: its resolved checkout must equal the declared commit and its deterministic source-tree hash must equal content_hash. ref_hint is diagnostic only and never selects bytes.
  • Authenticated trees live in an accretive content-addressed directory below the user's Vibe settings cache. A complete matching entry is reused offline; a cache miss is fetched into a sibling temporary directory and published by atomic rename only after both checks pass.
  • The owning package remains an ordinary physical vibedeps slot containing only its published payload. Vibe loaders resolve an explicit embedded-source qualifier through a root-aware view backed by the authenticated cache; there is no fallback from a missing package path into upstream. Generic tools may request the verified physical source root, but the absolute machine path is never persisted in the lock, slot record, index or generated artifact.
  • Fetch does not recurse into submodules and projection refuses gitlinks and symbolic links. An embedded source's own vibevm manifest, lifecycle hooks and dependency declarations are inert data unless a bridge explicitly selects an ordinary file from them.
  • The canonical cache tree is read-only input in policy. A mechanism that may write to its source receives a disposable copied/reflinked worktree; it never receives a hardlink or writable handle into the canonical cache.

2.3 Snapshot materialisation embeds the submodule content

16req r1

17How submodule content reaches the slot depends on the materialization mode (PROP-022):

  • 18snapshot / hardlink — the submodule's checked-out working tree is copied into the slot as ordinary files; nested .git directories and gitlink pointers are stripped (the same exclusion the top-level .git already gets). The submodule content is thus vendored into the snapshot and participates in the package content_hash.
  • in-place — nothing is copied; the submodule lives natively inside the slot's own git checkout, managed by git (§2.1).

2.4 The lockfile pins submodule state via the superproject commit

19req r1

  • 20Reproducibility rides on the package's resolved_commit already recorded in the lockfile: a superproject commit fixes the exact gitlink commit of every submodule, so a re-clone at resolved_commit with --recurse-submodules reconstructs byte-identical submodule content. No new lockfile field is required for the git-native form.
  • Explicit per-submodule pins are a possible future refinement, tied to the dependency-declared form of §2.2.

3. Rejected alternatives

  • 21Resolving a submodule as a vibevm package through the depsolver — rejected: it is git content under one package, not a registry node; treating it as a node would double-count identity and break PROP-002's one-repo / one-package rule.
  • Shallow submodule clones (--depth 1) by default — rejected as the default: a shallow submodule can miss the exact gitlink commit and fail checkout; depth control is a possible opt-in, not the baseline.
  • vibevm parsing .gitmodules itself — rejected: git already resolves submodules correctly across auth and nesting; reimplementing it would be a fragile re-do of solved work.

4. Out of scope

  • 22Treating a dependency-declared embedded source as a normal package dependency — excluded by §2.2; it has content identity and provenance but no package coordinate and no resolver edges.
  • Non-Git external source kinds — the v1 declaration accepts only Git. An archive/OCI source needs its own immutable identity and extraction rules before it can join the same abstraction.
  • Recursive vibevm resolution inside a submodule — a submodule's own vibe.toml, if any, is not honoured; the submodule is opaque content.

5. Acceptance

  • 23A package whose repo declares a submodule is cloned with its submodule working tree populated; update re-checks-out submodule content for the new superproject ref.
  • Under snapshot/hardlink, submodule content appears in the slot as plain files with no nested .git; it contributes to content_hash.
  • Under in-place, the submodule lives natively in the slot's git checkout.
  • Re-cloning at the lockfile's resolved_commit reconstructs identical submodule content with no extra lockfile field.
  • A vendored ("git in git") package needs none of this — it is plain files and installs unchanged.
  • A package distributed without .git can declare an exact external Git tree; a successful install records its source pin separately in vibe.lock, and a subsequent projection succeeds from the cache without contacting upstream.
  • 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-registry/PROP-021-submodule-sources

.md.xmlllms.txt