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        self.db.ensure_recovered_control_state()?;
44        owner.validate()?;
45        let result = match request {
46            IntegrityCheckRequest::Quick { entity } => self
47                .execute_quick_integrity_for_identity(&entity)
48                .map(IntegrityCheckResult::Quick),
49            IntegrityCheckRequest::DeepStart {
50                entity,
51                submission_key,
52            } => self
53                .start_deep_integrity_for_identity(&entity, owner, submission_key)
54                .map(IntegrityCheckResult::Deep),
55            IntegrityCheckRequest::DeepContinue {
56                job_id,
57                acknowledged_sequence,
58            } => {
59                job_id.validate()?;
60                self.continue_deep_integrity(job_id, &owner, acknowledged_sequence)
61                    .map(IntegrityCheckResult::Deep)
62            }
63            IntegrityCheckRequest::DeepAbort { job_id } => {
64                job_id.validate()?;
65                Self::abort_deep_integrity(job_id, &owner).map(IntegrityCheckResult::Deep)
66            }
67        };
68        run_next_integrity_retention_page::<C>()?;
69        result
70    }
71
72    fn execute_quick_integrity_for_identity(
73        &self,
74        entity: &IntegrityEntityIdentity,
75    ) -> Result<QuickIntegrityResult, IntegrityDeepError> {
76        let (runtime_entity, store) = self.integrity_target(entity)?;
77        let incarnation = database_incarnation_id()?;
78        match self.accepted_inspection_plan_for_runtime_entity(runtime_entity, store) {
79            Ok(plan) => {
80                Self::validate_integrity_plan_identity(entity, &plan)?;
81                execute_quick_integrity(&self.db, &plan).map_err(IntegrityDeepError::from)
82            }
83            Err(AcceptedInspectionPlanLoadError::Selected { identity, error }) => {
84                let accepted = IntegrityEntityIdentity::from_accepted_identity(&identity);
85                if entity != &accepted {
86                    return Err(IntegrityJobError::EntityIdentityMismatch.into());
87                }
88                Ok(uninspectable_quick_integrity(identity, incarnation, &error))
89            }
90            Err(AcceptedInspectionPlanLoadError::Unselected(error)) => {
91                Err(IntegrityDeepError::from(error))
92            }
93        }
94    }
95
96    fn integrity_target(
97        &self,
98        entity: &IntegrityEntityIdentity,
99    ) -> Result<(AcceptedRuntimeEntity, crate::db::registry::StoreHandle), IntegrityDeepError> {
100        entity.validate()?;
101        let runtime_entity = self
102            .db
103            .accepted_runtime_entity_for_path(entity.entity_path())?;
104        if runtime_entity.entity_tag().value() != entity.entity_tag()
105            || runtime_entity.store_path() != entity.store_path()
106        {
107            return Err(IntegrityJobError::EntityIdentityMismatch.into());
108        }
109        let store = self.db.store_handle(runtime_entity.store_path())?;
110
111        Ok((runtime_entity, store))
112    }
113
114    fn validate_integrity_plan_identity(
115        entity: &IntegrityEntityIdentity,
116        plan: &AcceptedInspectionPlan,
117    ) -> Result<(), IntegrityDeepError> {
118        if *entity != IntegrityEntityIdentity::from_accepted_identity(plan.identity_ref()) {
119            return Err(IntegrityJobError::EntityIdentityMismatch.into());
120        }
121        Ok(())
122    }
123
124    /// Start one authorized Deep job with an A/B proof handshake.
125    fn start_deep_integrity_for_identity(
126        &self,
127        entity: &IntegrityEntityIdentity,
128        owner: IntegrityJobOwner,
129        submission_key: IntegritySubmissionKey,
130    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
131        self.start_deep_integrity_for_identity_with_plan_loader(
132            entity,
133            owner,
134            submission_key,
135            |runtime_entity, store| {
136                self.accepted_inspection_plan_for_runtime_entity(runtime_entity, store)
137            },
138        )
139    }
140
141    fn start_deep_integrity_for_identity_with_plan_loader(
142        &self,
143        entity: &IntegrityEntityIdentity,
144        owner: IntegrityJobOwner,
145        submission_key: IntegritySubmissionKey,
146        mut load_plan: impl FnMut(
147            AcceptedRuntimeEntity,
148            crate::db::registry::StoreHandle,
149        )
150            -> Result<AcceptedInspectionPlan, AcceptedInspectionPlanLoadError>,
151    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
152        submission_key.validate()?;
153        let (runtime_entity, store) = self.integrity_target(entity)?;
154        let first_plan = load_plan(runtime_entity.clone(), store)
155            .map_err(|error| Self::deep_start_plan_load_error(entity, error))?;
156        Self::validate_integrity_plan_identity(entity, &first_plan)?;
157        let proof_a = capture_integrity_proof_vector(&self.db, &first_plan)?;
158
159        let store = self.db.store_handle(runtime_entity.store_path())?;
160        let second_plan = load_plan(runtime_entity, store)
161            .map_err(|error| Self::deep_start_plan_load_error(entity, error))?;
162        Self::validate_integrity_plan_identity(entity, &second_plan)?;
163        let proof_b = capture_integrity_proof_vector(&self.db, &second_plan)?;
164        start_deep_integrity_job(
165            &self.db,
166            &second_plan,
167            owner,
168            submission_key,
169            proof_a,
170            proof_b,
171        )
172    }
173
174    fn deep_start_plan_load_error(
175        entity: &IntegrityEntityIdentity,
176        error: AcceptedInspectionPlanLoadError,
177    ) -> IntegrityDeepError {
178        match error {
179            AcceptedInspectionPlanLoadError::Selected { identity, error } => {
180                if entity != &IntegrityEntityIdentity::from_accepted_identity(&identity) {
181                    return IntegrityJobError::EntityIdentityMismatch.into();
182                }
183                IntegrityDeepError::Uninspectable(IntegrityAuthorityDiagnostic::from_internal(
184                    &error,
185                ))
186            }
187            AcceptedInspectionPlanLoadError::Unselected(error) => {
188                IntegrityDeepError::Uninspectable(IntegrityAuthorityDiagnostic::from_internal(
189                    &error,
190                ))
191            }
192        }
193    }
194
195    /// Continue or replay one authorized Deep job.
196    fn continue_deep_integrity(
197        &self,
198        job_id: IntegrityJobId,
199        owner: &IntegrityJobOwner,
200        acknowledged_sequence: u64,
201    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
202        continue_deep_integrity_job(
203            &self.db,
204            job_id,
205            owner,
206            acknowledged_sequence,
207            |entity_path| {
208                let runtime_entity = self.db.accepted_runtime_entity_for_path(entity_path)?;
209                let store = self.db.store_handle(runtime_entity.store_path())?;
210                self.accepted_inspection_plan_for_runtime_entity(runtime_entity, store)
211                    .map_err(AcceptedInspectionPlanLoadError::into_internal)
212            },
213        )
214    }
215
216    /// Freeze one authorized Deep job for abort.
217    fn abort_deep_integrity(
218        job_id: IntegrityJobId,
219        owner: &IntegrityJobOwner,
220    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
221        abort_deep_integrity_job::<C>(job_id, owner)
222    }
223}