haematite 0.7.0

Content-addressed, branchable, actor-native storage engine
Documentation
//! The migration report (§4.2, Q4): the operator's artifact for one
//! `migrate_chunking` run.
//!
//! Serde-serialisable so the `haem migrate-chunking` CLI can emit JSON
//! alongside the human rendering, mirroring `VacuumReport`.

use std::path::PathBuf;

use serde::Serialize;

/// The cross-lane advisory every report carries.
///
/// Doc-1's vacuum SHOULD run before migration on a garbage-heavy store (§7.2 —
/// reclaim garbage before rewriting live data). This gates the OPERATIONAL run,
/// NOT this build; the migration itself neither invokes nor requires the vacuum.
pub const VACUUM_BEFORE_MIGRATE: &str = "advisory (cross-lane, sweep lane): run `haem vacuum --sweep` BEFORE migrating a \
     garbage-heavy store — reclaim unreachable nodes before re-chunking live data (§7.2). \
     This does not gate the build; it is the recommended operational sequencing.";

/// The manifest-bracket cross-lane note.
///
/// §4.2 step 4 brackets the branch-head rewrite in doc-1's manifest `Updating`
/// lifecycle "where a manifest exists". The manifest WRITE authority ships with
/// the sweep lane and is absent here, so branch heads are installed via the
/// record store's own atomic install and the manifest bracket is named as the
/// dependency, not improvised.
pub const MANIFEST_BRACKET: &str = "cross-lane (sweep lane): branch-head rewrites use the record store's atomic install; the \
     doc-1 manifest `Updating` bracket applies where a manifest exists — the manifest write \
     authority lands with the sweep lane.";

/// Which way the transaction ran.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MigrationDirection {
    /// v1 → v2 (the count-target rule to the byte-aware rule).
    Forward,
    /// v2 → v1 (the durable reverse run; an abort or an explicit downgrade).
    Abort,
}

/// What the run did, at a glance.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MigrationOutcome {
    /// The directory was already fully in the requested target format; nothing
    /// moved (idempotent no-op).
    AlreadyInTargetFormat,
    /// A forward migration completed: the directory is now v2, unfenced.
    MigratedToV2,
    /// A reverse run completed: the directory is now v1, unfenced.
    AbortedToV1,
}

/// The operator's artifact for one `migrate_chunking` run.
#[derive(Debug, Serialize)]
pub struct MigrationReport {
    pub data_dir: PathBuf,
    pub outcome: MigrationOutcome,
    pub direction: MigrationDirection,
    /// The fence's source/target policy labels for this run.
    pub source_policy: String,
    pub target_policy: String,
    pub shard_count: usize,
    /// Shards re-chunked and durably committed under the target policy.
    pub shards_rebuilt: usize,
    /// Shards already target-shaped (idempotent rerun / already-migrated).
    pub shards_unchanged: usize,
    /// Shards absent or empty (lazy / never-written).
    pub shards_empty: usize,
    /// Branch HEADs re-chunked and record-advanced.
    pub branch_heads_rebuilt: usize,
    /// Branch HEADs already target-shaped (no advance).
    pub branch_heads_unchanged: usize,
    pub duration_ms: u128,
    /// Cross-lane operational advisory ([`VACUUM_BEFORE_MIGRATE`]).
    pub vacuum_before_migrate: &'static str,
    /// Cross-lane manifest-bracket note ([`MANIFEST_BRACKET`]).
    pub manifest_bracket: &'static str,
}