Skip to main content

ic_backup/ops/persistence/effect_graph/
mod.rs

1//! Immutable bounded graph publication and exact original local graph admission.
2
3use super::{
4    BackupLayoutGuard, JournalLock, JournalLockError, PersistenceError, create_json_durable,
5    read_json,
6};
7use crate::model::{
8    artifacts::ArtifactChecksumRecord,
9    effect_graph::{EffectGraphRecord, MAX_EFFECT_GRAPH_BYTES},
10};
11use thiserror::Error;
12
13/// Durably create `effect-graph.json` under layout exclusion without replacing evidence.
14///
15/// # Errors
16/// Rejects excessive canonical bytes, existing/unsafe entries, replaced roots and IO/locks.
17pub fn create_effect_graph(
18    layout: &BackupLayoutGuard,
19    record: &EffectGraphRecord,
20) -> Result<(), EffectGraphPersistenceError> {
21    layout.check_root()?;
22    let path = layout.root().join("effect-graph.json");
23    let _lock = JournalLock::acquire(&path)?;
24    check_size(record)?;
25    create_json_durable(&path, record)?;
26    Ok(())
27}
28
29/// Admit retained bounded graph under its original exact expected digest, using local IO only.
30///
31/// Lost creation replies reconcile by reading the exact graph. This neither checks
32/// remote completion nor grants effects, fresh authority or plan acceptance.
33///
34/// # Errors
35/// Rejects unsafe/missing/oversized/invalid graphs, digest mismatch, replaced roots and locks.
36pub fn read_effect_graph(
37    layout: &BackupLayoutGuard,
38    expected: &ArtifactChecksumRecord,
39) -> Result<EffectGraphRecord, EffectGraphPersistenceError> {
40    layout.check_root()?;
41    let path = layout.root().join("effect-graph.json");
42    let _lock = JournalLock::acquire(&path)?;
43    let record: EffectGraphRecord = read_json(&path, MAX_EFFECT_GRAPH_BYTES)?;
44    check_size(&record)?;
45    if &record.digest() != expected {
46        return Err(EffectGraphPersistenceError::DigestMismatch);
47    }
48    Ok(record)
49}
50fn check_size(record: &EffectGraphRecord) -> Result<(), PersistenceError> {
51    if serde_json::to_vec_pretty(record)?.len() as u64 > MAX_EFFECT_GRAPH_BYTES {
52        return Err(PersistenceError::RecordTooLarge {
53            limit: MAX_EFFECT_GRAPH_BYTES,
54        });
55    }
56    Ok(())
57}
58
59/// Typed declared graph identity or immutable local persistence rejection.
60#[derive(Debug, Error)]
61pub enum EffectGraphPersistenceError {
62    /// Retained graph differs from the original exact declared digest.
63    #[error("effect graph digest mismatch")]
64    DigestMismatch,
65    /// Cooperating layout/journal ownership failed.
66    #[error(transparent)]
67    Lock(#[from] JournalLockError),
68    /// Bounded JSON, graph admission or durable filesystem access failed.
69    #[error(transparent)]
70    Persistence(#[from] PersistenceError),
71}
72
73#[cfg(all(test, unix))]
74mod tests;