1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! The gunnar server's storage **seam**: which repository, and what has no git
//! equivalent.
//!
//! # What this crate is, after 2026-08-10
//!
//! It used to be gunnar's own storage contract — six traits, `ObjectStore`,
//! `RefStore`, `ReachIndex`, `RepoStore`, `SecretStore` and the conformance
//! [`Harness`](conformance::Harness) — written before
//! [`git_storage_trait::GitOps`] existed. Three of those six were a second
//! vocabulary for what `GitOps` now says, and a system with two storage
//! contracts has neither. So three were deleted:
//!
//! | gone | said by `GitOps` as |
//! |---|---|
//! | `ObjectStore` | `get` / `has` / `size` / `extents` / `put_pack` |
//! | `RefStore` | `refs` / `update_ref` / `put_refs` |
//! | `ReachIndex` | `reachable` |
//!
//! What is left is the part that was never about git objects:
//!
//! * [`RepoStore`] — **the repo-resolution seam**. `GitOps` is object-oriented;
//! something must still own *"which repository"*, and this already did, and its
//! own doc had already made the right promise about `git_dir()` not being a
//! promise about a filesystem.
//! * [`SecretStore`] — the SSH host key and the issuer private key. No git
//! equivalent at all, and exactly the same *"where does this live with no home
//! directory"* problem, which is why it was here in the first place. **Since
//! 2026-08-10 no storage engine implements it**: the server's secrets are rows
//! in `control.redb` (`gunnar_core::secrets`), and what is left here is the
//! trait, the in-memory control, and the legacy Arrow log the migration reads
//! from. It is no longer reachable through [`RepoStore`].
//! * [`conformance`] — **one suite, run by both `GitOps` backends.** This is the
//! deliverable. Two implementations exist in order to be compared, and both
//! running the same suite is what makes a comparison a comparison rather than
//! two anecdotes.
//! * [`refname`] — the reference-name rules, with no store attached. `GitOps`
//! takes `&str` and validates nothing; these are the caller's rules, and one of
//! them is a path-traversal boundary.
//! # 🔴 What LEFT on 2026-08-10, and why this crate is now Arrow-free
//!
//! `arrow_log` (1682 lines) and `history` (857) — the Arrow IPC log and its
//! reader — **moved to `gunnar-core`** as `gunnar_core::arrow_log` and
//! `gunnar_core::history`. They were not contracts. They were one concrete
//! implementation of one of them, plus a reader of a file format nothing writes
//! any more, and they were the only reason this crate linked `arrow-ipc`,
//! `arrow-array` and `arrow-schema`.
//!
//! That matters beyond tidiness, and the reason is who consumes this crate.
//! **Both `GitOps` backends dev-depend on it to run [`conformance`], from two
//! other repositories** — `znippy/plugins/native/znippy-plugin-git` and
//! `edda/crates/storage-git-gix`. A conformance crate that drags three Arrow
//! crates into znippy's dev graph is the same mistake `git-storage-trait` being
//! `anyhow`-only exists to avoid: the contract must not make either implementer
//! resolve the other's dependencies. After the move the whole normal graph is
//! `git-storage-trait`, `gix-hash`, `anyhow`, `thiserror`, `tracing`.
//!
//! They went to `gunnar-core` rather than to a crate of their own because
//! `gunnar-core` is where their only remaining caller is — the migration in
//! `gunnar_core::secrets` reads the legacy log exactly once per secret — and
//! because a new workspace member would have moved the publish, fmt and
//! appliance-ledger guards for two files that are scheduled to be deleted.
//! They stayed **together**: `history::live_mark` uses `arrow_log`'s framing
//! scan and shares its `pub(crate)` column constants, and splitting a reader
//! from its writer is exactly the twinning `arrow_log.rs:217` warns about.
//!
//! # There is no database — and where that stopped being true
//!
//! Secrets *were* stored as a log-structured table: one write is one
//! `RecordBatch`, which is one framed Arrow IPC message, so all-or-nothing
//! across a write was a property of the *framing* rather than of a transaction
//! manager; see `gunnar_core::arrow_log` and the Arrow-IPC ref log. That was decided when
//! gunnar had no embedded database to put them in. It has had one since
//! The certificate-identity plan — the same engine the znippy arm already carried — and a key/value
//! pair with an atomic get-or-create is what a key/value store is for. The
//! framing argument still describes that module exactly; it no longer describes
//! where the server keeps its host key.
//!
//! # Verification
//!
//! [`conformance`] is one suite run against a backend, both hash kinds, and
//! against deliberately broken backends that it is asserted to catch. The last
//! of those is what makes it worth anything.
pub use ;
pub use ;
pub use RepoStore;
pub use ;
// Re-exported so downstream crates can name the types in these signatures
// without taking their own dependency on the exact versions gunnar pins.
//
// `git_storage_trait` is here for the reason the other two are, and it matters
// more: `RepoStore::git()` returns `Arc<dyn GitServe>`, so every consumer of this
// crate names that trait, and a consumer resolving a *different* copy of it
// would get a type error with no obvious cause.
pub use git_storage_trait;
pub use gix_hash;