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