Skip to content

v0.5 scope — rtb-vcs v0.2 git-operations slice

Status: IMPLEMENTED — all eight commits landed (PRs #42, #49, #50, #51, #52, #53, #54, plus this docs commit). Component page at docs/components/rtb-vcs.md; framework spec §9 aligned. A4 was obsoleted mid-flight (consistent shell-out architecture removed the need for git2-fallback). Parent contract: rust-tool-base.md §9 (VCS & release providers) and §16 (roadmap, "0.5" line). Predecessor: 2026-04-23-rtb-vcs-v0.1.md — release-provider slice (shipped in 0.2.0). Workspace target: the next workspace version after the 0.4.1 (App) point release. Provisionally labelled 0.5.0; release timing decided when the slice lands.


1. Motivation

The framework spec §9 calls for a Repo type and a TokenSource so downstream tools can clone, walk, blame, diff, and authenticate against the repositories whose releases they already consume. The release-provider slice (v0.1, shipped at 0.2.0) is read-only and metadata-only — there is no way for a downstream tool built on RTB to ask "what changed since the last release?" or "who last touched this file?" without re-importing gix directly and paying for its full API surface.

Concretely, the gap blocks four downstream stories:

  • rtb-cli-bin project generation + regeneration (v0.6). The scaffolder has two git interactions: (i) at rtb new, auto-initialise a git repository for the generated project (git init + initial commit), and (ii) at rtb regenerate, detect drift between the generated tool's current working tree and the regenerated template output (a diff op) and surface it cleanly. Without Repo, the scaffolder has to shell out to git or pull gix in directly.
  • rtb-ai release-note summarisation. The plan in framework spec §16 is for rtb-ai to summarise the git log between two release tags. Today rtb-ai has no way to walk commits; it would have to depend on gix directly, bypassing the framework's auth and concurrency story.
  • Downstream tools that just need git. Any operator-flow tool built on RTB that wants to clone-and-inspect a repository (e.g. a code-review CLI, a release-prep tool, a dependency-graph walker) has to roll its own gix wrapper. The framework promises a Repo type; v0.5 is when we deliver it.
  • Downstream tools that need git as a foundation. Beyond the three named consumers, Repo is a deliberately foundational abstraction — tool authors building anything from CI-orchestrators to repo-statistics dashboards should be able to compose richer behaviour on top of Repo without re-importing gix and re-deriving the framework's auth/concurrency conventions.

Foundational, not curated

This last bullet is load-bearing. The GTB equivalent (pkg/vcs/repo in go-tool-base) started life over-engineered for an earlier incarnation, got pared back to a sensible abstraction, and then quietly became the load-bearing piece that downstream tools layered their own git-based functionality on top of. The lesson for v0.5: design the surface as a foundation that consumers compose on top of, not as a curated facade that exposes only what the named consumers (scaffolder, release-notes) need today. That framing pulls the A3 cut up — see §7.

v0.5 closes the gap by adding a thin Repo abstraction over gix (with git2 as a feature-gated fallback for ops gix cannot yet perform) plus a TokenSource for auth that integrates with the v0.4.1 typed-config story.

2. The design tension

The framework spec §9.2 fixes some shape ("gix is the primary backend"; "Repo::spawn_blocking(...) runs blocking gix operations inside tokio::task::spawn_blocking"; "fallback to git2 only for operations gix cannot yet perform"). What it does not fix:

  • The public surface (sync vs async, ownership of the underlying gix::Repository, error model).
  • How TokenSource integrates with the post-v0.4.1 typed-config world — spec §9.3 references app.config.auth.env / app.config.auth.keychain / app.config.auth.value, which assume a fixed config shape that no longer exists.
  • The minimum-viable feature set: which ops ship in v0.5 vs deferred to v0.6+?
  • Whether git2-fallback is opt-in or always-on.

These are the seven open questions in §7. Resolving them defines the slice.

3. Sketch of the proposed surface

The shapes below are proposed, not committed. They exist so the open questions in §7 have something concrete to refer to. Resolutions in §8 will pin the final shape before TDD begins.

3.1 Repo type (read paths)

//! crates/rtb-vcs/src/git/mod.rs

pub struct Repo {
    inner: gix::ThreadSafeRepository,
    path: PathBuf,
}

impl Repo {
    /// Initialise a new repository at `path`. The scaffolder's
    /// `rtb new` flow uses this to git-init a freshly generated
    /// project before its initial commit.
    pub async fn init(
        path: impl AsRef<Path>,
        opts: InitOptions,
    ) -> Result<Self, RepoError>;

    /// Open an existing repository at `path`. Discovers `.git` if
    /// `path` is a subdirectory.
    pub async fn open(path: impl AsRef<Path>) -> Result<Self, RepoError>;

    /// Clone `url` into `dst`. Auth resolved via the supplied
    /// `TokenSource` for HTTPS URLs; SSH uses the host's agent.
    pub async fn clone(
        url: &str,
        dst: impl AsRef<Path>,
        opts: CloneOptions,
    ) -> Result<Self, RepoError>;

    /// Iterate the commit graph reachable from `revspec` (e.g.
    /// `HEAD`, `main..feature`, `v0.4.0..HEAD`). Returns an async
    /// stream so very long histories don't materialise as a `Vec`.
    pub fn walk(&self, revspec: &str) -> Result<CommitWalk<'_>, RepoError>;

    /// Diff two tree-ish references. Returns a structured `Diff`
    /// (file additions/deletions/hunks) rather than a raw patch
    /// string — callers that need patches call `Diff::to_patch`.
    pub async fn diff(&self, a: &str, b: &str) -> Result<Diff, RepoError>;

    /// `git blame`-style line authorship for `path` at `revspec`.
    pub async fn blame(&self, path: &Path, revspec: &str) -> Result<Blame, RepoError>;

    /// Repository status — staged / unstaged / untracked file lists.
    pub async fn status(&self) -> Result<RepoStatus, RepoError>;
}

3.2 Repo type (write paths)

impl Repo {
    /// Fetch from `remote` (default `origin`). Auth via `TokenSource`.
    pub async fn fetch(&self, remote: &str, opts: FetchOptions) -> Result<(), RepoError>;

    /// Check out `revspec`. Refuses dirty working trees by default;
    /// `CheckoutOptions::force(true)` overrides.
    pub async fn checkout(&self, revspec: &str, opts: CheckoutOptions) -> Result<(), RepoError>;

    /// Stage `paths` and create a commit.
    pub async fn commit(&self, paths: &[&Path], message: &str) -> Result<Oid, RepoError>;

    /// Push `refspec` to `remote`. **`git2-fallback` feature only**
    /// (gix push is experimental as of gix 0.66; we re-evaluate when
    /// gix stabilises it). Returns `RepoError::PushUnsupported` when
    /// the feature is disabled.
    #[cfg(feature = "git2-fallback")]
    pub async fn push(&self, remote: &str, refspec: &str) -> Result<(), RepoError>;
}

3.3 Auth — reuse rtb-credentials::Resolver

Per A2 resolution, no new auth trait. Auth-requiring ops accept a &CredentialRef (already declared by the host tool's typed config since v0.4) and resolve it through rtb-credentials::Resolver::resolve — which already returns SecretString and walks the env → keychain → literal → fallback-env precedence chain documented in rtb-credentials v0.1.

//! crates/rtb-vcs/src/git/auth.rs — thin glue, not a parallel trait.

use rtb_credentials::{CredentialError, CredentialRef, Resolver};
use secrecy::SecretString;

/// Resolve `cref` for git auth, mapping `CredentialError` into
/// `RepoError::Auth` so call sites get a single error type.
pub(crate) async fn resolve_for_git(
    resolver: &Resolver,
    cref: &CredentialRef,
) -> Result<SecretString, RepoError> {
    resolver.resolve(cref).await.map_err(RepoError::Auth)
}

The auth-requiring Repo methods (clone, fetch, push) take &CredentialRef directly:

impl Repo {
    pub async fn clone(
        url: &str,
        dst: impl AsRef<Path>,
        opts: CloneOptions,
        auth: Option<&CredentialRef>,  // None = anonymous / SSH-agent
    ) -> Result<Self, RepoError>;

    pub async fn fetch(
        &self,
        remote: &str,
        opts: FetchOptions,
        auth: Option<&CredentialRef>,
    ) -> Result<(), RepoError>;
}

The Resolver itself is constructed by the host tool (typically via Resolver::with_platform_default()) and threaded into operations via CloneOptions / FetchOptions or carried on the Repo instance — finalised in implementation. The point is that the seam is rtb-credentials, not a parallel rtb-vcs::TokenSource trait.

For ad-hoc cases where a downstream tool just wants "use $GITHUB_TOKEN" without typed config, they construct a CredentialRef { fallback_env: Some("GITHUB_TOKEN".into()), ..Default::default() } and pass it in. No special-case helper needed — the existing CredentialRef shape already covers it.

3.4 Error model

RepoError: thiserror::Error + miette::Diagnostic, following the same shape as ProviderError and CredentialError. Per A8 resolution, gix::Error and git2::Error are wrapped, not leaked — every variant is either a domain error or carries a stringified cause so we can swap the backend without breaking the public API.

Variants:

  • Io { path: PathBuf, source: std::io::Error } — filesystem path issues with the offending path attached.
  • OpenFailed { path: PathBuf, cause: String }Repo::open could not load the repository at path. cause carries the backend's stringified error.
  • InitFailed { path: PathBuf, cause: String }Repo::init could not create the repository at path.
  • CloneFailed { url: String, cause: String } — clone setup or transport failure (URL parse, refs negotiation, network).
  • FetchFailed { remote: String, cause: String } — fetch transport / refs failure.
  • CheckoutFailed { revspec: String, cause: String } — checkout could not switch to revspec.
  • CommitFailed { cause: String } — commit creation / staging failure.
  • PushFailed { remote: String, refspec: String, cause: String } — push transport / refs failure (only reachable with git2-fallback).
  • RevspecNotFound { revspec: String } — caller-facing for bad rev references that don't resolve to a known object.
  • DirtyWorkingTree { paths: Vec<PathBuf> } — checkout / commit guard tripped; the listed paths are dirty.
  • Auth(rtb_credentials::CredentialError) — credential resolution failed (wraps rtb-credentials's existing error; not a backend leak because rtb-credentials is part of the framework's stable public surface).
  • PushUnsupported — push attempted with git2-fallback disabled.

The internal mapping from gix::Error / git2::Error to these variants lives in crates/rtb-vcs/src/git/error.rs and is private. Test suites assert on variant shape (assert!(matches!(err, RepoError::CloneFailed { .. }))) rather than backend internals.

3.5 Module layout

crates/rtb-vcs/src/
├── lib.rs              # existing — release-provider re-exports
├── config.rs           # existing — ReleaseSourceConfig etc.
├── release.rs          # existing — ReleaseProvider etc.
├── http.rs             # existing
├── github.rs           # existing
├── gitlab.rs           # existing
├── ...                 # other backends
└── git/                # NEW — entire module gated on the `git` Cargo feature
    ├── mod.rs          # Repo type + InitOptions/CloneOptions re-exports
    ├── auth.rs         # pub(crate) glue calling rtb-credentials::Resolver
    ├── error.rs        # pub(crate) gix/git2 → RepoError mapping (A8)
    ├── walk.rs         # CommitWalk async stream
    ├── diff.rs         # Diff value type
    ├── blame.rs        # Blame value type
    ├── status.rs       # RepoStatus
    ├── init.rs         # init impl
    ├── clone.rs        # clone impl
    ├── checkout.rs     # CheckoutOptions + impl
    ├── fetch.rs        # FetchOptions + impl
    ├── commit.rs       # commit impl
    └── push.rs         # cfg(feature = "git2-fallback") push impl

3.6 Cargo features

[features]
# Existing release-provider feature set unchanged.
default = ["github", "gitlab", "gitea", "codeberg", "direct", "bitbucket", "git"]

# NEW: opt-out for tools that only need release-provider behaviour.
# Default-on because the framework spec promises `Repo`; tools that
# truly only want releases can `default-features = false, features = ["github"]`.
# Pulls in gix + rtb-credentials (auth glue for clone/fetch/push).
git = ["dep:gix", "dep:rtb-credentials"]

# NEW: opt-in for push / advanced ops gix can't yet do. A4 may move
# this to default-on; for now opt-in keeps the libgit2 C dep off the
# critical path for cross-compile builds.
git2-fallback = ["dep:git2", "git"]

4. Implementation outline

(Provisional — sequencing finalised in §8 once open questions resolve.)

  1. Foundation. git/mod.rs with Repo::open + Repo::status, RepoError enum, auth.rs skeleton. Async surface backed by tokio::task::spawn_blocking.
  2. Read paths. walk, diff, blame against fixture repos created via gix-testtools.
  3. Write paths (safe). fetch, checkout, commit. Dirty-tree guards.
  4. git2-fallback + push. Behind the feature flag; CI matrix includes a "fallback enabled" lane.
  5. TokenSource integration. CredentialRef blanket impl, from_env helper, docs.
  6. Docs. Component page (docs/components/rtb-vcs.md extension), concept page (docs/concepts/vcs.md), framework-spec §9 amended to match shipped shape.

5. Out of scope (deferred)

  • Submodule support. gix submodule support is incomplete; defer to v0.6+ or until gix stabilises it.
  • LFS. Large-file storage is a v1.0+ concern; tools needing LFS shell out to git-lfs themselves at v0.5.
  • Worktree management (git worktree add/remove). Useful but not blocking any v0.6 work; defer.
  • Repo write surface beyond commit/push. No merge, rebase, cherry-pick, revert at v0.5. The release-note + scaffolder consumers don't need them.
  • Custom transports. Tool authors who need HTTP-proxy-only or self-signed-cert transports configure gix directly via an escape hatch (Repo::from_gix(gix::Repository)) — TBD in A6.

6. Public-API stability impact

This is an additive slice — no existing rtb-vcs types change. The only sensitivity is around the git Cargo feature: making it default-on means existing tools that depend on rtb-vcs without default-features = false start pulling gix (≈ 2.5 MiB binary growth, ≈ 30 s extra compile). The alternative — git opt-in — keeps the existing compile profile but contradicts the framework spec's "the framework provides Repo" promise. A7 decides.

rtb-update, the only current consumer, is unaffected — it imports rtb_vcs::ReleaseProvider etc., none of which move.

7. Open questions

Resolution policy: each question stays open until the human reviewer fills in the Resolution stub below it. Implementation does not begin until A1–A7 are all marked RESOLVED. New questions raised mid-review get appended as A8+ without renumbering.

  • A1 — Sync vs async public surface. gix is blocking; tokio::task::spawn_blocking lifts it into async. Three options:
  • (a) Public surface is async fn; every method internally calls spawn_blocking. Matches the framework spec's "async surface". Adds tokio dependency to rtb-vcs (currently tokio-free for the release-provider backends that use reqwest directly).
  • (b) Public surface is blocking; callers wrap themselves. Simpler API; punts the concurrency story onto every consumer.
  • © Both — Repo is blocking, Repo::async_view() -> AsyncRepo wraps it. Doubles the API surface.
  • Resolution (2026-05-12): (a) async, internal spawn_blocking. Matches framework spec §9.2 wording. The A2 resolution already forces rtb-vcs to grow a tokio dependency (to call rtb_credentials::Resolver::resolve for auth), so the marginal cost of full async is zero. Going blocking would require a block_on dance internally just to call the auth resolver — strictly worse. Async also matches every other rtb-* crate that does I/O.

  • A2 — Auth seam location. Either:

  • (a) Lives in rtb-vcs::auth as a new TokenSource trait. rtb-vcs depends on rtb-credentials for the blanket impl TokenSource for CredentialRef. Concentrates VCS auth in one place but introduces a parallel trait.
  • (b) Reuse rtb-credentials::Resolver directly — auth-requiring Repo methods take &CredentialRef, rtb-vcs calls resolver.resolve(cref).await. No new trait.
  • Resolution (2026-05-12): (b) reuse rtb-credentials::Resolver. Per user steer "we should be utilising rtb-credentials" — Resolver::resolve already returns SecretString and walks the documented precedence chain. Introducing a parallel TokenSource trait would duplicate that surface without adding capability. §3.3 updated to match.

  • A3 — How foundational? Given the "foundation, not curated facade" framing in §1, this is really about how much of the foundational vocabulary we lay down in v0.5 vs v0.5.x point releases. Three cuts:

  • (a) Curated MVP: open + walk + diff + status. Covers release-notes + drift detection; insufficient as a foundation (no init, no clone, no commit — downstream tools building "something more" still have to import gix).
  • (b) Foundational read + light write: + init + clone + commit + blame. Covers the scaffolder's project-init flow, release-notes, drift detection, and gives downstream tools a sensible vocabulary to compose on. Doesn't include fetch/checkout/push.
  • © Full surface: + fetch + checkout + (under git2-fallback) push. Full operator-flow capability in one slice; significantly expands the test corpus.
  • Resolution (2026-05-12): © full surface. §3.2 ships in v0.5 in full. Rationale: foundational means foundational — punting fetch/checkout/push to a v0.5.x point release would force every downstream tool that needs them to either wait or re-import gix, exactly the failure mode §1 calls out. Acknowledged cost: expanded test corpus + the git2-fallback decision in A4 becomes load-bearing for push.

  • A4 — git2-fallback default. With gix push experimental, push is the only documented driver for the fallback. Either:

  • (a) Opt-in. Tools that need push enable it explicitly. Keeps the default compile profile lean (~+1.2 MiB binary).
  • (b) Default-on. Push "just works" for everyone. Pulls libgit2 (C dependency) into every RTB build.
  • Resolution (2026-05-12): (a) opt-in. libgit2 is the most common source of cross-compile pain in the Rust git ecosystem (musl + windows lanes). Pushing is a minority operation among the foundation's consumers; making everyone pay the cross-compile cost is the wrong default. Discoverability is preserved by RepoError::PushUnsupported carrying a help-laden diagnostic that names the feature flag. We re-evaluate when gix's push stabilises.
  • Obsoleted (2026-05-19): all v0.5 write paths (commit, fetch, checkout, clone-with-auth) shipped via shell-out to git. Push followed the same pattern in commit 7 — no libgit2 needed. The git2-fallback feature is intentionally not declared on rtb-vcs. RepoError::PushUnsupported stays in the #[non_exhaustive] enum for backwards compatibility but is no longer produced.

  • A5 — Test strategy. gix has gix-testtools for fixture-repo creation. Either:

  • (a) Use gix-testtools directly. Bakes a test-only gix dep; fast and idiomatic.
  • (b) Hand-roll fixture creation via shell scripts invoked from build.rs. Decouples from gix internals but reinvents what gix already provides.
  • © Pre-commit a set of .git/-bare fixture repos under tests/fixtures/. No build-time setup; slowest test feedback loop but most reproducible.
  • Resolution (2026-05-12): (a) gix-testtools. Standard ecosystem choice; gix is already in [dependencies] per A7 so the test-only dep is implicit. ©'s binary-blob commits are a separate ongoing maintenance burden we don't take on for foundational tests.

  • A6 — Repo escape hatch for advanced transports. Some tools need custom HTTP proxies, self-signed certs, or other transport tweaks Repo's API won't surface. Either:

  • (a) Expose Repo::from_gix(gix::Repository) -> Repo and Repo::into_gix(self) -> gix::Repository. Leaks gix into the public API; tightly couples downstream tools to a specific gix major version.
  • (b) No escape hatch. Tools with exotic transport needs depend on gix directly and don't use rtb-vcs::git.
  • © Per-method options via builders (CloneOptions::with_http_proxy(...), etc.). Adds API surface but keeps gix private.
  • Resolution (2026-05-12): (b) no escape hatch initially. A8 just decided to wrap the backend; offering a from_gix / into_gix escape contradicts that decision in the same release. Tools with genuinely exotic transport needs depend on gix directly and skip rtb-vcs::git — the foundation does not claim coverage of every edge case. We can add ©-style options when a concrete consumer asks for them, without breaking the public surface (option-builder additions are non-breaking).

  • A7 — git Cargo feature default. Either:

  • (a) Default-on. Matches the framework spec's "the framework provides Repo" promise; ~+2.5 MiB binary growth, ~+30s compile for tools that don't use it.
  • (b) Opt-in. Existing tools unaffected; tools that want Repo add features = ["git"]. Contradicts framework spec wording (would need an amendment).
  • Resolution (2026-05-12): (a) default-on. Per user steer "Repo should be default on". The framework spec §9 is the contract — Repo is part of the framework's promise and should not require downstream tools to opt into a feature flag to access it. The ~+2.5 MiB / ~+30s compile cost is acknowledged; tools with hard binary-size budgets can default-features = false and re-enable only the release-provider backends they need.

  • A8 — Backend error wrapping. Either:

  • (a) Surface gix::Error / git2::Error directly as RepoError::Gix(_) / RepoError::Git2(_). Cheaper to implement; downstream tools can match on backend internals.
  • (b) Wrap into semantic variants (OpenFailed, CloneFailed, FetchFailed, …) with stringified cause. Decouples the public API from gix/git2 versions; tests assert on variant shape rather than backend internals.
  • Resolution (2026-05-12): (b) wrap. Per user steer "wrapper in case we want to swap out gix at some point in the future" — a backend swap (gix → git2-primary, or some future Rust git library) must not break downstream pattern-matching. §3.4 updated to list the wrapped variants and notes the private mapping in crates/rtb-vcs/src/git/error.rs.

8. Slicing plan

Per A3 resolution = ©, seven commits with feature-flag boundaries between them so partial progress always passes CI:

  1. feat(vcs): Repo skeleton + RepoError + auth foundationgit/mod.rs with Repo::init + Repo::open + Repo::status, RepoError, auth integration with rtb-credentials per A2 resolution. Test against fixture repos per A5. (init + open go together because they're both constructors and the scaffolder needs init from day one.) Shipped 2026-05-13 (PR #42).
  2. feat(vcs): walk + diff read paths — async commit-walk stream, structured Diff. Drift-detection lands here. Originally bundled with blame, but gix 0.72 exposes blame only as the raw gix-blame re-export (no Repository::blame_file convenience) — splitting it out keeps this commit focused on the gix-Repository-method APIs and lets commit 2b decide between shelling out to git blame (deterministic, immediately available) or wrapping gix-blame directly (consistent backend, more work). Shipped 2026-05-14 (PR #49).
  3. feat(vcs): blame read pathRepo::blame(path, revspec) -> Blame. Backend chose gix-blame directly (consistent backend; the wrap-not-leak A8 contract means the choice doesn't reach the public surface — we can swap to a shell-out fallback later without breaking consumers). Shipped 2026-05-14 (PR #50).
  4. feat(vcs): clone + commit write pathsRepo::clone (anonymous in this commit; HTTPS auth via rtb-credentials deferred to commit 5b alongside fetch/push auth where the unified credentials-helper plumbing exists for all three ops), Repo::commit (path-staging + message; backend shells out to git per A8 wrap-not-leak — CloneOptions + Repo::commit signature are forward-compatible). Shipped 2026-05-15 (PR #51).
  5. feat(vcs): fetch + checkout write pathsRepo::fetch and Repo::checkout (with dirty-tree guard + force override). Both shell-out to git per the v0.5 write-path backend choice; anonymous-only in this commit, with auth landing in commit 5b alongside the auth-on-clone retrofit. Shipped 2026-05-15 (PR #52).
  6. feat(vcs): unified auth for clone+fetch (5b) — extends CloneOptions / FetchOptions with a credential: Option<CredentialRef> field + with_credential builder; resolves via Resolver::with_platform_default(); plumbs the resulting SecretString to git via an inline -c credential.helper='!f() { echo username=x-access-token; echo password=$RTB_VCS_GIT_TOKEN; }; f' snippet (process env carries the token; argv carries only the helper script). GIT_TERMINAL_PROMPT=0 prevents interactive prompts on auth failure. Push picks up the same helper in commit 7.
  7. feat(vcs): push write pathRepo::push(remote, refspec, PushOptions) via shell-out to git push. Reuses commit 6's auth wiring (-c credential.helper=... + RTB_VCS_GIT_TOKEN env). The originally-planned git2-fallback feature is dropped — the consistent shell-out architecture for all v0.5 write paths means we don't need libgit2 (see A4's obsolete note).
  8. docs: rtb-vcs v0.2 docs + framework-spec §9 alignment — component page extension, concept page, spec amendment.

9. Resolutions

Resolved 2026-05-12. The canonical resolution text lives under each open question in §7 — this section is the at-a-glance summary.

# Question Resolution
A1 Sync vs async public surface (a) async, internal spawn_blocking. A2 already forces a tokio dep so the marginal cost is zero, and block_on-ing the auth resolver internally would be strictly worse.
A2 Auth seam location (b) reuse rtb-credentials::Resolver — no parallel TokenSource trait. Auth-requiring Repo methods take &CredentialRef; Resolver::resolve already walks the documented precedence chain.
A3 How foundational? © full surfaceinit + open + clone + walk + diff + blame + status + commit + fetch + checkout + push all ship in v0.5. Punting fetch/checkout/push would force every downstream tool needing them to re-import gix, exactly the failure mode §1 calls out.
A4 git2-fallback default (a) opt-in. libgit2 is the biggest cross-compile pain point; pushing is a minority op. RepoError::PushUnsupported carries a help-laden diagnostic naming the feature flag.
A5 Test strategy (a) gix-testtools — standard ecosystem choice; gix is already in [dependencies] so the test-only dep is implicit.
A6 Repo escape hatch (b) no escape hatch initially. A from_gix/into_gix would directly contradict A8's wrap-the-backend decision; tools with exotic transport needs depend on gix directly. Option-builder additions remain non-breaking and can land later when a concrete consumer asks.
A7 git Cargo feature default (a) default-on. Framework spec §9 promises Repo; ~+2.5 MiB / ~+30s is acknowledged. Tools with hard binary-size budgets default-features = false and re-enable only the release-provider backends they need.
A8 Backend error wrapping (b) wrap into semantic variants. Decouples the public API from gix/git2 versions so we can swap backends without a public-API break. Tests assert on variant shape, not backend internals.

Downstream framework-spec amendments triggered by these resolutions

  • §9.2 (Git) — pin Repo as async (matches the existing wording), confirm git2-fallback is opt-in, name Repo::init / open / clone / walk / diff / blame / status / commit / fetch / checkout / push as the v0.5 surface.
  • §9.3 (Token resolution) — drop the app.config.auth.env|keychain|value shape (stale since v0.4.1), replace with "auth-requiring Repo methods accept a &CredentialRef and resolve through rtb_credentials::Resolver".
  • §16 (Roadmap) — flip the "0.5" pending line to a "shipped" line once the slicing plan in §8 lands.