sprawling-storage 0.0.8

The append-only, hash-chained Ledger of a sprawling city, its content store, checkpoints and worktrees.
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Copyright (c) 2026 2youg1 and the sprawling contributors

//! Worktree leases: a held tree and its measure.

use std::path::{Path, PathBuf};

use kernel::event::record::WorktreeOpened;
use kernel::{ByteLen, Payload};

use crate::error::StorageError;

use super::name::WorktreeName;

/// A live tree: what it is called, where it is, and what it costs.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorktreeLease {
    pub(crate) name: WorktreeName,
    pub(crate) path: PathBuf,
    pub(crate) disk: ByteLen,
    pub(crate) work: FileWork,
}

/// What putting a tree in place cost the filesystem, counted by the code
/// that did the work (`crates/storage/spec/Worktree.lean` ยง8-31).
///
/// A wall-clock reading of the same work moves with the machine and its
/// real-time scanner, which waits on every file created; these four
/// numbers do not, so they, and not the clock, are what a regression is
/// judged by. Directories made along the way are not counted: the
/// scanner waits on files.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct FileWork {
    /// Files written where no file stood.
    pub created: u64,
    /// Files written over because they differed from what the tree
    /// should hold.
    pub rewritten: u64,
    /// Entries taken away because the tree should not hold them; a
    /// directory counts once, with whatever it held.
    pub removed: u64,
    /// Directory entries read.
    pub walked: u64,
}

impl WorktreeLease {
    #[must_use]
    pub fn name(&self) -> &WorktreeName {
        &self.name
    }

    /// The root a run in this node writes against.
    #[must_use]
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// What the tree occupied when it was opened. `status` reports this,
    /// which is why it is measured rather than estimated.
    #[must_use]
    pub fn disk(&self) -> ByteLen {
        self.disk
    }

    /// What claiming this tree cost the filesystem.
    #[must_use]
    pub fn work(&self) -> FileWork {
        self.work
    }

    /// The `worktree_opened` payload: a name and a size, no path. An
    /// absolute path is a fact about this machine, and a history that
    /// carries one does not survive being moved to another.
    ///
    /// # Errors
    /// Propagates the payload's refusal to hold what it was given.
    pub fn opened_payload(&self) -> Result<Payload, StorageError> {
        Payload::of(&WorktreeOpened {
            name: self.name.as_str().to_owned(),
            disk_bytes: self.disk,
        })
        .map_err(|source| StorageError::Draft { source })
    }
}

#[cfg(test)]
#[allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::indexing_slicing,
    reason = "test code"
)]
mod tests {
    use super::super::trees::Worktrees;
    use serde_json::Value;

    use super::*;
    use crate::checkpoint::Checkpoint;
    use kernel::TimeMs;
    use kernel::consts_policy::WORKTREE_MAX_BYTES;
    fn city(dir: &Path) -> Worktrees {
        std::fs::create_dir_all(dir.join("lab")).unwrap();
        std::fs::write(dir.join("lab").join("notes.md"), b"first\n").unwrap();
        let mut checkpoint = Checkpoint::open(dir).unwrap();
        checkpoint
            .ensure_base(
                &["lab".to_owned()],
                TimeMs::new(1_000),
                &super::super::trees::tests::owner(),
            )
            .unwrap();
        Worktrees::open(dir).unwrap()
    }
    fn name(raw: &str) -> WorktreeName {
        WorktreeName::parse(raw).unwrap()
    }

    #[test]
    fn the_tree_is_measured_rather_than_estimated() {
        let dir = tempfile::tempdir().unwrap();
        let trees = city(dir.path());
        let lease = trees.claim(&name("node-1"), &[]).unwrap();
        assert!(
            lease.disk().get() >= 6,
            "the checked-out file has six bytes"
        );
        assert!(lease.disk().get() < WORKTREE_MAX_BYTES);

        let payload = lease.opened_payload().unwrap();
        let map = payload.as_map();
        assert_eq!(map.get("name").and_then(Value::as_str), Some("node-1"));
        assert!(map.contains_key("disk_bytes"));
        assert!(
            !map.contains_key("path"),
            "an absolute path is a fact about this machine, not about the city"
        );
    }
}