Skip to main content

ic_backup/ops/persistence/operation_plan/
mod.rs

1//! Immutable bounded operation plan publication and original local intent admission.
2
3use super::{
4    BackupLayoutGuard, JournalLock, JournalLockError, PersistenceError, create_json_durable,
5    read_json,
6};
7use crate::model::{
8    artifacts::ArtifactChecksumRecord,
9    operation_plan::{MAX_OPERATION_PLAN_BYTES, OperationPlanRecord},
10};
11use thiserror::Error;
12
13/// Durably create fixed `operation-plan.json` without replacing prior evidence.
14///
15/// The embedded inventory/graph are the declaration's original bindings; separate
16/// retained inventory/graph files are not adopted or rewritten by this operation.
17///
18/// # Errors
19/// Rejects excessive canonical bytes, unsafe/existing entries, replaced roots and IO/locks.
20pub fn create_operation_plan(
21    layout: &BackupLayoutGuard,
22    record: &OperationPlanRecord,
23) -> Result<(), OperationPlanPersistenceError> {
24    layout.check_root()?;
25    let path = layout.root().join("operation-plan.json");
26    let _lock = JournalLock::acquire(&path)?;
27    check_size(record)?;
28    create_json_durable(&path, record)?;
29    Ok(())
30}
31
32/// Admit bounded validated local plan under its exact original expected intent digest.
33///
34/// Lost creation responses reconcile through this exact local read. This observes
35/// no remote authority, does not create/reset journals and grants no dispatch permit.
36///
37/// # Errors
38/// Rejects unsafe/missing/oversized/invalid declarations, digest mismatch and ownership failures.
39pub fn read_operation_plan(
40    layout: &BackupLayoutGuard,
41    expected: &ArtifactChecksumRecord,
42) -> Result<OperationPlanRecord, OperationPlanPersistenceError> {
43    layout.check_root()?;
44    let path = layout.root().join("operation-plan.json");
45    let _lock = JournalLock::acquire(&path)?;
46    let record: OperationPlanRecord = read_json(&path, MAX_OPERATION_PLAN_BYTES)?;
47    check_size(&record)?;
48    if &record.digest() != expected {
49        return Err(OperationPlanPersistenceError::DigestMismatch);
50    }
51    Ok(record)
52}
53fn check_size(record: &OperationPlanRecord) -> Result<(), PersistenceError> {
54    super::json::check_json_size(record, MAX_OPERATION_PLAN_BYTES)
55}
56
57/// Typed original operation-plan identity or bounded immutable local admission failure.
58#[derive(Debug, Error)]
59pub enum OperationPlanPersistenceError {
60    /// Retained declaration differs from the exact original selected intent digest.
61    #[error("operation plan digest mismatch")]
62    DigestMismatch,
63    /// Cooperating layout/journal ownership failed.
64    #[error(transparent)]
65    Lock(#[from] JournalLockError),
66    /// Bounded JSON, model admission or durable filesystem access failed.
67    #[error(transparent)]
68    Persistence(#[from] PersistenceError),
69}
70
71#[cfg(all(test, unix))]
72mod tests;