znippy-plugin-git 0.1.1

Git object-store metadata plugin for znippy (native builtin — no WASM). Carries the reserved oid / commit-graph / reachability sub-indexes.
Documentation
//! Replication — sending a repository's stored bytes out to another gunnar.
//!
//! # THIS MODULE IS DELIBERATELY EMPTY
//!
//! [`replicate_out`] is a signature, a contract and a `todo!()`. It is a
//! placeholder to be filled in **tomorrow (from 2026-08-07)** and it is not a
//! forgotten stub: the shape below is the decision that was made today, and the
//! wire is the part that was not. [`tests::replicate_out_is_deliberately_empty`]
//! asserts the emptiness, so that if someone half-implements this the test that
//! says "still empty" fails and has to be removed on purpose.
//!
//! **Nothing here opens a socket, and no protocol is designed here.** Do not add
//! one to this file as a convenience — a peer transport is its own decision.
//!
//! ## What was decided, and is therefore in the signature
//!
//! * **STORE-side.** Replication is a command on the store that holds the bytes.
//!   It is not a read path and not a plugin hook; the receiving gunnar is a peer,
//!   not a client of a lookup.
//! * **No index travels with it.** Not the Arrow object index, not the stree oid
//!   section, not the redb tail. The receiver has its own
//!   [`ObjectReadStack`](crate::read_stack::ObjectReadStack) and rebuilds its own
//!   projection from what it receives — an index is a derivation, and shipping a
//!   derivation is how two copies of a repository come to disagree about the
//!   same bytes. This is the reason [`ReplicationReceipt`] reports objects and
//!   bytes and says nothing about rows or generations of any index.
//! * **One transaction, per repository.** The unit is one repository's archive
//!   between two generation marks: it either lands whole at the peer or it does
//!   not land. There is no partial-repository state to reconcile, because a
//!   half-replicated object set is exactly the thing whose repair costs more
//!   than the resend.
//! * **Append-only makes it resumable, later.** Because the archive is
//!   append-only, `from_generation` is a watermark and not a diff: everything
//!   above it is new and nothing below it has changed. A resumed transaction is
//!   therefore a fresh one with a higher watermark. That property is why the
//!   parameter is a single `u64` and not a manifest.

use std::path::Path;

use anyhow::Result;

/// Another gunnar. Deliberately just a name and an address: what an endpoint
/// string means is the transport's business, and the transport is not decided.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Peer {
    /// Stable identity of the peer, for logs and for the receipt.
    pub name: String,
    /// Where to reach it. Opaque to this crate.
    pub endpoint: String,
}

/// What a completed replication transaction reports.
///
/// Counts of things that actually moved — objects and bytes — and the watermark
/// the next transaction should start from. Nothing about indexes: none travelled.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReplicationReceipt {
    pub peer: String,
    /// Objects sent. Zero is a legitimate, successful outcome: the peer was
    /// already current.
    pub objects_sent: u64,
    /// Stored bytes sent, as stored — a delta chunk counts as its delta.
    pub bytes_sent: u64,
    /// The watermark this transaction started from.
    pub from_generation: u64,
    /// The watermark to pass as `from_generation` next time.
    pub to_generation: u64,
}

/// **Send one repository's new objects to `peer`. NOT IMPLEMENTED — see the
/// module documentation.**
///
/// A store-side, all-or-nothing transaction over the bytes of `archive` above
/// `from_generation`. No index of any kind is transmitted; the peer derives its
/// own.
///
/// # Panics
///
/// Always. This is a placeholder with a settled signature and no body yet.
pub fn replicate_out(
    _archive: &Path,
    _peer: &Peer,
    _from_generation: u64,
) -> Result<ReplicationReceipt> {
    todo!(
        "replication transaction: deliberately empty, to be implemented tomorrow. \
         The signature is settled (store-side, per repository, watermarked, no index \
         travels); the transport is not. Do not open a socket from this file without \
         deciding the protocol first."
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    /// **The emptiness is asserted, so it cannot be mistaken for an oversight.**
    ///
    /// If this test starts failing, `replicate_out` grew a body — which is the
    /// intended future. Delete this test in the same commit that does it, on
    /// purpose.
    ///
    /// Seen RED by replacing the `todo!()` with `Ok(ReplicationReceipt { … })`:
    /// "the replication placeholder no longer panics — if it was implemented,
    /// delete this test in the same commit: ()". Restored.
    #[test]
    fn replicate_out_is_deliberately_empty() {
        let peer = Peer {
            name: "gunnar-2".into(),
            endpoint: "unspecified".into(),
        };
        let outcome = std::panic::catch_unwind(|| {
            let _ = replicate_out(Path::new("/nonexistent.znippy"), &peer, 0);
        });
        let payload = outcome.expect_err(
            "the replication placeholder no longer panics — if it was implemented, delete this \
             test in the same commit",
        );
        let msg = payload
            .downcast_ref::<String>()
            .cloned()
            .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
            .unwrap_or_default();
        assert!(
            msg.contains("deliberately empty"),
            "it panics, but not as the placeholder: {msg}"
        );
    }
}