plugins/native/znippy-plugin-git/src/replicate.rs
//! 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}"
);
}
}