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    super::json::check_json_size(record, MAX_CONSISTENCY_REQUIREMENT_BYTES)
60}
61/// Typed exact retained requirement or bounded local storage denial.
62#[derive(Debug, Error)]
63pub enum ConsistencyPersistenceError {
64    /// Retained guarantee differs from the exact originally expected requirement.
65    #[error("consistency requirement digest mismatch")]
66    DigestMismatch,
67    /// Original full plan identity differs.
68    #[error(transparent)]
69    Requirement(#[from] ConsistencyRequirementError),
70    /// Original plan is not admitted from the held layout.
71    #[error(transparent)]
72    Plan(#[from] OperationPlanPersistenceError),
73    /// Layout/journal exclusion failed.
74    #[error(transparent)]
75    Lock(#[from] JournalLockError),
76    /// Bounded JSON or durable filesystem access failed.
77    #[error(transparent)]
78    Persistence(#[from] PersistenceError),
79}
80
81#[cfg(all(test, unix))]
82mod tests;