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 -> runtime hook -> accepted inspection plan.
5
6#[cfg(test)]
7use crate::{
8    db::integrity::{
9        DerivedInspectionLimits, DerivedIntegrityPage, IntegrityProofVector,
10        PhysicalUnitCheckpoint, RowInspectionLimits, RowIntegrityPage,
11        execute_index_integrity_page, execute_reverse_integrity_page, execute_row_integrity_page,
12    },
13    error::InternalError,
14};
15use crate::{
16    db::{
17        DbSession, QuickIntegrityResult,
18        commit::database_incarnation_id,
19        integrity::{
20            IntegrityCheckRequest, IntegrityCheckResult, IntegrityDeepError,
21            IntegrityEntityIdentity, IntegrityJobError, IntegrityJobId, IntegrityJobOwner,
22            IntegrityJobReceipt, IntegritySubmissionKey, abort_deep_integrity_job,
23            capture_integrity_proof_vector, continue_deep_integrity_job, execute_quick_integrity,
24            run_next_integrity_retention_page, start_deep_integrity_job,
25            uninspectable_quick_integrity,
26        },
27        runtime_hooks::EntityRuntimeHooks,
28        schema::AcceptedInspectionPlan,
29        session::accepted_schema::AcceptedInspectionPlanLoadError,
30    },
31    traits::CanisterKind,
32};
33
34impl<C: CanisterKind> DbSession<C> {
35    /// Execute one trusted typed integrity request.
36    ///
37    /// The caller must enforce controller or equivalent integrity-specific
38    /// authorization before accepting caller-controlled requests. The owner
39    /// must be a stable identity for that already-authorized caller or
40    /// capability; possession of a job ID is never authorization.
41    ///
42    /// # Errors
43    ///
44    /// Returns a typed protocol error for invalid requests, authorization
45    /// ownership mismatches, and stale acknowledgements, or an internal error
46    /// when accepted authority or physical inspection cannot be read safely.
47    pub fn execute_admin_integrity(
48        &self,
49        request: IntegrityCheckRequest,
50        owner: IntegrityJobOwner,
51    ) -> Result<IntegrityCheckResult, IntegrityDeepError> {
52        owner.validate()?;
53        let result = match request {
54            IntegrityCheckRequest::Quick { entity } => self
55                .execute_quick_integrity_for_identity(&entity)
56                .map(IntegrityCheckResult::Quick),
57            IntegrityCheckRequest::DeepStart {
58                entity,
59                submission_key,
60            } => self
61                .start_deep_integrity_for_identity(&entity, owner, submission_key)
62                .map(IntegrityCheckResult::Deep),
63            IntegrityCheckRequest::DeepContinue {
64                job_id,
65                acknowledged_sequence,
66            } => {
67                job_id.validate()?;
68                self.continue_deep_integrity(job_id, &owner, acknowledged_sequence)
69                    .map(IntegrityCheckResult::Deep)
70            }
71            IntegrityCheckRequest::DeepAbort { job_id } => {
72                job_id.validate()?;
73                Self::abort_deep_integrity(job_id, &owner).map(IntegrityCheckResult::Deep)
74            }
75        };
76        run_next_integrity_retention_page::<C>()?;
77        result
78    }
79
80    fn execute_quick_integrity_for_identity(
81        &self,
82        entity: &IntegrityEntityIdentity,
83    ) -> Result<QuickIntegrityResult, IntegrityDeepError> {
84        let (hooks, store) = self.integrity_target(entity)?;
85        let incarnation = database_incarnation_id()?;
86        match self.accepted_inspection_plan_for_runtime_hook(hooks, store) {
87            Ok(plan) => {
88                Self::validate_integrity_plan_identity(entity, &plan)?;
89                execute_quick_integrity(&self.db, &plan).map_err(IntegrityDeepError::from)
90            }
91            Err(AcceptedInspectionPlanLoadError::Selected { identity, error }) => {
92                let accepted = IntegrityEntityIdentity::from_accepted_identity(identity);
93                if entity != &accepted {
94                    return Err(IntegrityJobError::EntityIdentityMismatch.into());
95                }
96                Ok(uninspectable_quick_integrity(identity, incarnation, &error))
97            }
98            Err(AcceptedInspectionPlanLoadError::Unselected(error)) => {
99                Err(IntegrityDeepError::from(error))
100            }
101        }
102    }
103
104    fn integrity_target(
105        &self,
106        entity: &IntegrityEntityIdentity,
107    ) -> Result<(&EntityRuntimeHooks<C>, crate::db::registry::StoreHandle), IntegrityDeepError>
108    {
109        entity.validate()?;
110        let hooks = self.db.runtime_hook_for_entity_path(entity.entity_path())?;
111        if hooks.entity_tag.value() != entity.entity_tag()
112            || hooks.store_path != entity.store_path()
113        {
114            return Err(IntegrityJobError::EntityIdentityMismatch.into());
115        }
116        let store = self.db.recovered_store(hooks.store_path)?;
117
118        Ok((hooks, store))
119    }
120
121    fn validate_integrity_plan_identity(
122        entity: &IntegrityEntityIdentity,
123        plan: &AcceptedInspectionPlan,
124    ) -> Result<(), IntegrityDeepError> {
125        if entity != &IntegrityEntityIdentity::from_accepted_identity(plan.identity()) {
126            return Err(IntegrityJobError::EntityIdentityMismatch.into());
127        }
128        Ok(())
129    }
130
131    /// Execute one bounded internal row-inspection page.
132    ///
133    /// The checkpoint and limits remain inside the database boundary. Patch 5
134    /// binds this exact core to authorized durable Deep jobs; no application
135    /// caller can author or resume physical progress directly.
136    #[cfg(test)]
137    pub(in crate::db) fn execute_integrity_row_page_for_entity(
138        &self,
139        entity_path: &str,
140        checkpoint: PhysicalUnitCheckpoint,
141        limits: RowInspectionLimits,
142    ) -> Result<RowIntegrityPage, InternalError> {
143        let hooks = self.db.runtime_hook_for_entity_path(entity_path)?;
144        let store = self.db.recovered_store(hooks.store_path)?;
145        let plan = self
146            .accepted_inspection_plan_for_runtime_hook(hooks, store)
147            .map_err(AcceptedInspectionPlanLoadError::into_internal)?;
148        execute_row_integrity_page(&self.db, &plan, checkpoint, limits)
149    }
150
151    /// Execute one bounded internal active forward-index page.
152    ///
153    /// The index ordinal is accepted-plan order, not public or physical
154    /// identity. Patch 5 owns phase sequencing through authorized jobs.
155    #[cfg(test)]
156    pub(in crate::db) fn execute_integrity_index_page_for_entity(
157        &self,
158        entity_path: &str,
159        index_ordinal: usize,
160        checkpoint: PhysicalUnitCheckpoint,
161        limits: DerivedInspectionLimits,
162    ) -> Result<DerivedIntegrityPage, InternalError> {
163        let hooks = self.db.runtime_hook_for_entity_path(entity_path)?;
164        let store = self.db.recovered_store(hooks.store_path)?;
165        let plan = self
166            .accepted_inspection_plan_for_runtime_hook(hooks, store)
167            .map_err(AcceptedInspectionPlanLoadError::into_internal)?;
168        execute_index_integrity_page(&self.db, &plan, index_ordinal, checkpoint, limits)
169    }
170
171    /// Execute one bounded internal active source-owned reverse-relation page.
172    ///
173    /// The relation ordinal is accepted-plan order. Pending and retired
174    /// generations remain outside this traversal by construction.
175    #[cfg(test)]
176    pub(in crate::db) fn execute_integrity_reverse_page_for_entity(
177        &self,
178        entity_path: &str,
179        relation_ordinal: usize,
180        checkpoint: PhysicalUnitCheckpoint,
181        limits: DerivedInspectionLimits,
182    ) -> Result<DerivedIntegrityPage, InternalError> {
183        let hooks = self.db.runtime_hook_for_entity_path(entity_path)?;
184        let store = self.db.recovered_store(hooks.store_path)?;
185        let plan = self
186            .accepted_inspection_plan_for_runtime_hook(hooks, store)
187            .map_err(AcceptedInspectionPlanLoadError::into_internal)?;
188        execute_reverse_integrity_page(&self.db, &plan, relation_ordinal, checkpoint, limits)
189    }
190
191    /// Capture the complete pre/post advancement proof for one entity.
192    #[cfg(test)]
193    pub(in crate::db) fn capture_integrity_proof_for_entity(
194        &self,
195        entity_path: &str,
196    ) -> Result<IntegrityProofVector, InternalError> {
197        let hooks = self.db.runtime_hook_for_entity_path(entity_path)?;
198        let store = self.db.recovered_store(hooks.store_path)?;
199        let plan = self
200            .accepted_inspection_plan_for_runtime_hook(hooks, store)
201            .map_err(AcceptedInspectionPlanLoadError::into_internal)?;
202        capture_integrity_proof_vector(&self.db, &plan)
203    }
204
205    /// Start one authorized Deep job with an A/B proof handshake.
206    fn start_deep_integrity_for_identity(
207        &self,
208        entity: &IntegrityEntityIdentity,
209        owner: IntegrityJobOwner,
210        submission_key: IntegritySubmissionKey,
211    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
212        submission_key.validate()?;
213        let (hooks, store) = self.integrity_target(entity)?;
214        let first_plan = self
215            .accepted_inspection_plan_for_runtime_hook(hooks, store)
216            .map_err(AcceptedInspectionPlanLoadError::into_internal)?;
217        Self::validate_integrity_plan_identity(entity, &first_plan)?;
218        let proof_a = capture_integrity_proof_vector(&self.db, &first_plan)?;
219
220        let store = self.db.recovered_store(hooks.store_path)?;
221        let second_plan = self
222            .accepted_inspection_plan_for_runtime_hook(hooks, store)
223            .map_err(AcceptedInspectionPlanLoadError::into_internal)?;
224        Self::validate_integrity_plan_identity(entity, &second_plan)?;
225        let proof_b = capture_integrity_proof_vector(&self.db, &second_plan)?;
226        start_deep_integrity_job(
227            &self.db,
228            &second_plan,
229            owner,
230            submission_key,
231            proof_a,
232            proof_b,
233        )
234    }
235
236    /// Continue or replay one authorized Deep job.
237    fn continue_deep_integrity(
238        &self,
239        job_id: IntegrityJobId,
240        owner: &IntegrityJobOwner,
241        acknowledged_sequence: u64,
242    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
243        continue_deep_integrity_job(
244            &self.db,
245            job_id,
246            owner,
247            acknowledged_sequence,
248            |entity_path| {
249                let hooks = self.db.runtime_hook_for_entity_path(entity_path)?;
250                let store = self.db.recovered_store(hooks.store_path)?;
251                self.accepted_inspection_plan_for_runtime_hook(hooks, store)
252                    .map_err(AcceptedInspectionPlanLoadError::into_internal)
253            },
254        )
255    }
256
257    /// Freeze one authorized Deep job for abort.
258    fn abort_deep_integrity(
259        job_id: IntegrityJobId,
260        owner: &IntegrityJobOwner,
261    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
262        abort_deep_integrity_job::<C>(job_id, owner)
263    }
264
265    #[cfg(test)]
266    pub(in crate::db) fn start_deep_integrity_for_entity(
267        &self,
268        entity_path: &str,
269        owner: IntegrityJobOwner,
270        submission_key: IntegritySubmissionKey,
271    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
272        let hooks = self.db.runtime_hook_for_entity_path(entity_path)?;
273        let store = self.db.recovered_store(hooks.store_path)?;
274        let plan = self
275            .accepted_inspection_plan_for_runtime_hook(hooks, store)
276            .map_err(AcceptedInspectionPlanLoadError::into_internal)?;
277        let entity = IntegrityEntityIdentity::from_accepted_identity(plan.identity());
278
279        self.start_deep_integrity_for_identity(&entity, owner, submission_key)
280    }
281
282    #[cfg(test)]
283    pub(in crate::db) fn continue_deep_integrity_for_tests(
284        &self,
285        job_id: IntegrityJobId,
286        owner: &IntegrityJobOwner,
287        acknowledged_sequence: u64,
288    ) -> Result<IntegrityJobReceipt, IntegrityDeepError> {
289        self.continue_deep_integrity(job_id, owner, acknowledged_sequence)
290    }
291}