gunnar-store 1.6.0

The gunnar server's storage seam: RepoStore (which repository), SecretStore (what has no git equivalent), and the one conformance suite both GitOps backends run
Documentation
//! 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.

#![warn(missing_docs)]
#![warn(clippy::all)]
#![doc(
    html_logo_url = "https://gunnar.rs/download/gunnar.webp",
    html_favicon_url = "https://gunnar.rs/download/favicon.svg"
)]

#[cfg(feature = "conformance")]
pub mod conformance;
pub mod error;
pub mod mem;
pub mod object;
pub mod refname;
pub mod repo;
pub mod secret;

pub use error::{Error, Result};
pub use refname::{DEFAULT_BRANCH, HEAD, MAX_REF_COMPONENT_BYTES, MAX_SYMREF_DEPTH};
pub use repo::RepoStore;
pub use secret::{
    SecretStore, SecretStoreExt, ACTOR_TICKET_KEY, FORGE_ATTEST_KEY, ISSUER_PRIVATE_KEY,
    PROVENANCE_KEY, SSH_HOST_KEY, TRANSPARENCY_LOG_KEY, WEBHOOK_KEY,
};

// 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;