haematite 0.6.2

Content-addressed, branchable, actor-native storage engine
Documentation
//! `#[doc(hidden)]` test-support projections on [`Database`].
//!
//! These methods exist ONLY so the e2e suites can observe internal state
//! (promise ballots, stored stamp envelopes, live epochs) or force faults
//! (shard shutdown) through the public handle without widening the production
//! API. None of them are part of the supported surface; they live in this
//! sibling file to keep `db.rs` focused on the production API.

use super::Database;

impl Database {
    /// Test-support: stop every shard actor so subsequent storage commands fail.
    ///
    /// Used by the 2a-4 receiver tests to force a genuine apply fault (a
    /// disconnected/timed-out shard reply, distinct from a CAS mismatch) and prove
    /// it surfaces as `Rejected(ApplyError)`. Not part of the production API.
    #[doc(hidden)]
    pub fn shutdown_shards_for_test(&self) {
        for handle in self.router.materialised_handles() {
            drop(handle.shutdown(self.timeout));
        }
    }

    /// Test-support: read a shard's current durably-`promised` ballot (AA-3-2).
    ///
    /// Used by the election e2e tests to assert the swing voter's monotonic
    /// `promised` reflects the MAX winner (the single-live-owner invariant). Not
    /// part of the production API.
    #[doc(hidden)]
    #[must_use]
    pub fn promised_ballot_for_test(&self, shard_id: usize) -> Option<crate::sync::Ballot> {
        let handle = self.router.handle_for_shard(shard_id).ok()?;
        handle
            .read_promise_state(self.timeout)
            .ok()
            .map(|state| state.promised)
    }

    /// Test-support: durably advance a shard's `promised` ballot (AA-3-2), the same
    /// way an inbound `Prepare` would, WITHOUT needing a live election/transport.
    ///
    /// Returns `true` iff the ballot strictly exceeded the prior `promised` (so it
    /// was recorded). Used by the A1b receiver fence test to put a shard's
    /// `promised` above an inbound batch's epoch so the batch is fenced. Not part of
    /// the production API.
    #[doc(hidden)]
    pub fn record_promise_for_test(&self, shard_id: usize, ballot: crate::sync::Ballot) -> bool {
        let Ok(handle) = self.router.handle_for_shard(shard_id) else {
            return false;
        };
        matches!(
            handle.record_promise(ballot, self.timeout),
            Ok(crate::shard::actor::RecordPromiseOutcome::Promised)
        )
    }

    /// Test-support: decode the committed commit-stamp `(epoch, seq)` a node
    /// stored for `key` (AA-3-4a). Reads the RAW stored envelope (stamp NOT
    /// stripped) and decodes its stamp, so a test can prove every replica stored
    /// the IDENTICAL owner-assigned stamp. Returns `None` if the key is absent or
    /// was stored without a stamp envelope.
    #[doc(hidden)]
    #[must_use]
    pub fn stored_stamp_for_test(&self, key: &[u8]) -> Option<crate::sync::Stamp> {
        let handle = self.router.handle_for(key).ok()?;
        let raw = handle.get_raw(key.to_vec(), self.timeout).ok()??;
        crate::ttl::entry::StampedEntry::decode(&raw)
            .ok()
            .flatten()
            .map(|entry| entry.stamp().clone())
    }

    /// Test-support (AA-3-4b): `Some(true)` if `key` is stored as a STAMPED
    /// TOMBSTONE on this node, `Some(false)` if it is stored as a stamped value,
    /// `None` if absent or stored without a stamp envelope. Reads the RAW stored
    /// envelope so a test can prove a committed delete landed a tombstone (not a
    /// removal) on a peer, and that R-TOMB kept it through a sweep.
    #[doc(hidden)]
    #[must_use]
    pub fn stored_is_tombstone_for_test(&self, key: &[u8]) -> Option<bool> {
        let handle = self.router.handle_for(key).ok()?;
        let raw = handle.get_raw(key.to_vec(), self.timeout).ok()??;
        crate::ttl::entry::StampedEntry::decode(&raw)
            .ok()
            .flatten()
            .map(|entry| entry.is_tombstone())
    }

    /// Test-support: read a shard's IN-MEMORY `live_epoch` (R-LE, AA-3-4a). This
    /// is `Ballot::bottom()` until a successful `acquire_shard` THIS lifetime, and
    /// is NEVER seeded from the disk-recovered `owner_epoch`.
    #[doc(hidden)]
    #[must_use]
    pub fn live_epoch_for_test(&self, shard_id: usize) -> crate::sync::Ballot {
        self.owner_stamps.live_epoch(shard_id)
    }

    /// Test-support: the commit stamp the NEXT write to `shard_id` would draw,
    /// WITHOUT advancing the counter (R-LE / R-SEQ peek). Used by the crash gate
    /// to prove a recovered owner would stamp `bottom`, never the recovered `e'`.
    #[doc(hidden)]
    #[must_use]
    pub fn next_stamp_for_test(&self, shard_id: usize) -> crate::sync::Stamp {
        self.owner_stamps.peek_stamp(shard_id)
    }
}