haematite 0.6.2

Content-addressed, branchable, actor-native storage engine
Documentation
//! FENCE-CHAIN r8 R4d — the D2 ownership-transfer pin (r5 F4), verbatim.
//!
//! The interleaving: creator A creates the directory; competitor B takes the lock
//! first, fully initialises, closes; A takes the lock later and is refused at
//! `ensure_uninitialised`. A's created-this-call bit must NOT delete B's completed
//! database. The `inject_foreign_config_next_create` seam reproduces exactly this
//! window deterministically — a foreign completed config appears after A creates
//! the directory but before A reaches `ensure_uninitialised`.

use std::error::Error;

use super::startup::{FOREIGN_SHARD_COUNT, inject_foreign_config_next_create};
use super::{Database, DatabaseConfig, DatabaseError};

type TestResult = Result<(), Box<dyn Error>>;

fn config(data_dir: &std::path::Path, shard_count: usize) -> DatabaseConfig {
    DatabaseConfig {
        data_dir: data_dir.to_path_buf(),
        shard_count,
        distributed: None,
    }
}

/// A create that created the directory but is then refused at
/// `ensure_uninitialised` (a competitor completed initialisation in the window)
/// MUST leave the competitor's database byte-identical — never deleted, never
/// rewritten.
#[test]
fn ownership_transfer_refusal_never_deletes_the_winners_database() -> TestResult {
    let temp = tempfile::tempdir()?;
    let data_dir = temp.path().join("db");
    assert!(
        !data_dir.exists(),
        "the directory must be absent so created_dir is true"
    );

    // Arm the F4 interleaving: this create (shard_count 1) will create the dir,
    // then observe a foreign completed config (shard_count FOREIGN_SHARD_COUNT)
    // and be refused at ensure_uninitialised.
    inject_foreign_config_next_create();
    let refused = Database::create(config(&data_dir, 1));

    assert!(
        matches!(
            refused,
            Err(DatabaseError::DataDirAlreadyInitialised { .. })
        ),
        "the created-the-dir call must be refused at ensure_uninitialised, got {refused:?}"
    );

    // The competitor's database survives: the directory still exists and opens as
    // the FOREIGN config (proving A neither deleted nor rewrote it).
    assert!(
        data_dir.exists(),
        "F4: the refused creator must NOT delete the winner's directory"
    );
    let opened = Database::open(&data_dir)?;
    assert_eq!(
        opened.shard_count(),
        FOREIGN_SHARD_COUNT,
        "the winner's config must survive byte-identically (foreign shard_count intact)"
    );
    Ok(())
}