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