Registries and the index
01Packages are published to a place vibe knows how to read: by default a public organisation on GitHub, one repository per package. A project lists such places in the order it trusts them. A catalogue beside each one answers searches without cloning anything.
vibe registry list --path hello-vibe
No `[[registry]]` entries in `vibe.toml`. Use `--registry <path>` on `vibe install` for a local-directory source, or add a `[[registry]]` block.
What a registry is
03A registry is not a server vibe runs. It is a hosting organisation, such as https://github.com/vibespecs, where every package is its own git repository named after the package's coordinate. Publishing means pushing a repository and tagging a version; installing means cloning at a tag. Who may publish is decided by the hosting service's own permissions, so vibe carries no accounts of its own.
04 Each package is its own git repository — no monorepo. Per-package maintainer permissions are hosting-native (a package repo's owner controls access); no central merge queue.
05A project declares its registries in the manifest as an ordered list. Each entry has a local name, the organisation's root address, and a naming convention that maps a coordinate to a repository name. When a package is requested, vibe walks the list in order and the first registry that has a matching version wins.
06 Resolution: the solver iterates registries in array order; the first that has a satisfying match for a pkgref wins. Versions of the same pkgref are not unioned across registries — this prevents a lower-trust registry from influencing resolve when a higher-trust one already has a valid answer.
07The list is an array in priority order; beside it a mirror is a second address for the same registry, and an override bypasses the walk for one coordinate. An entry's url is the organisation's root, never a package repository, and it is a plain git address, https://, ssh://, git@host: or file://, with no shorthand for any particular host. Its naming says how a coordinate becomes a repository name; the default joins the group and the name with a dot.
08[[registry]]is an array, priority-ordered.[[mirror]]is a first-class fallback layer, transparent to the lockfile.[[override]]bypasses the resolver for pins. Schema and code path support all three from day one.
09
url — organization root URL, not a package repo URL. A registry is a hosting-org; packages are children of it.
10 URL syntax is just git URL —git@host:…,ssh://,https://,file://. Nogithub:/gitverse:shorthands. New hosts "just work" as long asgitspeaks to them.
11naming— convention for mapping a pkgref to a package repo name under this org. Values:"fqdn"(default —org.vibevm.world/wal→<org>/org.vibevm.world.wal; introduced and made the default by PROP-008 §2.5, shipped M1.19 as a_-joined form and re-ruled to the dot join 2026-08-13),"kind-name"(legacy —flow:wal→<org>/flow-wal; the default this section originally declared, superseded by PROP-008),"name"(if name collisions are impossible in a given registry),"kind/name"(for hosts supporting nested repos). Other registries may ship with different conventions; the setting is per-registry, not global.
12The walk moves on to the next registry only when one answers that it does not have the package. A connection failure, a server error or a malformed manifest stops the install with that error, because an outage or a typo is something you want to know about. On a registry declared public, a demand for credentials counts as «not here» and the walk continues; on a registry that declares an authentication regime, it is a real failure.
13 A[[registry]]is a distinct package source — its own naming convention, its own publishing identity, its own trust scope. The priority-ordered registry walk falls through onUnknownPackageonly: a registry that confidently answers "I don't have this package" is free to defer to the next one. Any other primary failure — connect-failure (DNS / TCP), auth-failure on a registry that explicitly requires authentication, server error, malformed manifest — halts the install with an actionable error. This is the same policy Cargo and npm apply to a registry that errors out: the operator wants to know about a typo or an outage, not paper over it with a different registry that may carry a different version.
14auth-aware 401 classification (§2.2.1). Onauth = "none"a 401 / 403 response is anUnknownPackagesignal, not an auth-failure: the registry is declared public, anything that responds with "you cannot read this without credentials" is — from this consumer's standpoint — equivalent to "this package does not have a public answer here." The walk falls through to the next registry, exactly like a 404. This is what unblocks the common case where one host (GitVerse) returns 401 for a missing repo while another (GitHub) returns 404 — the resolver treats both uniformly. Onauth = "token-env"or"credential-helper"a 401 is a realAuthFailedand halts: the registry was declared as authenticated, the credentials presented were rejected, this is information the operator must see.
15An entry can be switched off without being deleted: enabled = false makes every command skip that registry until you flip it back.
16 Decision. Every[[registry]]carries anenabledflag, defaulttrue. Settingenabled = falseswitches a registry off without deleting its entry — it is skipped by every resolution path (install/outdated/search/registry sync/vendor), because the filter lives at the one resolver-construction point (MultiRegistryResolver::from_manifest): a disabled registry is never built, so nothing downstream can consult it. Re-enable by flipping the flag back; no re-add. The defaulttrueis skipped on serialize, so only an explicitenabled = falseappears in a writtenvibe.toml. The flag applies uniformly to a projectvibe.tomland the machine-global~/.vibe/registry.toml.
17A machine may add registries of its own in ~/.vibe/registry.toml, merged after the project's; a project's list always wins over the machine's. This is how a company points every project on a laptop at its private registry without editing each project. The file carries the same [[registry]], [[mirror]] and [[override]] sections as a project manifest, for any registry, remote or local; a name declared in both places is the project's.
18 Project-level[[registry]]always overrides the user-level default — the same precedence theUserConfig[env]layer already follows (the project / live value wins).
19 Decision. Registry settings may also live in a per-user file resolved through the settings chokepoint (vibe_core::settings::registry_config_path→~/.vibe/registry.toml, or$VIBE_SETTINGS/registry.toml). It carries the same[[registry]]/[[mirror]]/[[override]]sections as a projectvibe.toml— any registry, not only local ones: a remotehttps:///ssh:///git@org (withauth) is merged and searched exactly like afile:/// path repo. A common motivation is keeping machine-local registries (afile://checkout, a path repo) out of a team-sharedvibe.toml, where a hard-coded local path would differ per teammate; but a whole extra remote registry can be added machine-wide the same way. (Locality matters only to--offline, §2.2.2.1.)
20 Merge — project first, dedupe by name. The effective registry list is the project's[[registry]]entries followed by the global file's, with anamecollision resolved in the project's favour (the project entry wins; the global one is dropped). Mirrors are concatenated (project first). Overrides are project-first, deduped bypkgref(project wins). The merge is a pure function (vibe_core::merge_effective), verified in isolation. A project's explicit declaration always outranks a machine default, so a sharedvibe.tomlstays authoritative for the team while each machine supplements it with its own local repositories.
21The default trust set the specification names is exactly two roots, https://github.com/vibespecs and https://gitverse.ru/vibespecs. A project vibe init creates today carries no registry block at all, so before the first install add one with vibe registry add, or let the machine-wide file supply it. Every other registry is trusted only because you added it.
22 The default trust set is exactly two roots —https://github.com/vibespecsandhttps://gitverse.ru/vibespecs— trusted by default as the registriesvibe initwrites. Every other registry is trusted only by the user's own act of adding it to their configuration (owner ruling, 2026-08-13).
23 Default in new projects.vibe initwrites the default registry URL (DEFAULT_REGISTRY_URLinvibe_core::manifest) into every newvibe.toml's[[registry]]entry unless the operator passes--no-registryor overrides with--registry-url <URL>/--registry-ref <REF>.
The index
24Cloning a repository to learn what is in it is slow, and listing an organisation to search it is impossible without an account. So a registry may keep an index: a separate repository beside the packages that records, for every published version, the manifest's summary and the content fingerprint. vibe search reads the index; a fresh install reads it to skip a round of clones.
25
Decision. The index layer is strictly additive. Every existing vibevm code path keeps working exactly as today when no index is present. No registry is required to have an index. No project is required to consume one; a consumer that finds none falls back to the live git ls-remote path that exists today.
26The index is a cache, never the truth. If it disagrees with a package repository, the repository wins, and a package resolved through the index is still verified against its content fingerprint when it arrives. A registry without an index works exactly as before, only slower; a missing index is not an error.
27 Decision. Package repositories are the source of truth for content (manifests, files, tags). The index is a derived hot cache, regeneratable from the authoritative package state.
28 This matters because it disambiguates the failure mode: if the index disagrees with reality, reality wins.
29The index location is derived from the registry's address and can be overridden per registry with an environment variable named after the registry, VIBEVM_INDEX_URL_<NAME>; the literal value none switches index lookups off for that registry.
30 The environment variableVIBEVM_INDEX_URL_<REGISTRY>is the ladder's top rung — the operator's per-run re-point, no longer the only source. Until 2026-08-20 it was the sole locator, deliberately weaker than the manifest field it stood in for (per-shell, per-run, travelling with neither project nor lockfile) — which is why it never closed the requirement above. Now it overrides the key: env beatsindex_urlbeats the default, andnoneat either explicit rung disables the index. The name normalization (ASCII alphanumerics upper-cased, the rest to_``) is unchanged.
31A registry entry may pin its index with index_url; without one, a public GitHub organisation maps to its index repository on the raw content host and any other host to <registry-url>/index. A probe of the index has three outcomes: found, absent, or refused. Only absent falls through quietly to a live listing, because nobody promised an index; an index that is there and cannot be read is reported. vibe search asks each configured index the question directly rather than downloading the whole catalogue.
32 Configurable but defaulted. A[[registry]]block pins a custom index location — the key exists inRegistrySection(one type serving both the projectvibe.tomland the machine-global~/.vibe/registry.toml, so the columns share one vocabulary), and this exact block parses (pinned by testprop005_index_url_example_parses, which carries it verbatim):
33 The bottom rung is host-aware. Canonical publichttps://github.com/<org>maps tohttps://raw.githubusercontent.com/<org>/index/<registry-ref>; other hosts retain<registry-url>/index. This makes both fresh and already- seeded GitHub configurations whoseindex_urlfield is absent consume the static repository rather than its HTML page. The full ladder remains env override → manifest key → host-aware default; exactnoneon either explicit rung disables lookup. Lookalike hosts, nested paths, userinfo, unsafe owners/refs, queries and fragments are never rewritten.
34 A probe answersfound,absent, orrefused, and the third is what keeps this section honest. «Absent» is the only outcome that falls through quietly, because it is the one that means what the fall-through assumes: nothing is published here. An index that IS there and cannot serve this consumer — it refused us (401/403), its body does not parse as a handshake, its handshake format is one this build does not read, or it publishes no world of this build's epoch — answersrefused, carrying the offered epochs, this build's epoch, a recipe, and whatever the document said inmin_client/notice/successor. Collapsing that into «absent» would make a private, broken or newer-than-us index indistinguishable from a missing one, which is the silence PROP-044 §2 forbids: a break that announces itself is normal life, a riddle is what strands users.
35 404 / connect-failure on the index → silent fallback to livels-remote: no error message, because the operator never promised an index. This half is built, and it is theabsentoutcome of##A-PROBE-HAS-THREE-OUTCOMES-NOT-TWO— the other two outcomes are never silent.
36 Walks every configured registry's index through the same client the resolver uses — probe, then query — rather than downloadingprimary.jsonl.gzand scanning it locally. The whole-file scan was the shape this document first imagined and is not what shipped: asking the index a question keeps the bandwidth proportional to the answer instead of to the catalog, and it puts one discovery ladder (§2.1) under every consumer instead of two. Index is the enabling layer for M2.10;vibe searchis the headline consumer of this PROP.
Mirrors, overrides and git sources
37A mirror is another address for the same registry, tried first for availability and verified against the same fingerprints; a mirror that serves different bytes for a known version is refused, not trusted. Mirrors never appear in the lock file: the canonical address is what gets recorded, so switching mirrors changes nothing for your teammates.
38 Mirror integrity verification is mandatory, not optional. A mirror whosecontent_hashfor(kind, name, version)differs from the lockfile pin fails the install with an actionable error. This closes the supply-chain hole where a hijacked mirror could substitute content.
39An override replaces one package with a copy from elsewhere, for a hotfix or a patch waiting upstream; it short-circuits the registry walk for that one coordinate and is marked as such in the lock file, so nobody mistakes it for a published version.
40
Decision. [[override]] bypasses the registry layer for a named pkgref:
41An override relaxes nothing: the copy's fingerprint is still pinned in the lock file and verified on every install, and the entry carries overridden = true.
42 The resolver short-circuits: it does not consult[[registry]]for this pkgref at all; it fetches directly from the given URL at the given ref. Content hash is still pinned in the lockfile and verified on each install — an override does not relax integrity. The lockfile recordsoverridden = trueon that entry. Avibe list --overridesflag is specified here and not shipped — the lockfile field is the only surface today.
43A dependency may also point straight at a git repository, at a tag, a commit or a branch. A tag and a commit are pinned; a branch is re-walked on update, and the lock file records the commit that was actually installed.
44 Mutable branch. Lockfile records the resolved commit at install time; subsequentvibe updatere-walks branch HEAD. Mutable — see "Mutability andvibe update" below.
45 Decision. A dependency may be declared as a first-class git-source in[requires.packages]— fetching the package from an arbitrary git repository instead of resolving it through[[registry]]. This is the vibevm analogue of Cargo's[dependencies] foo = { git = "..." }, npm's"foo": "git+https://...", Poetry'sfoo = { git = "..." }, Bundler'sgem 'foo', git: '...', Go modules' baseline behaviour. The use cases are:
46A git source is written as an inline table on the requirement, with a git address and exactly one of tag, rev or branch; none or two is refused, because guessing a default branch is not acceptable on a surface that decides what code enters your project. The source's auth is declared on the source itself, never borrowed from a registry on the same host. When the repository arrives, vibe reads the package's own manifest and refuses one whose kind and name differ from what you required.
47 Wire form.[requires.packages]becomes a TOML table whose values are either a version-constraint string (registry-resolved, the M1.13 shape) or an inline-table (registry-resolved with options, or git-source). The legacy array-of-strings shape (packages = ["flow:wal@^0.3"]) parses transparently into table-form on read; on round-trip the manifest writes table-form.
48 Exactly one oftag/rev/branchmust be present in a git-source declaration. Zero is rejected at parse time withMissingRef. Two or more rejected withConflictingRefs. There is no "default branch HEAD" fall-back — too magical for a security-sensitive surface; explicit > implicit.
49 Auth. Per-sourceauthis explicit, not host-derived. The resolver does not look at[[registry]] authfor the same host and apply it transitively to a git-source pointing at that host — too magical, creates implicit ordering dependencies between sections of the manifest. If a project has multiple packages from the same private host, the operator can either:
50 The pkgref<kind>:<name>is read from the package'svibe-package.toml[package]section on the resolved git ref (same path as registry-resolved manifest fetch viagit archive). The resolver verifies that the(kind, name)declared in[requires.packages]matches what the repo actually carries; mismatch =PackageIdentityMismatch. This means a malicious git-source cannot impersonateflow:walif itsvibe-package.tomldeclares it asfeat:auth.
51The source of a requirement is decided in a fixed order: an override first, then a git source declared on the requirement, then the registry walk. A branch is followed only by vibe update; vibe install keeps the commit the lock recorded.
52 Resolution order. When the resolver looks up a pkgref, the source is decided in this order:
53 Mutability andvibe update. Tags and revs are immutable by definition; force-push is detected via content-hash. Branches are explicitly mutable:vibe installagainst a branch resolves to the current branch HEAD and pins that commit in the lockfile.vibe updatere-walks each branch-declared git-source, and if HEAD has moved, re-resolves and re-locks.vibe install(no flag) does not chase a branch's HEAD on subsequent runs — the lockfile'sresolved_commitis authoritative untilupdateis called. This matches Cargo's behaviour (cargo builddoes not bump branch deps;cargo updatedoes).
Authentication
54A public registry needs no credentials, and vibe sends none: it silences git's credential helpers so an install in a script never hangs on a password prompt. A private registry declares its regime in the manifest: a token read from an environment variable, the system's credential helper, or SSH keys. The token comes from your environment and never lands in a file vibe writes.
55 Token never lands on disk via vibevm. The token comes from the operator's environment. Vibe reads it, builds the credentialed URL in memory, hands it to the spawned git process, and discards. The lockfile'ssource_urlfield always carries the canonical URL (no embedded credentials) — symmetric with the[[mirror]]invariant in §2.3. Token discipline (PROP-000 §20) applies: the value is treated as surface-secret; it does not appear in any vibevm-emitted output. Modern git (≥2.31) auto-redacts passwords from its own stderr, so even on errors the token is not echoed.
Packages on this machine
56A vibe built from a source checkout treats that checkout's in-tree packages as an embedded registry: on by default for such a build, off for a distributed one, and off for one command with --no-default-registry. Version enumeration still unions the embedded and the declared registries, so a newer published version is seen; an override or a git source on a requirement stays above the embedded registry. --embedded-short-circuit stops enumeration at the embedded registry for the packages it serves, so a fully embedded graph resolves with no network at all.
57
Its default follows the install origin: on for origin = "external", off for
a distribution.
58--no-default-registry(envVIBE_NO_DEFAULT_REGISTRY=1) suppresses the embedded registry entirely for a command.
59 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.
60 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:
61--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.
62A package resolved this way is recorded with source_kind = "embedded", and vibe check warns that such a lock is not portable. In --frozen and other non-interactive runs the embedded registry is off, so a lock that only works on one developer's machine cannot pass in a build server.
63 A package resolved from the embedded registry recordssource_kind = "embedded"invibe.lock(a PROP-002SourceKindvariant besideregistry/git/override/path). Itssource_urlis thefile://path into<source_path>/packages.
64 Warn.vibe checkwarns (does not fail) when the lock carries anysource_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." Asource_kind = "local"entry is portable and does NOT warn.
65
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.
66A project that carries a packages/ folder beside its manifest gets that folder opened as a local registry with no declaration at all. Packages resolved from it record source_kind = "local", which is portable, because every checkout resolves the same folder to the same content. --no-prefer-local bypasses the folder for one command.
67 REQ. A project carrying<project_root>/packages/(whereproject_rootis the directory holding the project'svibe.toml, resolved byresolve_project_root) gets that directory auto-opened as aLocalRegistryand composed into the local-registry family alongside the vibe-embedded registry. No[[registry]]block, no--registry <path>, no~/.vibe/registry.tomlmachine entry needed.
68 REQ. A package resolved from project-local recordssource_kind = "local"invibe.lock(§4) — distinct fromembedded. Unlikeembedded, it is portable and the reproducibility guard (§5) does NOT warn on it: every checkout of the project resolves the samepackages/to the same content.
69 REQ.--no-prefer-localsuppresses project-packages discovery for one command (use when a project'spackages/is stale, broken, or deliberately bypassed). It does NOT suppress vibe-embedded —--no-default-registryremains the knob for that.--prefer-localis the explicit affirmation of the default (project-local wins the local family); mutually exclusive with--no-prefer-local.
Edge cases and rules
70A vibe built from a source checkout treats that checkout's in-tree packages as an ambient registry, consulted first: a developer of vibe installs the packages being developed without publishing them. A distributed vibe has no such registry.
71 This PROP makes the in-treepackages/of a source-installedvibean ambient default registry — resolved automatically, with zero configuration in the consuming project.
72The source repository of vibe itself is mirrored on two hosts, but that is a different thing from the package registry: the mirrors carry the program's source, the registry carries packages, and the credentials for the two are never shared.
73 This PROP governs the source repository; it is orthogonal to the package registry, and the two must not be conflated.
74vibe drives registries through the git program on your PATH and checks for it before it starts; VIBE_GIT_BINARY points it at another copy. A registry clone older than one hour is refreshed before an install, a younger one is used as is, and vibe registry sync refreshes regardless of age.
75 Runtime dependency ongitinPATH. Acceptable: our target audience is developers who already have git installed. We perform a preflightgit --versioncheck and emit an actionable error (with a pointer tohttps://git-scm.com/downloads) if it is missing.
76 Resolved — shipped as proposed. TheVIBE_GIT_BINARYPATH override lives ingit_backend/shell.rsand its comment cites §6 of this PROP; the env-var form was chosen over a CLI flag exactly to keep the CLI surface stable.
77 Decision: the default freshness TTL is 1 hour, checked againstmeta.toml.last_pulled_at. An install whose registry cache is older than the TTL triggers an implicitupdate. An install whose cache is younger skips the pull.vibe registry syncforces an update regardless of age.
78A file the resolver needs, a manifest or a redirect stub, is read straight from the host over HTTPS when the host is GitHub or GitVerse, with a credential sent as a header and never in the address; a miss settles the question only for a tag or a commit, whose content is fixed, and on a branch git is asked next. A host the table does not name never enters this path.
79 Before a single file is asked of git, the backend reads it over HTTPS from the host's own raw endpoint when the host is one it knows —github.comthroughraw.githubusercontent.com,gitverse.ruthrough its contents API — with any credential sent only as a bearer header, never in the address. A hit is the file. A miss is authoritative only for a tag or a commit SHA, whose content is fixed; on a branch, or for a manifest, the read falls through to git, whose answer stands as it always did. A host the table does not name never enters this path.
80A read the host refuses for the moment, with a rate limit or a server error, is retried a few times with a short pause before the reader falls back to git; a plain «not found» is never retried.
81 A raw read that the host refuses with429or a5xxis retried a small, bounded number of times with a short pause, honouringRetry-Afterwithin that bound, before the read falls through to git as any other unexpected answer does. A404is never retried: on a tag or a commit it is authoritative, and on a branch it is what git will be asked about next.