<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="prop-017">PROP-017 — Resolvo as the production resolver</title>
  <status stage="impl" state="done" comment="C 2026-07-25: the port is complete per §6 and ResolvoDepSolver is the shipped production default (registry.rs:117)"/>
  <p p="1"><fact id="status-line" status="impl/done">**Status.** Design proposal accepted (owner decision, 2026-06-14) and
**the port is COMPLETE** — `ResolvoDepSolver` is the shipped production default
(`crates/vibe-cli/src/registry.rs:117`, `--solver` defaults to `resolvo`), as §6
records. Only the far-backlog reverse weak-deps remain. Companion to [PROP-003](PROP-003-dep-evolution.xml)
(dependency-model evolution) and [PROP-002](../vibe-registry/PROP-002-decentralized-registry.xml)
(registry / depsolver seam).</fact></p>
  <list ordered="false" p="2">
    <item><fact id="SUPERSEDES-BACKEND" status="impl/done">**Supersedes.** The solver-*backend* decision of [PROP-003 §2.2](PROP-003-dep-evolution.xml#solver-backend)
  (libsolv via thin FFI) and the libsolv-specific algorithm detail of
  [§2.3](PROP-003-dep-evolution.xml#solver-features) and
  [§3.1–3.2](PROP-003-dep-evolution.xml#rule-encoding).</fact></item>
    <item><fact id="RESOLVO-IS-PRODUCTION" status="impl/done">**The production
  solver is [`resolvo`](https://github.com/prefix-dev/resolvo) (pure-Rust,
BSD-3-Clause), not libsolv.**</fact></item>
    <item><fact id="VOCABULARY-UNCHANGED" status="impl/done">PROP-003's dependency *vocabulary* — features
(§2.4), subskills (§2.5), interface tags (§2.6), conditional deps (§2.6.1),
i18n (§2.7), manifest/lockfile schema (§2.8–2.9) — is **unchanged**; this
PROP only swaps the engine that satisfies it and records how the vocabulary
maps onto resolvo's model.</fact></item>
  </list>
  <section id="why" title="1. Why resolvo now — reversing the libsolv-first call">
    <list ordered="false" p="3">
      <item><fact id="libsolv-first-context" status="impl/done">PROP-003 §2.2 picked libsolv "for v1" and explicitly *kept the door open*
  for resolvo (§4.1: "resolvo remains a viable second impl"). It gave three
  reasons to defer resolvo.</fact></item>
      <item><fact id="reasons-decayed" status="impl/done">By 2026-06 all three have decayed, while
libsolv's costs — which §2.2 underweighted — are structural and do not
  decay:</fact></item>
    </list>
    <p p="4"><fact id="deferral-reasons-lead" status="impl/done">**The three deferral reasons, re-evaluated:**</fact></p>
    <list ordered="false" p="5">
      <item><fact id="reason-age" status="impl/done">*"Younger codebase (~3 years vs ~17)."* resolvo is now ~4–5 years old,
  at `0.11.0`, multi-threaded, and the production resolver for `pixi`,
  `rattler` (the Rust conda stack), and `rip` (PyPI-in-Rust).</fact></item>
      <item><fact id="reason-battle-tested" status="impl/done">*"Less battle-tested under adversarial inputs."* conda dependency graphs
  are among the most conflict-rich in existence; resolvo runs against them
  in production at scale. This is the reason that has decayed most.</fact></item>
      <item><fact id="reason-introspection" status="impl/done">*"No rule-level introspection for explanation-driven errors."* resolvo
  now ships structured conflict explanation
  (`Conflict::display_user_friendly` / `Conflict::graph`).</fact></item>
    </list>
    <p p="6"><fact id="structural-costs-lead" status="impl/done">**libsolv's structural costs (do not decay):**</fact></p>
    <list ordered="false" p="7">
      <item><fact id="COST-C-FFI" status="impl/done">**C toolchain + FFI + `unsafe`.** libsolv is C; a thin FFI shim is still
  ~20–30 `unsafe extern` calls — the **first C dependency in the
  workspace** and the first `unsafe` surface in a crate that today carries
  `#![forbid(unsafe_code)]`. It forces a C compiler into CI on every
  platform. resolvo is pure Rust: the `forbid(unsafe_code)` posture on
  `vibe-resolver` survives intact.</fact></item>
      <item><fact id="COST-EAGER-POOL" status="impl/done">**Eager pool population.** libsolv wants the whole pool materialised
  before solving ([PROP-003 §3.2 phase 1](PROP-003-dep-evolution.xml#solver-phases):
  walk `list_versions` for every transitively-reachable package up front).
  PROP-003 §3.4 itself names the *network-bound fetch layer* as the real
  bottleneck — and eager population maximises exactly those fetches.
  resolvo's provider is pulled **lazily / on demand**, so metadata is
  fetched only for packages the search actually visits.</fact></item>
      <item><fact id="COST-WINDOWS" status="impl/done">**Windows operational risk.** §2.2 named "libsolv proves operationally
  heavy on Windows" as the trigger to switch to resolvo. The maintainer's
  primary platform is Windows; a C library via FFI (MSVC/MinGW, submodule
  or build-script fragility) is precisely that risk, on precisely that
  platform.</fact></item>
    </list>
    <list ordered="false" p="8">
      <item><fact id="NET-VERDICT" status="impl/done">**Net:** resolvo is pure-Rust (no `unsafe`, no C toolchain), BSD-3-Clause
  (clean under [PROP-000 §3](../../common/PROP-000.xml) with no owner ruling
  needed), lazy-fetch (aligned with the real bottleneck), gives
  human-readable conflict explanations, and gets "prefer newest" almost free
  via candidate ordering.</fact></item>
      <item><fact id="pubgrub-weaker" status="impl/done">pubgrub was also considered and remains the weaker
fit for vibevm: its range-over-named-packages model encodes the
capability / virtual-package vocabulary as synthetic packages and degrades
exactly the explanations we want (PROP-003 §4.2 records this).</fact></item>
    </list>
    <p p="9"><fact id="owner-decision" status="impl/done">This decision was taken by the owner directly; the libsolv reasoning is
retained in PROP-003 §2.2 as decision history.</fact></p>
  </section>
  <section id="architecture" title="2. Architecture — adapter behind the stable `DepSolver` seam">
    <p p="10"><fact id="SEAM-STABLE" status="impl/done">The consumer seam does not move. `crates/vibe-resolver/src/lib.rs` keeps:</fact></p>
    <fence lang="rust" p="11">pub trait DepSolver {
    fn solve(&amp;self, roots: &amp;[PackageRef]) -&gt; Result&lt;ResolvedGraph, SolveError&gt;;
}</fence>
    <p p="12"><fact id="consumers-untouched" status="impl/done">The install / update / vendor / check pipelines call `DepSolver::solve`
and are untouched. resolvo arrives as one new `impl DepSolver`:</fact></p>
    <list ordered="false" p="13">
      <item><fact id="RESOLVO-DEPSOLVER" status="impl/done">**`ResolvoDepSolver&lt;P: DepProvider&gt;`** — a `#[cell(seam = "DepSolver",
  variant = "resolvo")]`. Its `solve` builds a `VibevmResolvoProvider`
  from the roots + the vibevm `DepProvider`, runs `resolvo::Solver`, and
  maps the chosen solvables back into a `ResolvedGraph`.</fact></item>
      <item><fact id="VIBEVM-PROVIDER" status="impl/done">**`VibevmResolvoProvider`** — implements resolvo's two traits,
  `Interner` + (async) `DependencyProvider`, adapting vibevm's world (package
  identities, version sets, manifests) to resolvo's `NameId` / `SolvableId`
  / `VersionSetId` model. This struct **is the swap unit** (§5): a
  different engine, or a future resolvo major, means rewriting this adapter
  and nothing the consumers can see.</fact></item>
      <item><fact id="SEMVER-VERSION-SET" status="impl/done">**`SemverVersionSet`** — a `resolvo::utils::VersionSet` with
  `type V = semver::Version`; an `enum { Any, Req(semver::VersionReq),
  None }` so that `VersionSpec::Latest → Any`, a `[conflicts]` /
  obsoletes range → a complement or `None` (match-nothing) set.</fact></item>
    </list>
    <section id="runtime" title="2.1 Sync CLI, no async runtime">
      <list ordered="false" p="14">
        <item><fact id="NOW-OR-NEVER" status="impl/done">resolvo `0.11`'s `DependencyProvider` methods are `async fn`, but `Solver`
  defaults to `NowOrNeverRuntime`, which polls each future exactly once and
  **panics if it ever yields `Pending`**.</fact></item>
        <item><fact id="SYNC-ADAPTER" status="impl/done">Our adapter methods compute
synchronously — they read from the sync vibevm `DepProvider` (which may
block on the network, but blocking is not yielding) — so every future is
  `Ready` on first poll.</fact></item>
        <item><fact id="NO-ASYNC-RUNTIME" status="impl/done">**vibevm pulls in no async runtime, no `tokio`, no
  `pollster`.**</fact></item>
        <item><fact id="AWAIT-SHARP-EDGE" status="impl/done">The one sharp edge, recorded so no future edit trips it: an
adapter method must never `.await` a genuinely-pending future under the
  default runtime.</fact></item>
      </list>
    </section>
    <section id="provider-enrichment" title="2.2 Provider enrichment — candidate enumeration">
      <list ordered="false" p="15">
        <item><fact id="provider-history" status="impl/done">vibevm's `DepProvider` was shaped around the naive solver: `resolve_version`
  picks *one* concrete version for a `PackageRef`, baking version selection
  into the provider.</fact></item>
        <item><fact id="ENUMERATION-NEEDED" status="impl/done">A real solver must enumerate candidates and choose the
optimum itself. So `DepProvider` gains one method:</fact></item>
      </list>
      <fence lang="rust" p="16">fn list_versions(&amp;self, group: &amp;Group, name: &amp;str)
    -&gt; Result&lt;Vec&lt;semver::Version&gt;, DepProviderError&gt;;</fence>
      <p p="17"><fact id="LIST-VERSIONS-BACKING" status="impl/done">backed by the registry layer's existing `Registry::list_versions`
(`vibe-registry`), which every registry impl already provides. This is an
enrichment of the *world model*, not a change to the consumer seam;
`resolve_version` stays for the naive / sat cells.</fact></p>
    </section>
    <section id="output" title="2.3 Shared output contract">
      <p p="18"><fact id="SHARED-OUTPUT-BUILDER" status="impl/done">The roots-first ordering, exact-version pinning (`=x.y.z` on every
dependency edge, the lockfile reproducibility contract), and
obsolete-dropping that `naive.rs` builds today are extracted into a
`pub(crate)` output builder and reused by `ResolvoDepSolver`. Both cells
therefore satisfy the *same* observable contract by construction — which
is what lets the differential oracle (§4) hold them to byte-identical
graphs.</fact></p>
    </section>
    <section id="unsatisfiable" title="2.4 Rich conflict explanation">
      <p p="19"><fact id="CONFLICT-EXPLANATION" status="impl/done">On `Err(UnsolvableOrCancelled::Unsolvable(conflict))`, resolvo gives a
human-readable derivation via `conflict.display_user_friendly(&amp;solver)`.
`SolveError` gains a variant to carry it:</fact></p>
      <fence lang="rust" p="20">SolveError::Unsatisfiable { explanation: String }</fence>
      <list ordered="false" p="21">
        <item><fact id="EXPLANATION-PAYOFF" status="impl/done">This is the user-facing payoff of the switch: "package A needs C ^1 but B
  needs C ^2, and only C 1.0 and 2.0 exist" instead of a bare UNSAT.</fact></item>
        <item><fact id="STRUCTURED-VARIANTS-REMAIN" status="impl/done">The
  structured `SolveError` variants (`VersionConflict`, `CapabilityUnmet`,
`DisjunctionUnsatisfiable`, `ConflictsDeclared`) remain for cases the
  adapter can attribute precisely.</fact></item>
      </list>
    </section>
  </section>
  <section id="encoding" title="3. Vocabulary → resolvo encoding">
    <list ordered="false" p="22">
      <item><fact id="RPM-LINEAGE" status="impl/done">vibevm's dependency vocabulary is RPM-lineage (provides / requires /
  conflicts / obsoletes plus the four weak-dep levels) — the same lineage
  resolvo inherits from libsolv, so the mapping is natural.</fact></item>
      <item><fact id="CONSTRAINT-CHANNELS" status="impl/done">resolvo's
  constraint channels: `KnownDependencies.requirements: Vec&lt;ConditionalRequirement&gt;`
("must be satisfied", pulls the package in) and
`KnownDependencies.constrains: Vec&lt;VersionSetId&gt;` ("*if* present, the
version must match" — does not pull the package in).</fact></item>
      <item><fact id="REQUIREMENT-FORMS" status="impl/done">Requirements are
  `Requirement::Single(VersionSetId)` or `Requirement::Union(VersionSetUnionId)`.</fact></item>
      <item><fact id="SINGLE-VERSION-AUTO" status="impl/done">Single-version-per-name is enforced by resolvo automatically.</fact></item>
    </list>
    <table p="23">
      <tr>
        <td>vibevm concept</td>
        <td>resolvo encoding</td>
        <td>Slice</td>
      </tr>
      <tr>
        <td><fact id="ROW-REQUIRES" status="impl/done">`[requires.packages]` (concrete dep `X ^v`)</fact></td>
        <td><fact id="ROW-REQUIRES-RESOLVO-ENCODING" status="impl/done">`requirements += Single(VersionSetId(X, ^v))`</fact></td>
        <td><fact id="ROW-REQUIRES-SLICE" status="impl/done">S2</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-PREFER-NEWEST" status="impl/done">version selection ("prefer newest")</fact></td>
        <td><fact id="ROW-PREFER-NEWEST-RESOLVO-ENCODING" status="impl/done">`sort_candidates` orders a name's solvables by `semver::Version` **descending** → first solution is newest-feasible</fact></td>
        <td><fact id="ROW-PREFER-NEWEST-SLICE" status="impl/done">S2</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-SINGLE-VERSION" status="impl/done">single version per `(kind, name)`</fact></td>
        <td><fact id="ROW-SINGLE-VERSION-RESOLVO-ENCODING" status="impl/done">automatic (one `SolvableId` per `NameId`)</fact></td>
        <td><fact id="ROW-SINGLE-VERSION-SLICE" status="impl/done">S2</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-REQUIRES-ANY" status="impl/done">`[[requires_any]]` (`one_of = [A, B, …]`)</fact></td>
        <td><fact id="ROW-REQUIRES-ANY-RESOLVO-ENCODING" status="impl/done">`requirements += Union(VersionSetUnionId[A, B, …])` — native OR + backtracking</fact></td>
        <td><fact id="ROW-REQUIRES-ANY-SLICE" status="impl/done">S4</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-CAPABILITY" status="impl/done">capability / interface (`provides` / `requires.capabilities`)</fact></td>
        <td><fact id="ROW-CAPABILITY-RESOLVO-ENCODING" status="impl/done">virtual `NameId`; `get_candidates(cap)` returns the providing packages' solvables (a reverse index the adapter builds); a `requires.capabilities` entry is a `Single(VersionSetId(cap, ^v))`</fact></td>
        <td><fact id="ROW-CAPABILITY-SLICE" status="impl/done">S4</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-CONFLICTS" status="impl/done">`[conflicts]` (X conflicts Y)</fact></td>
        <td><fact id="ROW-CONFLICTS-RESOLVO-ENCODING" status="impl/done">`constrains += VersionSetId(Y, None)` in X's deps — if Y is forced in, the match-nothing set conflicts</fact></td>
        <td><fact id="ROW-CONFLICTS-SLICE" status="impl/done">S4</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-OBSOLETES" status="impl/done">`[obsoletes]` (X obsoletes Y `&lt; v`)</fact></td>
        <td><fact id="ROW-OBSOLETES-RESOLVO-ENCODING" status="impl/done">`constrains += VersionSetId(Y, complement(&lt;v))`; obsoleted entries dropped by the shared output builder</fact></td>
        <td><fact id="ROW-OBSOLETES-SLICE" status="impl/done">S4</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-RECOMMENDS" status="impl/done">`[recommends]` (weak forward: prefer, don't fail)</fact></td>
        <td><fact id="ROW-RECOMMENDS-RESOLVO-ENCODING" status="impl/done">**DONE** — a post-solve greedy expansion: each recommend is tried via a re-solve, kept (as a non-root) only if the graph stays satisfiable, else silently dropped. `soft_requirements` is root-only, so per-package recommends ride the loop (batching them as soft is §8 future work)</fact></td>
        <td><fact id="ROW-RECOMMENDS-SLICE" status="impl/done">done</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-SUGGESTS" status="impl/done">`[suggests]` (weak forward hint)</fact></td>
        <td><fact id="ROW-SUGGESTS-RESOLVO-ENCODING" status="impl/done">**DONE** — never fed to the solver, so never auto-installed (CLI surfacing is a thin follow-up)</fact></td>
        <td><fact id="ROW-SUGGESTS-SLICE" status="impl/done">done</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-SUPPLEMENTS" status="spec/done">`[supplements]` (reverse: install me if Y is present)</fact></td>
        <td><fact id="ROW-SUPPLEMENTS-RESOLVO-ENCODING" status="spec/done">far backlog (§8) — a reverse weak-dep needing a `who-supplements-Y` index</fact></td>
        <td><fact id="ROW-SUPPLEMENTS-SLICE" status="spec/done">backlog</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-ENHANCES" status="spec/done">`[enhances]` (reverse hint)</fact></td>
        <td><fact id="ROW-ENHANCES-RESOLVO-ENCODING" status="spec/done">far backlog (§8) — `who-enhances-Y` is a reverse lookup</fact></td>
        <td><fact id="ROW-ENHANCES-SLICE" status="spec/done">backlog</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-FEATURES-EXCLUSIVE" status="impl/done">`[features.exclusive]` (at-most-one group)</fact></td>
        <td><fact id="ROW-FEATURES-EXCLUSIVE-RESOLVO-ENCODING" status="impl/done">**DONE** — validated in `features.rs` (`expand_features`), intra-package, above the solver</fact></td>
        <td><fact id="ROW-FEATURES-EXCLUSIVE-SLICE" status="impl/done">done</fact></td>
      </tr>
      <tr>
        <td><fact id="ROW-FEATURE-UNIFICATION" status="impl/done">feature unification</fact></td>
        <td><fact id="ROW-FEATURE-UNIFICATION-RESOLVO-ENCODING" status="impl/done">stays in `features.rs` above the solver (already implemented); the solver sees the unified requirement set</fact></td>
        <td><fact id="ROW-FEATURE-UNIFICATION-SLICE" status="impl/done">—</fact></td>
      </tr>
    </table>
    <p p="24"><fact id="CONDITIONAL-FIXPOINT" status="impl/done">Conditional dependencies ([PROP-003 §2.6.1](PROP-003-dep-evolution.xml#conditional-deps))
keep their fixpoint shape: solve unconditional → evaluate context
predicates → add requirements → re-solve. resolvo is cheap to re-run, and
its laziness means the re-solve only re-touches the changed subtree.</fact></p>
  </section>
  <section id="dominance" title="4. Correctness — the differential dominance contract">
    <p p="25"><fact id="oracle-socket" status="impl/done">`crates/vibe-resolver/tests/solver_properties.rs` already drives two
`DepSolver` cells over the same generated world and demands they agree;
it was built (its own docs say so) as "DBT-0011's landing pad" for exactly
this. `ResolvoDepSolver` plugs into the same socket as `Sat`, under a
**dominance** contract:</fact></p>
    <list ordered="false" p="26">
      <item><fact id="DOM-NAIVE-SOLVES" status="impl/done">**naive solves ⟹ resolvo solves identically.** Provable for the
  concrete-dep worlds the generator emits: when naive's greedy first-pick
  succeeds, that pick is the highest version satisfying the first
  constraint, and (since it also satisfies all others) it equals the
  highest version satisfying *all* constraints — which is resolvo's
  optimum. So the graphs are byte-identical. Any drift here is a bug.</fact></item>
      <item><fact id="DOM-NAIVE-FAILS" status="impl/done">**naive fails ⟹ resolvo may solve.** The first-pick-wins trap arises
  naturally in generated worlds (a root takes a dep's highest version,
  another path carets a lower major); resolvo's complete CDCL search finds
  the feasible lower version. This *is* resolvo's reason to exist.</fact></item>
      <item><fact id="DOM-RESOLVO-FAILS" status="impl/done">**resolvo fails where naive solves ⟹ always a bug.**</fact></item>
      <item><fact id="DOM-BOTH-FAIL" status="impl/done">**both fail ⟹ pass without comparing error discriminants.** Unlike the
  `Sat` cell — which re-emits naive's own `SolveError` verbatim and so
  shares its discriminant — resolvo produces its own richer errors
  (`Unsatisfiable` with a derivation). Demanding discriminant equality
  would force resolvo to throw away its better diagnostics. The relaxation
  is deliberate and recorded here (card scaffold-d: "a divergence is
  recorded with its debt id before the assertion is relaxed").</fact></item>
    </list>
    <p p="27"><fact id="VOCAB-UNIT-TESTS" status="impl/done">The capability / disjunction / conflict / obsolete / weak-dep vocabulary
is *not* exercised by the generator (it emits `[requires.packages]` only);
those land with their own unit tests mirroring `naive/tests.rs`, plus
resolvo-only cases (disjunction backtracking) naive cannot pass.</fact></p>
  </section>
  <section id="swap" title="5. Abstraction &amp; the swap requirement">
    <list ordered="false" p="28">
      <item><fact id="SWAP-REQUIREMENT" status="impl/done">**Requirement (owner, 2026-06-14).** Changing the resolver engine — a
  different solver, or a future incompatible resolvo major — must cost
  *one new provider/adapter and nothing else*.</fact></item>
      <item><fact id="SWAP-BOUNDARIES" status="impl/done">The consumer-facing
`DepSolver` and the world-facing `DepProvider` are the stable boundaries;
everything resolvo-specific is confined to `ResolvoDepSolver` +
`VibevmResolvoProvider` + `SemverVersionSet`.</fact></item>
    </list>
    <p p="29"><fact id="swap-recipe" status="impl/done">Concretely, a future swap is: write a new `impl DepSolver` whose adapter
bridges the same vibevm `DepProvider` world to the new engine, register it
as a `#[cell]` variant, and add it to the differential oracle so it is
held to the same dominance contract. No consumer, no manifest, no lockfile
change. This is the same `GitBackend`-style indirection PROP-001 uses to
keep the git backend swappable, applied to the solver.</fact></p>
  </section>
  <section id="phases" title="6. Phases / staging">
    <p p="30"><fact id="PORT-COMPLETE" status="impl/done">**Status (2026-06-14): the port is COMPLETE — resolvo is the default
production solver.** Each slice landed as a topic commit with green gates.</fact></p>
    <list ordered="false" p="31">
      <item><fact id="DONE-DOC" status="impl/done">**DONE** — this document + the PROP-003 §2.2 supersede note.</fact></item>
      <item><fact id="DONE-OUTPUT-BUILDER" status="impl/done">**DONE** — the shared output builder + the `VersionEnumerator` seam +
  `SolveError::Unsatisfiable`.</fact></item>
      <item><fact id="DONE-ADAPTER" status="impl/done">**DONE** — `resolvo` dep; `ResolvoDepSolver` + `VibevmResolvoProvider` +
  `SemverVersionSet`; `[requires.packages]` + newest + narrowing.</fact></item>
      <item><fact id="DONE-ORACLE" status="impl/done">**DONE** — `differential_naive_vs_resolvo_dominance` (the oracle).</fact></item>
      <item><fact id="DONE-VOCAB" status="impl/done">**DONE** — `[[requires_any]]`→`Union`, `[conflicts]`, `[obsoletes]`, and
  capabilities via the closure pre-scan.</fact></item>
      <item><fact id="DONE-ENUMERATION" status="impl/done">**DONE** — production version enumeration
  (`MultiRegistryResolver::list_versions` + the `VersionEnumerator`
  provider impls).</fact></item>
      <item><fact id="DONE-DEFAULT-FLIP" status="impl/done">**DONE** — `--solver &lt;naive|sat|resolvo&gt;` override + **the default
  flipped to resolvo** in the R-001 selection seam (`vibe-cli`).</fact></item>
      <item><fact id="DONE-WEAK-FORWARD" status="impl/done">**DONE (forward weak-deps)** — `[recommends]` (a post-solve greedy
  best-effort expansion) and `[suggests]` (parsed, never auto-installed)
  gained a `Manifest` schema and solver behaviour;
  `[features.exclusive]` was already validated in `features.rs`.</fact></item>
      <item><fact id="BACKLOG-REVERSE" status="spec/done">**FAR BACKLOG (reverse weak-deps + lockfile, §8)** — `[supplements]`
  and `[enhances]` (reverse-direction, need a reverse index) and the
  `[meta].solver` lockfile recording (needs a lockfile schema-version
  bump) wait until the rest is ready.</fact></item>
    </list>
    <p p="32"><fact id="CELLS-STAY" status="impl/done">naive and sat stay in tree: naive as the small-graph fast path and the
oracle's reference cell; sat as a recorded pure-Rust backtracker — both
still selectable via `--solver`.</fact></p>
  </section>
  <section id="determinism" title="7. Determinism &amp; performance">
    <list ordered="false" p="33">
      <item><fact id="DETERMINISM" status="impl/done">**Determinism.** `list_versions` returns a stable order; `sort_candidates`
  is a total order over `semver::Version`; the output builder re-sorts into
  roots-first + `(group, name)` order. Given deterministic provider
  responses, `solve` is deterministic — the property the oracle's
  `solve_is_deterministic` pins.</fact></item>
      <item><fact id="PERFORMANCE" status="impl/done">**Performance.** resolvo's laziness preserves vibevm's network profile:
  a package's versions are fetched only when the search first asks for that
  name, a manifest only when a solvable is explored. "Prefer newest" costs
  a sort, not a separate optimisation pass. At vibevm's scale (hundreds of
  packages, depth-3 graphs — PROP-003 §3.4) the solve is far from the
  bottleneck; the fetch layer is, and laziness is the right lever there.</fact></item>
    </list>
  </section>
  <section id="future-work" title="8. Future work">
    <list ordered="false" p="34">
      <item><fact id="FUTURE-CAPABILITY-INDEX" status="spec/done">**Capability resolution via a registry reverse-index.** The near-term
  capability handling (§3) resolves `[requires.capabilities]` against a
  pre-scan of the transitive package closure — correct, and strictly
  stronger than naive's already-seen-graph matching, but it forgoes
  laziness for capabilities and only finds providers reachable through
  the package-dependency graph. A fuller design adds a real
  `capability → providers` **reverse-index** to the registry (the
  git-backed registry has none today): the resolver would then enumerate
  capability providers *lazily*, exactly as it enumerates package
  versions, and so find providers that no package edge references. This
  is new registry infrastructure — an index format, a publish-time
  emitter, and a query path — recorded here as the capability layer's
  natural evolution. Not scheduled; the trigger is capability routing
  across packages-not-yet-seen becoming load-bearing.</fact></item>
      <item><fact id="FUTURE-REVERSE-WEAK" status="spec/done">**Reverse weak-deps + the `[meta].solver` lockfile field.** The forward
  weak-deps (`[recommends]`, `[suggests]`) are implemented (PROP-003
  §2.3.3) — schema in `vibe-core`, behaviour in `ResolvoDepSolver`
  (recommends a greedy best-effort expansion, suggests never installed).
  The reverse levels wait on a reverse index: `[supplements]` ("install
  me if Y is present") and `[enhances]` ("what enhances Y") are
  reverse-direction lookups, the same shape as the capability
  reverse-index above. Folding the per-recommend loop into one
  `soft_requirements`-batched solve is a perf optimisation that can land
  any time. Separately, the lockfile `[meta]` block has no `solver` field
  (despite PROP-003 §2.1's note); recording the selected cell for a
  reproducible re-resolve needs a lockfile schema-version bump.</fact></item>
    </list>
  </section>
  <section id="references" title="9. References">
    <list ordered="false" p="35">
      <item><fact id="ref-resolvo" status="spec/done">resolvo: &lt;https://github.com/prefix-dev/resolvo&gt; (BSD-3-Clause), crate
  `resolvo 0.11` (MSRV 1.85.1); `Interner` + async `DependencyProvider`,
  `Solver` / `Problem` / `Requirement` / `KnownDependencies` /
  `conflict::Conflict`, `utils::{Pool, VersionSet}`, `runtime::NowOrNeverRuntime`.</fact></item>
      <item><fact id="ref-prop-003" status="spec/done">[PROP-003](PROP-003-dep-evolution.xml) — dependency-model evolution
  (vocabulary retained; §2.2 backend superseded here).</fact></item>
      <item><fact id="ref-prop-002" status="spec/done">[PROP-002 §2.8](../vibe-registry/PROP-002-decentralized-registry.xml) —
  the `DepSolver` / `DepProvider` seam.</fact></item>
      <item><fact id="ref-prop-000" status="spec/done">[PROP-000 §3](../../common/PROP-000.xml) — permissive-license policy
  (resolvo BSD-3-Clause is clean).</fact></item>
    </list>
  </section>
</spec>
