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::{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                Ok(uninspectable_quick_integrity(
98                    identity,
99                    database_incarnation_id()?,
100                    &error,
101                ))
102            }
103            Err(AcceptedInspectionPlanLoadError::Unselected(error)) => {
104                Err(IntegrityDeepError::from(error))
105            }
106        }
107    }
108
109    fn execute_quick_integrity_with_catalog(
110        &self,
111        entity: &IntegrityEntityIdentity,
112        catalog: &AcceptedSchemaCatalogContext,
113    ) -> Result<QuickIntegrityResult, IntegrityDeepError> {
114        let plan = catalog.inspection_plan();
115        Self::validate_integrity_plan_identity(entity, plan)?;
116        execute_quick_integrity(
117            &self.db,
118            plan,
119            catalog.runtime_root_identity().database_incarnation(),
120        )
121        .map_err(IntegrityDeepError::from)
122    }
123
124    fn integrity_target(
125        &self,
126        entity: &IntegrityEntityIdentity,
127    ) -> Result<(AcceptedRuntimeEntity, crate::db::registry::StoreHandle), IntegrityDeepError> {
128        entity.validate()?;
129        let runtime_entity = self
130            .db
131            .accepted_runtime_entity_for_path(entity.entity_path())?;
132        if runtime_entity.entity_tag().value() != entity.entity_tag()
133            || runtime_entity.store_path() != entity.store_path()
134        {
135            return Err(IntegrityJobError::EntityIdentityMismatch.into());
136        }
137        let store = self.db.store_handle(runtime_entity.store_path())?;
138
139        Ok((runtime_entity, store))
140    }
141
142    fn validate_integrity_plan_identity(
143        entity: &IntegrityEntityIdentity,
144        plan: &AcceptedInspectionPlan,
145    ) -> Result<(), IntegrityDeepError> {
146        if *entity != IntegrityEntityIdentity::from_accepted_identity(plan.identity_ref()) {
147            return Err(IntegrityJobError::EntityIdentityMismatch.into());
148        }
149        Ok(())
150    }
151
152    /// Start one authorized Deep job with an A/B proof handshake.
153    fn start_deep_integrity_for_identity(
154        &self,
155        entity: &IntegrityEntityIdentity,
156        owner: IntegrityJobOwner,
157        submission_key: IntegritySubmissionKey,
158    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
159        self.start_deep_integrity_for_identity_with_plan_loader(
160            entity,
161            owner,
162            submission_key,
163            |runtime_entity, store| {
164                self.accepted_inspection_plan_for_runtime_entity(runtime_entity, store)
165            },
166        )
167    }
168
169    fn start_deep_integrity_for_identity_with_plan_loader(
170        &self,
171        entity: &IntegrityEntityIdentity,
172        owner: IntegrityJobOwner,
173        submission_key: IntegritySubmissionKey,
174        mut load_plan: impl FnMut(
175            AcceptedRuntimeEntity,
176            crate::db::registry::StoreHandle,
177        )
178            -> Result<AcceptedInspectionPlan, AcceptedInspectionPlanLoadError>,
179    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
180        submission_key.validate()?;
181        let (runtime_entity, store) = self.integrity_target(entity)?;
182        let first_plan = load_plan(runtime_entity.clone(), store)
183            .map_err(|error| Self::deep_start_plan_load_error(entity, error))?;
184        Self::validate_integrity_plan_identity(entity, &first_plan)?;
185        let proof_a = capture_integrity_proof_vector(&self.db, &first_plan)?;
186
187        let store = self.db.store_handle(runtime_entity.store_path())?;
188        let second_plan = load_plan(runtime_entity, store)
189            .map_err(|error| Self::deep_start_plan_load_error(entity, error))?;
190        Self::validate_integrity_plan_identity(entity, &second_plan)?;
191        let proof_b = capture_integrity_proof_vector(&self.db, &second_plan)?;
192        start_deep_integrity_job(
193            &self.db,
194            &second_plan,
195            owner,
196            submission_key,
197            proof_a,
198            proof_b,
199        )
200    }
201
202    fn deep_start_plan_load_error(
203        entity: &IntegrityEntityIdentity,
204        error: AcceptedInspectionPlanLoadError,
205    ) -> IntegrityDeepError {
206        match error {
207            AcceptedInspectionPlanLoadError::Selected { identity, error } => {
208                if entity != &IntegrityEntityIdentity::from_accepted_identity(&identity) {
209                    return IntegrityJobError::EntityIdentityMismatch.into();
210                }
211                IntegrityDeepError::Uninspectable(IntegrityAuthorityDiagnostic::from_internal(
212                    &error,
213                ))
214            }
215            AcceptedInspectionPlanLoadError::Unselected(error) => {
216                IntegrityDeepError::Uninspectable(IntegrityAuthorityDiagnostic::from_internal(
217                    &error,
218                ))
219            }
220        }
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}