Skip to main content

icydb_core/db/session/
integrity.rs

1//! Module: db::session::integrity
2//! Responsibility: session routing into accepted-native integrity inspection.
3//! Does not own: inspection semantics, accepted schema construction, or recovery.
4//! Boundary: authorized entity path -> accepted runtime entity -> accepted inspection plan.
5
6use crate::{
7    db::{
8        DbSession, QuickIntegrityResult,
9        commit::database_incarnation_id,
10        integrity::{
11            IntegrityAuthorityDiagnostic, IntegrityCheckRequest, IntegrityCheckResult,
12            IntegrityDeepError, IntegrityEntityIdentity, IntegrityJobError, IntegrityJobId,
13            IntegrityJobOwner, IntegrityJobReceipt, IntegritySubmissionKey,
14            abort_deep_integrity_job, capture_integrity_proof_vector, continue_deep_integrity_job,
15            execute_quick_integrity, run_next_integrity_retention_page, start_deep_integrity_job,
16            uninspectable_quick_integrity,
17        },
18        runtime_entity_catalog::AcceptedRuntimeEntity,
19        schema::AcceptedInspectionPlan,
20        session::accepted_schema::AcceptedInspectionPlanLoadError,
21    },
22    traits::CanisterKind,
23};
24
25impl<C: CanisterKind> DbSession<C> {
26    /// Execute one trusted typed integrity request.
27    ///
28    /// The caller must enforce controller or equivalent integrity-specific
29    /// authorization before accepting caller-controlled requests. The owner
30    /// must be a stable identity for that already-authorized caller or
31    /// capability; possession of a job ID is never authorization.
32    ///
33    /// # Errors
34    ///
35    /// Returns a typed protocol error for invalid requests, authorization
36    /// ownership mismatches, and stale acknowledgements, or an internal error
37    /// when accepted authority or physical inspection cannot be read safely.
38    pub fn execute_admin_integrity(
39        &self,
40        request: IntegrityCheckRequest,
41        owner: IntegrityJobOwner,
42    ) -> Result<IntegrityCheckResult, IntegrityDeepError> {
43        owner.validate()?;
44        let result = match request {
45            IntegrityCheckRequest::Quick { entity } => self
46                .execute_quick_integrity_for_identity(&entity)
47                .map(IntegrityCheckResult::Quick),
48            IntegrityCheckRequest::DeepStart {
49                entity,
50                submission_key,
51            } => self
52                .start_deep_integrity_for_identity(&entity, owner, submission_key)
53                .map(IntegrityCheckResult::Deep),
54            IntegrityCheckRequest::DeepContinue {
55                job_id,
56                acknowledged_sequence,
57            } => {
58                job_id.validate()?;
59                self.continue_deep_integrity(job_id, &owner, acknowledged_sequence)
60                    .map(IntegrityCheckResult::Deep)
61            }
62            IntegrityCheckRequest::DeepAbort { job_id } => {
63                job_id.validate()?;
64                Self::abort_deep_integrity(job_id, &owner).map(IntegrityCheckResult::Deep)
65            }
66        };
67        run_next_integrity_retention_page::<C>()?;
68        result
69    }
70
71    fn execute_quick_integrity_for_identity(
72        &self,
73        entity: &IntegrityEntityIdentity,
74    ) -> Result<QuickIntegrityResult, IntegrityDeepError> {
75        let (runtime_entity, store) = self.integrity_target(entity)?;
76        let incarnation = database_incarnation_id()?;
77        match self.accepted_inspection_plan_for_runtime_entity(runtime_entity, store) {
78            Ok(plan) => {
79                Self::validate_integrity_plan_identity(entity, &plan)?;
80                execute_quick_integrity(&self.db, &plan).map_err(IntegrityDeepError::from)
81            }
82            Err(AcceptedInspectionPlanLoadError::Selected { identity, error }) => {
83                let accepted = IntegrityEntityIdentity::from_accepted_identity(&identity);
84                if entity != &accepted {
85                    return Err(IntegrityJobError::EntityIdentityMismatch.into());
86                }
87                Ok(uninspectable_quick_integrity(identity, incarnation, &error))
88            }
89            Err(AcceptedInspectionPlanLoadError::Unselected(error)) => {
90                Err(IntegrityDeepError::from(error))
91            }
92        }
93    }
94
95    fn integrity_target(
96        &self,
97        entity: &IntegrityEntityIdentity,
98    ) -> Result<(AcceptedRuntimeEntity, crate::db::registry::StoreHandle), IntegrityDeepError> {
99        entity.validate()?;
100        let runtime_entity = self
101            .db
102            .accepted_runtime_entity_for_path(entity.entity_path())?;
103        if runtime_entity.entity_tag().value() != entity.entity_tag()
104            || runtime_entity.store_path() != entity.store_path()
105        {
106            return Err(IntegrityJobError::EntityIdentityMismatch.into());
107        }
108        let store = self.db.recovered_store(runtime_entity.store_path())?;
109
110        Ok((runtime_entity, store))
111    }
112
113    fn validate_integrity_plan_identity(
114        entity: &IntegrityEntityIdentity,
115        plan: &AcceptedInspectionPlan,
116    ) -> Result<(), IntegrityDeepError> {
117        if *entity != IntegrityEntityIdentity::from_accepted_identity(plan.identity_ref()) {
118            return Err(IntegrityJobError::EntityIdentityMismatch.into());
119        }
120        Ok(())
121    }
122
123    /// Start one authorized Deep job with an A/B proof handshake.
124    fn start_deep_integrity_for_identity(
125        &self,
126        entity: &IntegrityEntityIdentity,
127        owner: IntegrityJobOwner,
128        submission_key: IntegritySubmissionKey,
129    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
130        self.start_deep_integrity_for_identity_with_plan_loader(
131            entity,
132            owner,
133            submission_key,
134            |runtime_entity, store| {
135                self.accepted_inspection_plan_for_runtime_entity(runtime_entity, store)
136            },
137        )
138    }
139
140    fn start_deep_integrity_for_identity_with_plan_loader(
141        &self,
142        entity: &IntegrityEntityIdentity,
143        owner: IntegrityJobOwner,
144        submission_key: IntegritySubmissionKey,
145        mut load_plan: impl FnMut(
146            AcceptedRuntimeEntity,
147            crate::db::registry::StoreHandle,
148        )
149            -> Result<AcceptedInspectionPlan, AcceptedInspectionPlanLoadError>,
150    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
151        submission_key.validate()?;
152        let (runtime_entity, store) = self.integrity_target(entity)?;
153        let first_plan = load_plan(runtime_entity.clone(), store)
154            .map_err(|error| Self::deep_start_plan_load_error(entity, error))?;
155        Self::validate_integrity_plan_identity(entity, &first_plan)?;
156        let proof_a = capture_integrity_proof_vector(&self.db, &first_plan)?;
157
158        let store = self.db.recovered_store(runtime_entity.store_path())?;
159        let second_plan = load_plan(runtime_entity, store)
160            .map_err(|error| Self::deep_start_plan_load_error(entity, error))?;
161        Self::validate_integrity_plan_identity(entity, &second_plan)?;
162        let proof_b = capture_integrity_proof_vector(&self.db, &second_plan)?;
163        start_deep_integrity_job(
164            &self.db,
165            &second_plan,
166            owner,
167            submission_key,
168            proof_a,
169            proof_b,
170        )
171    }
172
173    fn deep_start_plan_load_error(
174        entity: &IntegrityEntityIdentity,
175        error: AcceptedInspectionPlanLoadError,
176    ) -> IntegrityDeepError {
177        match error {
178            AcceptedInspectionPlanLoadError::Selected { identity, error } => {
179                if entity != &IntegrityEntityIdentity::from_accepted_identity(&identity) {
180                    return IntegrityJobError::EntityIdentityMismatch.into();
181                }
182                IntegrityDeepError::Uninspectable(IntegrityAuthorityDiagnostic::from_internal(
183                    &error,
184                ))
185            }
186            AcceptedInspectionPlanLoadError::Unselected(error) => {
187                IntegrityDeepError::Uninspectable(IntegrityAuthorityDiagnostic::from_internal(
188                    &error,
189                ))
190            }
191        }
192    }
193
194    /// Continue or replay one authorized Deep job.
195    fn continue_deep_integrity(
196        &self,
197        job_id: IntegrityJobId,
198        owner: &IntegrityJobOwner,
199        acknowledged_sequence: u64,
200    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
201        continue_deep_integrity_job(
202            &self.db,
203            job_id,
204            owner,
205            acknowledged_sequence,
206            |entity_path| {
207                let runtime_entity = self.db.accepted_runtime_entity_for_path(entity_path)?;
208                let store = self.db.recovered_store(runtime_entity.store_path())?;
209                self.accepted_inspection_plan_for_runtime_entity(runtime_entity, store)
210                    .map_err(AcceptedInspectionPlanLoadError::into_internal)
211            },
212        )
213    }
214
215    /// Freeze one authorized Deep job for abort.
216    fn abort_deep_integrity(
217        job_id: IntegrityJobId,
218        owner: &IntegrityJobOwner,
219    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
220        abort_deep_integrity_job::<C>(job_id, owner)
221    }
222}