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