Skip to main content

ic_backup/ops/persistence/consistency/
mod.rs

1//! Bounded immutable original-plan consistency requirement retention; no fence authority.
2
3use super::{
4    BackupLayoutGuard, JournalLock, JournalLockError, OperationPlanPersistenceError,
5    PersistenceError, create_json_durable, read_json, read_operation_plan,
6};
7use crate::model::{
8    artifacts::ArtifactChecksumRecord,
9    consistency::{
10        ConsistencyRequirementError, ConsistencyRequirementRecord,
11        MAX_CONSISTENCY_REQUIREMENT_BYTES,
12    },
13    operation_plan::OperationPlanRecord,
14};
15use thiserror::Error;
16
17/// Durably create fixed `consistency-requirement.json` without replacing original evidence.
18///
19/// Requires the exact original plan already retained under layout exclusion. This
20/// persists the reviewed guarantee, not an active fence, paid allowance or release permit.
21/// # Errors
22/// Rejects mismatched/missing original plan, excessive bytes, unsafe/existing paths and IO/locks.
23pub fn create_consistency_requirement(
24    layout: &BackupLayoutGuard,
25    plan: &OperationPlanRecord,
26    record: &ConsistencyRequirementRecord,
27) -> Result<(), ConsistencyPersistenceError> {
28    record.validate_plan(plan)?;
29    read_operation_plan(layout, &plan.digest())?;
30    let path = layout.root().join("consistency-requirement.json");
31    let _lock = JournalLock::acquire(&path)?;
32    check_size(record)?;
33    create_json_durable(&path, record)?;
34    Ok(())
35}
36/// Read bounded validated original requirement under its exact expected digest and retained plan.
37///
38/// Lost local creation replies reconcile by this read; never overwrite/downgrade an
39/// existing guarantee or recover a fence/consumed authority from absence.
40/// # Errors
41/// Rejects unsafe/missing/oversized/invalid declarations, original plan or requirement mismatch.
42pub fn read_consistency_requirement(
43    layout: &BackupLayoutGuard,
44    plan: &OperationPlanRecord,
45    expected: &ArtifactChecksumRecord,
46) -> Result<ConsistencyRequirementRecord, ConsistencyPersistenceError> {
47    read_operation_plan(layout, &plan.digest())?;
48    let path = layout.root().join("consistency-requirement.json");
49    let _lock = JournalLock::acquire(&path)?;
50    let record: ConsistencyRequirementRecord = read_json(&path, MAX_CONSISTENCY_REQUIREMENT_BYTES)?;
51    check_size(&record)?;
52    record.validate_plan(plan)?;
53    if &record.digest() != expected {
54        return Err(ConsistencyPersistenceError::DigestMismatch);
55    }
56    Ok(record)
57}
58fn check_size(record: &ConsistencyRequirementRecord) -> Result<(), PersistenceError> {
59    if serde_json::to_vec_pretty(record)?.len() as u64 > MAX_CONSISTENCY_REQUIREMENT_BYTES {
60        return Err(PersistenceError::RecordTooLarge {
61            limit: MAX_CONSISTENCY_REQUIREMENT_BYTES,
62        });
63    }
64    Ok(())
65}
66/// Typed exact retained requirement or bounded local storage denial.
67#[derive(Debug, Error)]
68pub enum ConsistencyPersistenceError {
69    /// Retained guarantee differs from the exact originally expected requirement.
70    #[error("consistency requirement digest mismatch")]
71    DigestMismatch,
72    /// Original full plan identity differs.
73    #[error(transparent)]
74    Requirement(#[from] ConsistencyRequirementError),
75    /// Original plan is not admitted from the held layout.
76    #[error(transparent)]
77    Plan(#[from] OperationPlanPersistenceError),
78    /// Layout/journal exclusion failed.
79    #[error(transparent)]
80    Lock(#[from] JournalLockError),
81    /// Bounded JSON or durable filesystem access failed.
82    #[error(transparent)]
83    Persistence(#[from] PersistenceError),
84}
85
86#[cfg(all(test, unix))]
87mod tests;