remem-ai 0.6.35

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use std::path::{Path, PathBuf};
use std::time::SystemTime;
#[cfg(any(unix, windows))]
use std::{collections::VecDeque, sync::Mutex, sync::OnceLock};

use anyhow::{bail, Context, Result};

#[cfg(windows)]
use super::super::windows_security::{self, WindowsPathFingerprint};
use super::super::{checked_relative_path, LocalModelFile, LocalModelManifest, LocalModelSymlink};
use super::artifacts::canonical_regular_path;
use super::verify_manifest_symlink;

const VERIFIED_MANIFEST_CACHE_CAPACITY: usize = 8;

#[cfg(any(unix, windows))]
#[derive(Debug, Clone, PartialEq, Eq)]
struct VerifiedManifestCacheEntry {
    install_dir: PathBuf,
    manifest_sha256: String,
    fingerprints: ManifestFingerprints,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct ManifestFingerprints {
    files: Vec<ModelFileFingerprint>,
    symlinks: Vec<ModelSymlinkFingerprint>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct ModelFileFingerprint {
    relative_path: String,
    metadata: MetadataFingerprint,
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct ModelSymlinkFingerprint {
    relative_path: String,
    link_target: String,
    resolved_path: String,
    link_metadata: MetadataFingerprint,
    resolved_file: ModelFileFingerprint,
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct MetadataFingerprint {
    bytes: u64,
    modified: SystemTime,
    #[cfg(unix)]
    device: u64,
    #[cfg(unix)]
    inode: u64,
    #[cfg(unix)]
    change_time_secs: i64,
    #[cfg(unix)]
    change_time_nanos: i64,
    #[cfg(windows)]
    windows: WindowsPathFingerprint,
}

#[cfg(any(unix, windows))]
static VERIFIED_MANIFEST_CACHE: OnceLock<Mutex<VecDeque<VerifiedManifestCacheEntry>>> =
    OnceLock::new();

pub(super) fn manifest_fingerprints(
    install_dir: &Path,
    manifest: &LocalModelManifest,
) -> Result<ManifestFingerprints> {
    let files = manifest
        .files
        .iter()
        .map(|file| model_file_fingerprint(install_dir, file))
        .collect::<Result<Vec<_>>>()?;
    let symlinks = manifest
        .symlinks
        .iter()
        .map(|symlink| model_symlink_fingerprint(install_dir, manifest, symlink))
        .collect::<Result<Vec<_>>>()?;
    Ok(ManifestFingerprints { files, symlinks })
}

fn model_file_fingerprint(
    install_dir: &Path,
    file: &LocalModelFile,
) -> Result<ModelFileFingerprint> {
    let (path, metadata) = canonical_regular_path(install_dir, &file.path)?;
    if metadata.len() != file.bytes {
        bail!(
            "checksum target {} size changed: expected {} bytes, got {}",
            path.display(),
            file.bytes,
            metadata.len()
        );
    }
    Ok(ModelFileFingerprint {
        relative_path: file.path.clone(),
        metadata: metadata_fingerprint(&path, &metadata)?,
    })
}

fn model_symlink_fingerprint(
    install_dir: &Path,
    manifest: &LocalModelManifest,
    symlink: &LocalModelSymlink,
) -> Result<ModelSymlinkFingerprint> {
    verify_manifest_symlink(install_dir, manifest, symlink)?;
    let path = install_dir.join(checked_relative_path(&symlink.path)?);
    let metadata =
        std::fs::symlink_metadata(&path).with_context(|| format!("stat {}", path.display()))?;
    let resolved_file = manifest
        .files
        .iter()
        .find(|file| file.path == symlink.resolved_path)
        .with_context(|| {
            format!(
                "manifest symlink {} resolves to unlisted file {}",
                symlink.path, symlink.resolved_path
            )
        })?;
    Ok(ModelSymlinkFingerprint {
        relative_path: symlink.path.clone(),
        link_target: symlink.link_target.clone(),
        resolved_path: symlink.resolved_path.clone(),
        link_metadata: metadata_fingerprint(&path, &metadata)?,
        resolved_file: model_file_fingerprint(install_dir, resolved_file)?,
    })
}

fn metadata_fingerprint(path: &Path, metadata: &std::fs::Metadata) -> Result<MetadataFingerprint> {
    let modified = metadata
        .modified()
        .with_context(|| format!("read modified time for {}", path.display()))?;
    #[cfg(unix)]
    {
        use std::os::unix::fs::MetadataExt;
        Ok(MetadataFingerprint {
            bytes: metadata.len(),
            modified,
            device: metadata.dev(),
            inode: metadata.ino(),
            change_time_secs: metadata.ctime(),
            change_time_nanos: metadata.ctime_nsec(),
        })
    }
    #[cfg(windows)]
    {
        Ok(MetadataFingerprint {
            bytes: metadata.len(),
            modified,
            windows: windows_security::path_fingerprint(path, metadata.file_type().is_symlink())
                .with_context(|| format!("read Windows file identity for {}", path.display()))?,
        })
    }
    #[cfg(not(any(unix, windows)))]
    {
        Ok(MetadataFingerprint {
            bytes: metadata.len(),
            modified,
        })
    }
}

#[cfg(any(unix, windows))]
pub(super) fn verified_cache_contains(
    install_dir: &Path,
    manifest_sha256: &str,
    fingerprints: &ManifestFingerprints,
) -> Result<bool> {
    let cache = verified_manifest_cache()
        .lock()
        .map_err(|_| anyhow::anyhow!("local model verification cache lock poisoned"))?;
    Ok(cache.iter().any(|entry| {
        entry.install_dir == install_dir
            && entry.manifest_sha256 == manifest_sha256
            && entry.fingerprints == *fingerprints
    }))
}

#[cfg(not(any(unix, windows)))]
pub(super) fn verified_cache_contains(
    _install_dir: &Path,
    _manifest_sha256: &str,
    _fingerprints: &ManifestFingerprints,
) -> Result<bool> {
    Ok(false)
}

#[cfg(any(unix, windows))]
pub(super) fn cache_verified_manifest(
    install_dir: &Path,
    manifest_sha256: &str,
    fingerprints: ManifestFingerprints,
) -> Result<()> {
    let mut cache = verified_manifest_cache()
        .lock()
        .map_err(|_| anyhow::anyhow!("local model verification cache lock poisoned"))?;
    cache.retain(|entry| {
        entry.install_dir != install_dir || entry.manifest_sha256 != manifest_sha256
    });
    cache.push_back(VerifiedManifestCacheEntry {
        install_dir: install_dir.to_path_buf(),
        manifest_sha256: manifest_sha256.to_string(),
        fingerprints,
    });
    while cache.len() > VERIFIED_MANIFEST_CACHE_CAPACITY {
        cache.pop_front();
    }
    Ok(())
}

#[cfg(not(any(unix, windows)))]
pub(super) fn cache_verified_manifest(
    _install_dir: &Path,
    _manifest_sha256: &str,
    _fingerprints: ManifestFingerprints,
) -> Result<()> {
    Ok(())
}

#[cfg(any(unix, windows))]
fn verified_manifest_cache() -> &'static Mutex<VecDeque<VerifiedManifestCacheEntry>> {
    VERIFIED_MANIFEST_CACHE.get_or_init(|| Mutex::new(VecDeque::new()))
}