Skip to main content

ic_backup/ops/persistence/inventory/
mod.rs

1//! Immutable bounded inventory publication and exact local declared-identity admission.
2
3use super::{
4    BackupLayoutGuard, JournalLock, JournalLockError, PersistenceError, create_json_durable,
5    read_json,
6};
7use crate::model::{
8    artifacts::ArtifactChecksumRecord,
9    inventory::{InventoryRecord, MAX_INVENTORY_BYTES},
10};
11use thiserror::Error;
12
13/// Durably create `inventory.json` under held layout exclusion without replacing evidence.
14///
15/// # Errors
16/// Rejects excessive canonical bytes, existing/unsafe entries, replaced roots and IO/lock failures.
17pub fn create_inventory(
18    layout: &BackupLayoutGuard,
19    record: &InventoryRecord,
20) -> Result<(), InventoryError> {
21    layout.check_root()?;
22    let path = layout.root().join("inventory.json");
23    let _lock = JournalLock::acquire(&path)?;
24    check_size(record)?;
25    create_json_durable(&path, record)?;
26    Ok(())
27}
28
29/// Read bounded validated local inventory under its original expected canonical digest.
30///
31/// This observes no remote membership or state. A failed/lost creation response
32/// may be reconciled by reading the exact declared digest, never overwriting it.
33///
34/// # Errors
35/// Rejects unsafe/missing/oversized records, invalid declarations, digest mismatch and locks.
36pub fn read_inventory(
37    layout: &BackupLayoutGuard,
38    expected: &ArtifactChecksumRecord,
39) -> Result<InventoryRecord, InventoryError> {
40    layout.check_root()?;
41    let path = layout.root().join("inventory.json");
42    let _lock = JournalLock::acquire(&path)?;
43    let record: InventoryRecord = read_json(&path, MAX_INVENTORY_BYTES)?;
44    check_size(&record)?;
45    if &record.digest() != expected {
46        return Err(InventoryError::DigestMismatch);
47    }
48    Ok(record)
49}
50
51fn check_size(record: &InventoryRecord) -> Result<(), PersistenceError> {
52    if serde_json::to_vec_pretty(record)?.len() as u64 > MAX_INVENTORY_BYTES {
53        return Err(PersistenceError::RecordTooLarge {
54            limit: MAX_INVENTORY_BYTES,
55        });
56    }
57    Ok(())
58}
59
60/// Typed exact declared-identity or durable local inventory admission failure.
61#[derive(Debug, Error)]
62pub enum InventoryError {
63    /// Canonical retained declaration differs from its original selected digest.
64    #[error("inventory digest mismatch")]
65    DigestMismatch,
66    /// Exclusive layout/journal ownership failed.
67    #[error(transparent)]
68    Lock(#[from] JournalLockError),
69    /// Bounded JSON, model admission or durable filesystem access failed.
70    #[error(transparent)]
71    Persistence(#[from] PersistenceError),
72}
73
74#[cfg(all(test, unix))]
75mod tests;