Skip to main content

icydb_core/db/schema/
store.rs

1//! Module: db::schema::store
2//! Responsibility: stable BTreeMap-backed schema metadata persistence.
3//! Does not own: reconciliation policy, typed snapshot encoding, or generated proposal construction.
4//! Boundary: provides the third per-store stable memory alongside row and index stores.
5
6use crate::db::schema::identity_state::{
7    IdentityAdvanceId, IdentityRangeAdvance, IdentityRangeCommitState, IdentityState,
8    IdentityStateInventory, IdentityStateLifecycle, IdentityStateTransition,
9    IdentityStatementCursor, MAX_IDENTITY_STATE_RECORDS_PER_DATABASE, decode_identity_state,
10    encode_identity_state, prepare_identity_state_transition, validate_identity_state_closure,
11};
12use crate::db::schema::{
13    cardinality_build::{
14        CardinalityAcceptedDomain, CardinalityReadyCandidate, EmptyCardinalityReadyCandidate,
15    },
16    cardinality_generation::{
17        CardinalityAcceptedRootIdentity, CardinalityBuildCursor, CardinalityCountDigest,
18        CardinalityCountRecord, CardinalityCountSlot, CardinalityGenerationHeader,
19        CardinalityGenerationId, CardinalityGenerationState, CardinalitySourceIdentity,
20    },
21};
22use crate::{
23    db::{
24        codec::{
25            finalize_hash_sha256, new_hash_sha256, write_hash_len_u32, write_hash_str_u32,
26            write_hash_tag_u8, write_hash_u32, write_hash_u64,
27        },
28        commit::CommitSchemaFingerprint,
29        direction::Direction,
30        integrity::DatabaseIncarnationId,
31        journal::{JournalBatch, JournalRecord},
32        ordered_overlay::{OrderedOverlayEntry, ordered_overlay_entries},
33        positioned_overlay::{
34            JournalOverlayPosition, PositionedOverlayMetadata, PositionedOverlayRetirement,
35        },
36        runtime_entity_catalog::AcceptedRuntimeEntity,
37        schema::{
38            AcceptedFieldKind, AcceptedSchemaSnapshot, ConstraintActivationKind,
39            ConstraintActivationState, ConstraintId, ConstraintOrigin, ConstraintValidationJob,
40            FieldId, PersistedIndexKeyItemSnapshot, PersistedIndexKeySnapshot,
41            PersistedSchemaSnapshot, SchemaVersion, accepted_schema_cache_fingerprint,
42            accepted_schema_cache_fingerprint_for_persisted_snapshot,
43            accepted_schema_cache_fingerprint_method_version, decode_constraint_validation_job,
44            encode_constraint_validation_job, encode_persisted_schema_snapshot,
45            enum_catalog::{
46                AcceptedSchemaAuthority, AcceptedSchemaPublicationError, AcceptedSchemaRevision,
47                AcceptedSchemaRevisionBundle, AcceptedSchemaRootSelection,
48                AcceptedStoreCatalogScope, AcceptedValueCatalogHandle, CandidateSchemaRevision,
49                decode_verified_accepted_schema_revision_bundle,
50                prepare_accepted_schema_root_publication, select_current_accepted_schema_root,
51            },
52        },
53    },
54    error::InternalError,
55    types::EntityTag,
56};
57use ic_memory::{DefaultMemoryImpl, RuntimeMemory};
58use ic_storage::adapter::{StableBTreeMap, Storable, storable::Bound as StorableBound};
59use sha2::Digest;
60use std::borrow::Cow;
61#[cfg(test)]
62use std::cell::Cell;
63use std::cell::{OnceCell, Ref, RefCell};
64use std::collections::{BTreeMap as StdBTreeMap, BTreeSet};
65use std::ops::Bound as RangeBound;
66use std::rc::Rc;
67
68const SCHEMA_KEY_BYTES_USIZE: usize = 16;
69const SCHEMA_KEY_BYTES: u32 = 16;
70const SCHEMA_KEY_NAMESPACE_ACCEPTED_BUNDLE: u8 = 1;
71const SCHEMA_KEY_NAMESPACE_ACCEPTED_ROOT: u8 = 2;
72const SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB: u8 = 3;
73const SCHEMA_KEY_NAMESPACE_IDENTITY_STATE: u8 = 4;
74// Every role exposes the sole current method version while its separate domain
75// tag keeps data, index, and full-catalog fingerprint inputs disjoint.
76const SCHEMA_STORE_FINGERPRINT_METHOD_VERSION: u8 = 1;
77const SCHEMA_STORE_CATALOG_FINGERPRINT_DOMAIN: u8 = 1;
78const SCHEMA_STORE_DATA_ALLOCATION_FINGERPRINT_DOMAIN: u8 = 2;
79const SCHEMA_STORE_INDEX_ALLOCATION_FINGERPRINT_DOMAIN: u8 = 3;
80const ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_BOOL: u8 = 3;
81const ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_LIST: u8 = 29;
82const ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_SET: u8 = 30;
83const ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_MAP: u8 = 31;
84const ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_COMPOSITE: u8 = 32;
85
86#[cfg(test)]
87thread_local! {
88    static ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES: Cell<u64> = const { Cell::new(0) };
89    static CANONICAL_SCHEMA_BUNDLE_LOADS: Cell<u64> = const { Cell::new(0) };
90}
91
92#[cfg(test)]
93fn reset_accepted_schema_bundle_cache_miss_count_for_tests() {
94    ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES.with(|misses| misses.set(0));
95}
96
97#[cfg(test)]
98fn accepted_schema_bundle_cache_miss_count_for_tests() -> u64 {
99    ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES.with(Cell::get)
100}
101
102///
103/// RawSchemaKey
104///
105/// Stable key for one accepted catalog or schema/control record.
106/// Each maintained namespace owns its identity fields independently.
107///
108
109#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
110struct RawSchemaKey([u8; SCHEMA_KEY_BYTES_USIZE]);
111
112impl RawSchemaKey {
113    fn from_accepted_bundle(bundle_key: super::enum_catalog::AcceptedSchemaBundleKey) -> Self {
114        let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
115        out[0] = SCHEMA_KEY_NAMESPACE_ACCEPTED_BUNDLE;
116        out[4..12].copy_from_slice(&bundle_key.get().to_be_bytes());
117        Self(out)
118    }
119
120    fn from_accepted_root_slot(slot: usize) -> Result<Self, InternalError> {
121        let slot = u32::try_from(slot).map_err(|_| InternalError::store_invariant())?;
122        if slot > 1 {
123            return Err(InternalError::store_invariant());
124        }
125        let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
126        out[0] = SCHEMA_KEY_NAMESPACE_ACCEPTED_ROOT;
127        out[12..].copy_from_slice(&slot.to_be_bytes());
128        Ok(Self(out))
129    }
130
131    fn from_constraint_validation_job(entity: EntityTag, constraint_id: ConstraintId) -> Self {
132        let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
133        out[0] = SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB;
134        out[4..12].copy_from_slice(&entity.value().to_be_bytes());
135        out[12..].copy_from_slice(&constraint_id.get().to_be_bytes());
136        Self(out)
137    }
138
139    fn from_identity_state(entity: EntityTag, field_id: FieldId) -> Self {
140        let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
141        out[0] = SCHEMA_KEY_NAMESPACE_IDENTITY_STATE;
142        out[4..12].copy_from_slice(&entity.value().to_be_bytes());
143        out[12..].copy_from_slice(&field_id.get().to_be_bytes());
144        Self(out)
145    }
146
147    /// Return the entity tag encoded in this schema key.
148    #[must_use]
149    fn entity_tag(self) -> EntityTag {
150        let mut bytes = [0u8; size_of::<u64>()];
151        bytes.copy_from_slice(&self.0[4..12]);
152
153        EntityTag::new(u64::from_be_bytes(bytes))
154    }
155
156    /// Return the schema version encoded in this schema key.
157    #[must_use]
158    fn version(self) -> u32 {
159        let mut bytes = [0u8; size_of::<u32>()];
160        bytes.copy_from_slice(&self.0[12..]);
161
162        u32::from_be_bytes(bytes)
163    }
164
165    // Publication owns catalog namespaces only. Identity transitions and the
166    // paged cardinality lifecycle own the remaining records in this allocation.
167    const fn all_catalog_range_bounds() -> (RangeBound<Self>, RangeBound<Self>) {
168        let mut start = [0u8; SCHEMA_KEY_BYTES_USIZE];
169        start[0] = SCHEMA_KEY_NAMESPACE_ACCEPTED_BUNDLE;
170        let mut end = [u8::MAX; SCHEMA_KEY_BYTES_USIZE];
171        end[0] = SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB;
172        (
173            RangeBound::Included(Self(start)),
174            RangeBound::Included(Self(end)),
175        )
176    }
177
178    const fn all_constraint_validation_job_range_bounds() -> (RangeBound<Self>, RangeBound<Self>) {
179        let mut start = [0u8; SCHEMA_KEY_BYTES_USIZE];
180        start[0] = SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB;
181        let mut end = [u8::MAX; SCHEMA_KEY_BYTES_USIZE];
182        end[0] = SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB;
183        (
184            RangeBound::Included(Self(start)),
185            RangeBound::Included(Self(end)),
186        )
187    }
188
189    const fn all_identity_state_range_bounds() -> (RangeBound<Self>, RangeBound<Self>) {
190        let mut start = [0u8; SCHEMA_KEY_BYTES_USIZE];
191        start[0] = SCHEMA_KEY_NAMESPACE_IDENTITY_STATE;
192        let mut end = [u8::MAX; SCHEMA_KEY_BYTES_USIZE];
193        end[0] = SCHEMA_KEY_NAMESPACE_IDENTITY_STATE;
194        (
195            RangeBound::Included(Self(start)),
196            RangeBound::Included(Self(end)),
197        )
198    }
199
200    const fn is_accepted_root(self) -> bool {
201        self.0[0] == SCHEMA_KEY_NAMESPACE_ACCEPTED_ROOT
202    }
203
204    const fn is_constraint_validation_job(self) -> bool {
205        self.0[0] == SCHEMA_KEY_NAMESPACE_CONSTRAINT_VALIDATION_JOB
206    }
207
208    const fn is_identity_state(self) -> bool {
209        self.0[0] == SCHEMA_KEY_NAMESPACE_IDENTITY_STATE
210    }
211
212    fn constraint_id(self) -> Option<ConstraintId> {
213        self.is_constraint_validation_job()
214            .then(|| ConstraintId::new(self.version()))
215            .flatten()
216    }
217}
218
219impl RawSchemaKey {
220    const NAMESPACE_CARDINALITY_CONTROL: u8 = 5;
221    const NAMESPACE_CARDINALITY_COUNT_A: u8 = 6;
222    const NAMESPACE_CARDINALITY_COUNT_B: u8 = 7;
223    const CARDINALITY_HEADER_DISCRIMINATOR: u8 = 0;
224    const CARDINALITY_BUILD_CURSOR_DISCRIMINATOR: u8 = 1;
225
226    const fn from_cardinality_generation_header() -> Self {
227        let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
228        out[0] = Self::NAMESPACE_CARDINALITY_CONTROL;
229        out[SCHEMA_KEY_BYTES_USIZE - 1] = Self::CARDINALITY_HEADER_DISCRIMINATOR;
230        Self(out)
231    }
232
233    const fn from_cardinality_build_cursor() -> Self {
234        let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
235        out[0] = Self::NAMESPACE_CARDINALITY_CONTROL;
236        out[SCHEMA_KEY_BYTES_USIZE - 1] = Self::CARDINALITY_BUILD_CURSOR_DISCRIMINATOR;
237        Self(out)
238    }
239
240    fn from_cardinality_count(slot: CardinalityCountSlot, digest: CardinalityCountDigest) -> Self {
241        let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
242        out[0] = match slot {
243            CardinalityCountSlot::A => Self::NAMESPACE_CARDINALITY_COUNT_A,
244            CardinalityCountSlot::B => Self::NAMESPACE_CARDINALITY_COUNT_B,
245        };
246        out[1..].copy_from_slice(&digest.as_bytes()[..SCHEMA_KEY_BYTES_USIZE - 1]);
247        Self(out)
248    }
249
250    const fn cardinality_count_range_bounds(
251        slot: CardinalityCountSlot,
252    ) -> (RangeBound<Self>, RangeBound<Self>) {
253        let namespace = match slot {
254            CardinalityCountSlot::A => Self::NAMESPACE_CARDINALITY_COUNT_A,
255            CardinalityCountSlot::B => Self::NAMESPACE_CARDINALITY_COUNT_B,
256        };
257        let mut start = [0_u8; SCHEMA_KEY_BYTES_USIZE];
258        start[0] = namespace;
259        let mut end = [u8::MAX; SCHEMA_KEY_BYTES_USIZE];
260        end[0] = namespace;
261        (
262            RangeBound::Included(Self(start)),
263            RangeBound::Included(Self(end)),
264        )
265    }
266}
267
268impl Storable for RawSchemaKey {
269    fn to_bytes(&self) -> Cow<'_, [u8]> {
270        Cow::Borrowed(&self.0)
271    }
272
273    fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
274        debug_assert_eq!(
275            bytes.len(),
276            SCHEMA_KEY_BYTES_USIZE,
277            "RawSchemaKey::from_bytes received unexpected byte length",
278        );
279
280        if bytes.len() != SCHEMA_KEY_BYTES_USIZE {
281            return Self([0u8; SCHEMA_KEY_BYTES_USIZE]);
282        }
283
284        let mut out = [0u8; SCHEMA_KEY_BYTES_USIZE];
285        out.copy_from_slice(bytes.as_ref());
286        Self(out)
287    }
288
289    fn into_bytes(self) -> Vec<u8> {
290        self.0.to_vec()
291    }
292
293    const BOUND: StorableBound = StorableBound::Bounded {
294        max_size: SCHEMA_KEY_BYTES,
295        is_fixed_size: true,
296    };
297}
298
299/// Opaque value in the schema allocation. The key-specific bounded codec owns
300/// each accepted bundle, root, Identity, validation-job or cardinality payload.
301#[derive(Clone, Debug, Eq, PartialEq)]
302struct RawSchemaValue {
303    payload: Vec<u8>,
304}
305
306impl RawSchemaValue {
307    /// Store one already-versioned schema/control record.
308    #[must_use]
309    const fn from_encoded_control_record(payload: Vec<u8>) -> Self {
310        Self { payload }
311    }
312
313    #[must_use]
314    const fn as_bytes(&self) -> &[u8] {
315        self.payload.as_slice()
316    }
317}
318
319#[derive(Clone, Debug, Eq, PartialEq)]
320pub(in crate::db) struct AcceptedCatalogIdentity {
321    entity_tag: EntityTag,
322    entity_path: Rc<str>,
323    store_path: &'static str,
324    accepted_schema_revision: AcceptedSchemaRevision,
325    accepted_schema_version: SchemaVersion,
326    fingerprint_method_version: u8,
327    accepted_schema_fingerprint: CommitSchemaFingerprint,
328}
329
330impl AcceptedCatalogIdentity {
331    #[must_use]
332    pub(in crate::db) fn new(
333        entity_tag: EntityTag,
334        entity_path: impl Into<Rc<str>>,
335        store_path: &'static str,
336        accepted_schema_revision: AcceptedSchemaRevision,
337        accepted_schema_version: SchemaVersion,
338        accepted_schema_fingerprint: CommitSchemaFingerprint,
339    ) -> Self {
340        Self {
341            entity_tag,
342            entity_path: entity_path.into(),
343            store_path,
344            accepted_schema_revision,
345            accepted_schema_version,
346            fingerprint_method_version: accepted_schema_cache_fingerprint_method_version(),
347            accepted_schema_fingerprint,
348        }
349    }
350
351    #[must_use]
352    pub(in crate::db) const fn entity_tag(&self) -> EntityTag {
353        self.entity_tag
354    }
355
356    #[must_use]
357    pub(in crate::db) fn entity_path(&self) -> &str {
358        self.entity_path.as_ref()
359    }
360
361    #[must_use]
362    pub(in crate::db) fn entity_path_handle(&self) -> Rc<str> {
363        self.entity_path.clone()
364    }
365
366    #[must_use]
367    pub(in crate::db) const fn store_path(&self) -> &'static str {
368        self.store_path
369    }
370
371    #[must_use]
372    pub(in crate::db) const fn accepted_schema_revision(&self) -> AcceptedSchemaRevision {
373        self.accepted_schema_revision
374    }
375
376    #[must_use]
377    pub(in crate::db) const fn accepted_schema_version(&self) -> SchemaVersion {
378        self.accepted_schema_version
379    }
380
381    #[must_use]
382    pub(in crate::db) const fn fingerprint_method_version(&self) -> u8 {
383        self.fingerprint_method_version
384    }
385
386    #[must_use]
387    pub(in crate::db) const fn accepted_schema_fingerprint(&self) -> CommitSchemaFingerprint {
388        self.accepted_schema_fingerprint
389    }
390}
391
392#[derive(Clone, Debug, Eq, PartialEq)]
393pub(in crate::db) struct AcceptedCatalogSnapshotSelection {
394    identity: AcceptedCatalogIdentity,
395    value_catalog: AcceptedValueCatalogHandle,
396    snapshot: Rc<AcceptedSchemaSnapshot>,
397}
398
399impl AcceptedCatalogSnapshotSelection {
400    // Derive identity and shared authority together from the verified bundle.
401    // Consumers never need to serialize or revalidate this immutable selection.
402    fn from_verified_snapshot(
403        entity: EntityTag,
404        store_path: &'static str,
405        snapshot: &PersistedSchemaSnapshot,
406        value_catalog: AcceptedValueCatalogHandle,
407    ) -> Result<Self, InternalError> {
408        let snapshot = Rc::new(AcceptedSchemaSnapshot::try_new(snapshot.clone())?);
409        let fingerprint = accepted_schema_cache_fingerprint(&snapshot)?;
410        let identity = AcceptedCatalogIdentity::new(
411            entity,
412            snapshot.entity_path(),
413            store_path,
414            value_catalog.revision(),
415            snapshot.persisted_snapshot().version(),
416            fingerprint,
417        );
418
419        Ok(Self {
420            identity,
421            value_catalog,
422            snapshot,
423        })
424    }
425
426    #[must_use]
427    pub(in crate::db) fn identity(&self) -> AcceptedCatalogIdentity {
428        self.identity.clone()
429    }
430
431    #[must_use]
432    pub(in crate::db) const fn value_catalog_handle(&self) -> &AcceptedValueCatalogHandle {
433        &self.value_catalog
434    }
435
436    /// Select one entity snapshot and catalog directly from a verified schema
437    /// candidate during migration preparation.
438    #[cfg(any(test, feature = "migration"))]
439    pub(in crate::db) fn from_candidate(
440        candidate: &CandidateSchemaRevision,
441        entity_tag: EntityTag,
442        entity_path: &str,
443        store_path: &'static str,
444    ) -> Result<Option<Self>, InternalError> {
445        if candidate.store_path() != store_path {
446            return Err(InternalError::store_corruption());
447        }
448        let Some(snapshot) = candidate.bundle().entity_snapshots().get(&entity_tag) else {
449            return Ok(None);
450        };
451        if snapshot.entity_path() != entity_path {
452            return Err(InternalError::store_corruption());
453        }
454
455        Self::from_verified_snapshot(
456            entity_tag,
457            store_path,
458            snapshot,
459            AcceptedValueCatalogHandle::new(
460                candidate.bundle().enum_catalog().clone(),
461                candidate.bundle().composite_catalog().clone(),
462                AcceptedStoreCatalogScope::new(),
463                candidate.revision(),
464                candidate.root().fingerprint(),
465            ),
466        )
467        .map(Some)
468    }
469
470    /// Share the immutable snapshot verified when this selection was built.
471    #[must_use]
472    pub(in crate::db) fn snapshot(&self) -> Rc<AcceptedSchemaSnapshot> {
473        self.snapshot.clone()
474    }
475}
476
477impl Storable for RawSchemaValue {
478    fn to_bytes(&self) -> Cow<'_, [u8]> {
479        Cow::Borrowed(self.as_bytes())
480    }
481
482    fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
483        Self::from_encoded_control_record(bytes.into_owned())
484    }
485
486    fn into_bytes(self) -> Vec<u8> {
487        self.payload
488    }
489
490    const BOUND: StorableBound = StorableBound::Unbounded;
491}
492
493///
494/// SchemaStoreCatalogMetadata
495///
496/// Accepted schema-store catalog metadata derived from the selected bundle. This is diagnostic allocation metadata, not allocation identity.
497///
498
499#[derive(Clone, Copy, Debug, Eq, PartialEq)]
500pub(in crate::db) struct SchemaStoreCatalogMetadata {
501    schema_version: SchemaVersion,
502    schema_fingerprint_method_version: u8,
503    schema_fingerprint: CommitSchemaFingerprint,
504    entity_count: u64,
505}
506
507impl SchemaStoreCatalogMetadata {
508    /// Build catalog metadata from already-derived accepted schema facts.
509    #[must_use]
510    const fn new(
511        schema_version: SchemaVersion,
512        schema_fingerprint_method_version: u8,
513        schema_fingerprint: CommitSchemaFingerprint,
514        entity_count: u64,
515    ) -> Self {
516        Self {
517            schema_version,
518            schema_fingerprint_method_version,
519            schema_fingerprint,
520            entity_count,
521        }
522    }
523
524    /// Return the maximum latest schema version represented in the catalog.
525    #[must_use]
526    pub(in crate::db) const fn schema_version(self) -> SchemaVersion {
527        self.schema_version
528    }
529
530    /// Return the fingerprint method version for this diagnostic metadata row.
531    #[must_use]
532    pub(in crate::db) const fn schema_fingerprint_method_version(self) -> u8 {
533        self.schema_fingerprint_method_version
534    }
535
536    /// Return the deterministic catalog fingerprint for selected accepted
537    /// snapshots.
538    #[must_use]
539    pub(in crate::db) const fn schema_fingerprint(self) -> CommitSchemaFingerprint {
540        self.schema_fingerprint
541    }
542
543    /// Return number of entity schemas represented in this catalog metadata.
544    #[must_use]
545    pub(in crate::db) const fn entity_count(self) -> u64 {
546        self.entity_count
547    }
548}
549
550///
551/// SchemaStoreAllocationMetadata
552///
553/// Role-specific allocation metadata derived from the selected accepted bundle. These fingerprints describe the accepted contract that owns each
554/// allocation role; they are diagnostics, not allocation identity.
555///
556
557#[derive(Clone, Copy, Debug, Eq, PartialEq)]
558pub(in crate::db) struct SchemaStoreAllocationMetadata {
559    data: SchemaStoreCatalogMetadata,
560    index: SchemaStoreCatalogMetadata,
561    schema: SchemaStoreCatalogMetadata,
562}
563
564impl SchemaStoreAllocationMetadata {
565    /// Build one role-specific metadata set from already-derived accepted
566    /// schema facts.
567    #[must_use]
568    const fn new(
569        data: SchemaStoreCatalogMetadata,
570        index: SchemaStoreCatalogMetadata,
571        schema: SchemaStoreCatalogMetadata,
572    ) -> Self {
573        Self {
574            data,
575            index,
576            schema,
577        }
578    }
579
580    /// Return accepted row-layout allocation metadata for data memory.
581    #[must_use]
582    pub(in crate::db) const fn data(self) -> SchemaStoreCatalogMetadata {
583        self.data
584    }
585
586    /// Return accepted index-catalog allocation metadata for index memory.
587    #[must_use]
588    pub(in crate::db) const fn index(self) -> SchemaStoreCatalogMetadata {
589        self.index
590    }
591
592    /// Return accepted full schema-catalog allocation metadata for schema
593    /// memory.
594    #[must_use]
595    pub(in crate::db) const fn schema(self) -> SchemaStoreCatalogMetadata {
596        self.schema
597    }
598}
599
600///
601/// PendingRelationActivationDeleteBarrier
602///
603/// Accepted activation identity that blocks target deletion until a candidate
604/// reverse-relation generation is proven and promoted.
605///
606
607pub(in crate::db) struct PendingRelationActivationDeleteBarrier {
608    accepted_schema_fingerprint: CommitSchemaFingerprint,
609    source_entity_tag: EntityTag,
610    constraint_id: ConstraintId,
611}
612
613impl PendingRelationActivationDeleteBarrier {
614    #[must_use]
615    pub(in crate::db) const fn accepted_schema_fingerprint(&self) -> CommitSchemaFingerprint {
616        self.accepted_schema_fingerprint
617    }
618
619    #[must_use]
620    pub(in crate::db) const fn source_entity_tag(&self) -> EntityTag {
621        self.source_entity_tag
622    }
623
624    /// Return the stable accepted constraint identity.
625    #[must_use]
626    pub(in crate::db) const fn constraint_id(&self) -> ConstraintId {
627        self.constraint_id
628    }
629}
630
631///
632/// SchemaStore
633///
634/// Thin persistence wrapper over one journaled or heap schema metadata BTreeMap.
635/// Startup reconciliation writes and validates encoded schema snapshots here
636/// before row/index operations proceed.
637///
638
639pub struct SchemaStore {
640    backend: SchemaStoreBackend,
641    accepted_bundle_cache: RefCell<Option<AcceptedSchemaBundleCache>>,
642    cardinality_header_cache: RefCell<Option<(Vec<u8>, CardinalityGenerationHeader)>>,
643    accepted_catalog_scope: OnceCell<AcceptedStoreCatalogScope>,
644}
645
646struct AcceptedSchemaBundleCache {
647    selection: AcceptedSchemaRootSelection,
648    bundle: AcceptedSchemaRevisionBundle,
649    cardinality_domain: Rc<CardinalityAcceptedDomain>,
650    value_catalog: AcceptedValueCatalogHandle,
651    entity_selections: RefCell<StdBTreeMap<EntityTag, AcceptedCatalogSnapshotSelection>>,
652}
653
654enum SchemaStoreBackend {
655    Heap(StdBTreeMap<RawSchemaKey, RawSchemaValue>),
656    Journaled {
657        canonical: StableBTreeMap<RawSchemaKey, RawSchemaValue, DefaultMemoryImpl>,
658        live: StdBTreeMap<RawSchemaKey, RawSchemaValue>,
659        tombstones: BTreeSet<RawSchemaKey>,
660        positions: PositionedOverlayMetadata<RawSchemaKey>,
661    },
662}
663
664/// Control-flow result for schema-store traversal visitors.
665#[derive(Clone, Copy, Debug, Eq, PartialEq)]
666enum SchemaStoreVisit {
667    Continue,
668    #[cfg(test)]
669    Stop,
670}
671
672impl SchemaStoreVisit {
673    const fn should_stop(self) -> bool {
674        match self {
675            Self::Continue => false,
676            #[cfg(test)]
677            Self::Stop => true,
678        }
679    }
680}
681
682#[derive(Clone, Copy)]
683enum IdentityStateStorageView {
684    Effective,
685    Canonical,
686}
687
688/// Candidate and canonical payloads retained from preflight until atomic fold.
689/// Not a reusable plan: preparation and application belong to one batch callback.
690pub(in crate::db) struct PreparedAcceptedSchemaFold {
691    candidate: CandidateSchemaRevision,
692    expected_revision: AcceptedSchemaRevision,
693    identity_updates: Vec<(RawSchemaKey, Vec<u8>)>,
694    identity_removals: Vec<RawSchemaKey>,
695    retained: BTreeSet<RawSchemaKey>,
696    root_slot: usize,
697}
698
699/// Exact schema/control keys whose live values belong to one journal batch.
700pub(in crate::db) struct PreparedSchemaPositionPublication {
701    keys: Vec<RawSchemaKey>,
702    position: JournalOverlayPosition,
703}
704
705/// Preflighted exact schema/control retirement for one complete journal batch.
706pub(in crate::db) struct PreparedSchemaPositionRetirement {
707    entries: Vec<(RawSchemaKey, PositionedOverlayRetirement)>,
708}
709
710/// Preflighted count-record changes for one isolated cardinality build page.
711pub(in crate::db) struct PreparedCardinalityCountWrites {
712    slot: CardinalityCountSlot,
713    generation: CardinalityGenerationId,
714    entries: Vec<(RawSchemaKey, RawSchemaValue)>,
715    new_count_keys: u64,
716}
717
718impl PreparedCardinalityCountWrites {
719    #[must_use]
720    pub(in crate::db) const fn new_count_keys(&self) -> u64 {
721        self.new_count_keys
722    }
723}
724
725/// Fully preflighted atomic count-and-cursor publication for one build page.
726pub(in crate::db) struct PreparedCardinalityBuildPage {
727    count_entries: Vec<(RawSchemaKey, RawSchemaValue)>,
728    cursor: (RawSchemaKey, RawSchemaValue),
729}
730
731/// Fully preflighted exact count and Ready-watermark transition for one fold.
732pub(in crate::db) struct PreparedCardinalityMaintenance {
733    count_entries: Vec<(RawSchemaKey, Option<RawSchemaValue>)>,
734    header: (RawSchemaKey, RawSchemaValue),
735}
736
737#[derive(Clone, Copy)]
738enum IdentityStateWriteTarget {
739    Durable,
740    Materialized,
741    Canonical,
742}
743
744impl SchemaStore {
745    /// Initialize a volatile heap-backed schema store.
746    #[must_use]
747    pub const fn init_heap() -> Self {
748        Self {
749            backend: SchemaStoreBackend::Heap(StdBTreeMap::new()),
750            accepted_bundle_cache: RefCell::new(None),
751            cardinality_header_cache: RefCell::new(None),
752            accepted_catalog_scope: OnceCell::new(),
753        }
754    }
755
756    /// Initialize a journaled cached-stable schema store.
757    ///
758    /// Normal schema publication writes only the live projection. Canonical
759    /// stable schema history is updated by future journal fold/recovery paths.
760    #[must_use]
761    pub fn init_journaled(memory: RuntimeMemory<DefaultMemoryImpl>) -> Self {
762        Self {
763            backend: SchemaStoreBackend::Journaled {
764                canonical: StableBTreeMap::init(memory),
765                live: StdBTreeMap::new(),
766                tombstones: BTreeSet::new(),
767                positions: PositionedOverlayMetadata::new(),
768            },
769            accepted_bundle_cache: RefCell::new(None),
770            cardinality_header_cache: RefCell::new(None),
771            accepted_catalog_scope: OnceCell::new(),
772        }
773    }
774
775    /// Load the sole current durable cardinality-generation header.
776    pub(in crate::db) fn cardinality_generation_header(
777        &self,
778    ) -> Result<Option<CardinalityGenerationHeader>, InternalError> {
779        let key = RawSchemaKey::from_cardinality_generation_header();
780        let raw = self.get_canonical_raw_value(&key)?;
781        if let Some(raw) = raw.as_ref() {
782            self.decode_cardinality_header_cached(raw).map(Some)
783        } else {
784            self.cardinality_header_cache
785                .try_borrow_mut()
786                .map_err(|_| InternalError::store_invariant())?
787                .take();
788            Ok(None)
789        }
790    }
791
792    /// Load the sole bounded cardinality build cursor.
793    pub(in crate::db) fn cardinality_build_cursor(
794        &self,
795    ) -> Result<Option<CardinalityBuildCursor>, InternalError> {
796        let key = RawSchemaKey::from_cardinality_build_cursor();
797        self.get_canonical_raw_value(&key)?
798            .map(|raw| CardinalityBuildCursor::decode(raw.as_bytes()))
799            .transpose()
800    }
801
802    /// Load the generation header and build cursor through one bounded control range.
803    pub(in crate::db) fn cardinality_generation_control(
804        &self,
805    ) -> Result<
806        (
807            Option<CardinalityGenerationHeader>,
808            Option<CardinalityBuildCursor>,
809        ),
810        InternalError,
811    > {
812        let SchemaStoreBackend::Journaled { canonical, .. } = &self.backend else {
813            return Err(InternalError::store_invariant());
814        };
815        let header_key = RawSchemaKey::from_cardinality_generation_header();
816        let cursor_key = RawSchemaKey::from_cardinality_build_cursor();
817        let mut header = None;
818        let mut cursor = None;
819        for entry in canonical.range(header_key..=cursor_key) {
820            if *entry.key() == header_key {
821                header = Some(self.decode_cardinality_header_cached(&entry.value())?);
822            } else if *entry.key() == cursor_key {
823                cursor = Some(CardinalityBuildCursor::decode(entry.value().as_bytes())?);
824            } else {
825                return Err(InternalError::store_corruption());
826            }
827        }
828        Ok((header, cursor))
829    }
830
831    /// Load only lifecycle identity needed by advisory cardinality consumers.
832    ///
833    /// Cursor payload progress is deliberately not decoded or retained here:
834    /// Building progress does not change whether exact evidence is available.
835    pub(in crate::db) fn cardinality_generation_lifecycle_control(
836        &self,
837    ) -> Result<(Option<CardinalityGenerationHeader>, bool), InternalError> {
838        let SchemaStoreBackend::Journaled { canonical, .. } = &self.backend else {
839            return Err(InternalError::store_invariant());
840        };
841        let header_key = RawSchemaKey::from_cardinality_generation_header();
842        let cursor_key = RawSchemaKey::from_cardinality_build_cursor();
843        let mut header = None;
844        let mut cursor_present = false;
845        for entry in canonical.range(header_key..=cursor_key) {
846            if *entry.key() == header_key {
847                header = Some(self.decode_cardinality_header_cached(&entry.value())?);
848            } else if *entry.key() == cursor_key {
849                cursor_present = true;
850            } else {
851                return Err(InternalError::store_corruption());
852            }
853        }
854
855        Ok((header, cursor_present))
856    }
857
858    fn decode_cardinality_header_cached(
859        &self,
860        raw: &RawSchemaValue,
861    ) -> Result<CardinalityGenerationHeader, InternalError> {
862        if let Some((_, header)) = self
863            .cardinality_header_cache
864            .try_borrow()
865            .map_err(|_| InternalError::store_invariant())?
866            .as_ref()
867            .filter(|(bytes, _)| bytes.as_slice() == raw.as_bytes())
868        {
869            return Ok(*header);
870        }
871        let header = CardinalityGenerationHeader::decode(raw.as_bytes())?;
872        *self
873            .cardinality_header_cache
874            .try_borrow_mut()
875            .map_err(|_| InternalError::store_invariant())? =
876            Some((raw.as_bytes().to_vec(), header));
877        Ok(header)
878    }
879
880    /// Prove that no current cardinality authority or orphaned slot data exists.
881    pub(in crate::db) fn cardinality_storage_is_pristine(&self) -> Result<bool, InternalError> {
882        if self.cardinality_generation_header()?.is_some()
883            || self.cardinality_build_cursor()?.is_some()
884        {
885            return Ok(false);
886        }
887        Ok(
888            self.cardinality_count_slot_is_empty(CardinalityCountSlot::A)?
889                && self.cardinality_count_slot_is_empty(CardinalityCountSlot::B)?,
890        )
891    }
892
893    /// Persist one already-validated cardinality header into canonical control storage.
894    pub(in crate::db) fn write_cardinality_generation_header(
895        &mut self,
896        header: CardinalityGenerationHeader,
897    ) -> Result<(), InternalError> {
898        self.insert_canonical_raw_value(
899            RawSchemaKey::from_cardinality_generation_header(),
900            header.encode(),
901        )
902    }
903
904    /// Replace stale cardinality evidence with a fresh isolated Building generation.
905    ///
906    /// Every fallible read, generation increment, and backend check happens
907    /// before the header switch. Removing the obsolete cursor afterward is a
908    /// mechanical stable-map operation in the same replicated message.
909    pub(in crate::db) fn restart_cardinality_generation(
910        &mut self,
911        current: CardinalityGenerationHeader,
912        source: CardinalitySourceIdentity,
913    ) -> Result<CardinalityGenerationHeader, InternalError> {
914        if self.cardinality_generation_header()? != Some(current) {
915            return Err(InternalError::store_corruption());
916        }
917        if current.validate_source(source).is_ok() {
918            return Err(InternalError::store_invariant());
919        }
920        if let Some(cursor) = self.cardinality_build_cursor()? {
921            cursor.validate_header(current)?;
922        }
923        let next = CardinalityGenerationHeader::new(
924            current.generation().checked_next()?,
925            CardinalityGenerationState::Building,
926            current.slot().alternate(),
927            source,
928        );
929        let encoded = RawSchemaValue::from_encoded_control_record(next.encode());
930        let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
931            return Err(InternalError::store_invariant());
932        };
933        canonical.insert(RawSchemaKey::from_cardinality_generation_header(), encoded);
934        canonical.remove(&RawSchemaKey::from_cardinality_build_cursor());
935        Ok(next)
936    }
937
938    /// Publish exact zero for a physically empty canonical row/index domain.
939    pub(in crate::db) fn publish_empty_cardinality_generation(
940        &mut self,
941        candidate: &EmptyCardinalityReadyCandidate,
942    ) -> Result<CardinalityGenerationHeader, InternalError> {
943        if !self.cardinality_storage_is_pristine()? {
944            return Err(InternalError::store_corruption());
945        }
946        let ready = CardinalityGenerationHeader::new(
947            CardinalityGenerationId::INITIAL,
948            CardinalityGenerationState::Ready,
949            CardinalityCountSlot::A,
950            candidate.source(),
951        );
952        let encoded = RawSchemaValue::from_encoded_control_record(ready.encode());
953        let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
954            return Err(InternalError::store_invariant());
955        };
956        canonical.insert(RawSchemaKey::from_cardinality_generation_header(), encoded);
957        Ok(ready)
958    }
959
960    /// Atomically make one completely exhausted candidate planner-visible.
961    pub(in crate::db) fn publish_ready_cardinality_generation(
962        &mut self,
963        candidate: &CardinalityReadyCandidate,
964        source: CardinalitySourceIdentity,
965    ) -> Result<CardinalityGenerationHeader, InternalError> {
966        let building = candidate.header();
967        candidate.cursor().validate_header(building)?;
968        if building.state() != CardinalityGenerationState::Building
969            || building.validate_source(source).is_err()
970            || self.cardinality_generation_header()? != Some(building)
971            || self.cardinality_build_cursor()?.as_ref() != Some(candidate.cursor())
972        {
973            return Err(InternalError::store_corruption());
974        }
975        let ready = CardinalityGenerationHeader::new(
976            building.generation(),
977            CardinalityGenerationState::Ready,
978            building.slot(),
979            building.source(),
980        );
981        let encoded = RawSchemaValue::from_encoded_control_record(ready.encode());
982        let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
983            return Err(InternalError::store_invariant());
984        };
985        canonical.insert(RawSchemaKey::from_cardinality_generation_header(), encoded);
986        canonical.remove(&RawSchemaKey::from_cardinality_build_cursor());
987        Ok(ready)
988    }
989
990    /// Clear at most `limit` records from one inactive count slot.
991    ///
992    /// When the slot becomes empty, the initial Rows cursor is installed in
993    /// the same mutation boundary so a resumed builder never confuses clearing
994    /// with scanning.
995    pub(in crate::db) fn clear_cardinality_count_slot_page(
996        &mut self,
997        header: CardinalityGenerationHeader,
998        initial_cursor: &CardinalityBuildCursor,
999        limit: usize,
1000    ) -> Result<bool, InternalError> {
1001        if limit == 0 {
1002            return Err(InternalError::store_invariant());
1003        }
1004        initial_cursor.validate_header(header)?;
1005        let encoded_cursor = initial_cursor.encode()?;
1006        if self.cardinality_generation_header()? != Some(header)
1007            || self.cardinality_build_cursor()?.is_some()
1008        {
1009            return Err(InternalError::store_corruption());
1010        }
1011        let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1012            return Err(InternalError::store_invariant());
1013        };
1014        let bounds = RawSchemaKey::cardinality_count_range_bounds(header.slot());
1015        let collect_limit = limit
1016            .checked_add(1)
1017            .ok_or_else(InternalError::store_unsupported)?;
1018        let mut keys = Vec::new();
1019        keys.try_reserve_exact(collect_limit)
1020            .map_err(|_| InternalError::store_unsupported())?;
1021        for entry in canonical.range(bounds).take(collect_limit) {
1022            keys.push(*entry.key());
1023        }
1024        let has_more = keys.len() > limit;
1025        for key in keys.into_iter().take(limit) {
1026            canonical.remove(&key);
1027        }
1028        if !has_more {
1029            canonical.insert(
1030                RawSchemaKey::from_cardinality_build_cursor(),
1031                RawSchemaValue::from_encoded_control_record(encoded_cursor),
1032            );
1033        }
1034        Ok(has_more)
1035    }
1036
1037    /// Preflight coalesced positive count increments for one isolated page.
1038    pub(in crate::db) fn prepare_cardinality_count_increments(
1039        &self,
1040        slot: CardinalityCountSlot,
1041        generation: CardinalityGenerationId,
1042        increments: &[(CardinalityCountDigest, u64)],
1043    ) -> Result<PreparedCardinalityCountWrites, InternalError> {
1044        if !matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
1045            return Err(InternalError::store_invariant());
1046        }
1047        let mut entries = Vec::new();
1048        entries
1049            .try_reserve_exact(increments.len())
1050            .map_err(|_| InternalError::store_unsupported())?;
1051        let mut physical_keys = StdBTreeMap::new();
1052        let mut new_count_keys = 0_u64;
1053        for (digest, increment) in increments {
1054            if *increment == 0 {
1055                return Err(InternalError::store_invariant());
1056            }
1057            let key = RawSchemaKey::from_cardinality_count(slot, *digest);
1058            if let Some(previous_digest) = physical_keys.insert(key, *digest) {
1059                return Err(if previous_digest == *digest {
1060                    InternalError::store_invariant()
1061                } else {
1062                    InternalError::store_corruption()
1063                });
1064            }
1065            let current = self.get_canonical_raw_value(&key)?;
1066            let count = if let Some(raw) = current {
1067                let record = CardinalityCountRecord::decode(raw.as_bytes())?;
1068                record
1069                    .validate_identity(generation, *digest)
1070                    .map_err(|_| InternalError::store_corruption())?
1071                    .checked_add(*increment)
1072                    .ok_or_else(InternalError::store_unsupported)?
1073            } else {
1074                new_count_keys = new_count_keys
1075                    .checked_add(1)
1076                    .ok_or_else(InternalError::store_unsupported)?;
1077                *increment
1078            };
1079            let record = CardinalityCountRecord::new(generation, *digest, count)?;
1080            entries.push((
1081                key,
1082                RawSchemaValue::from_encoded_control_record(record.encode().to_vec()),
1083            ));
1084        }
1085        Ok(PreparedCardinalityCountWrites {
1086            slot,
1087            generation,
1088            entries,
1089            new_count_keys,
1090        })
1091    }
1092
1093    /// Bind preflighted count writes to the exact current cursor transition.
1094    pub(in crate::db) fn prepare_cardinality_build_page(
1095        &self,
1096        header: CardinalityGenerationHeader,
1097        current_cursor: &CardinalityBuildCursor,
1098        counts: PreparedCardinalityCountWrites,
1099        next_cursor: &CardinalityBuildCursor,
1100    ) -> Result<PreparedCardinalityBuildPage, InternalError> {
1101        current_cursor.validate_header(header)?;
1102        next_cursor.validate_header(header)?;
1103        if counts.slot != header.slot() || counts.generation != header.generation() {
1104            return Err(InternalError::store_invariant());
1105        }
1106        if self.cardinality_generation_header()? != Some(header)
1107            || self.cardinality_build_cursor()?.as_ref() != Some(current_cursor)
1108        {
1109            return Err(InternalError::store_corruption());
1110        }
1111        let encoded_cursor = next_cursor.encode()?;
1112        Ok(PreparedCardinalityBuildPage {
1113            count_entries: counts.entries,
1114            cursor: (
1115                RawSchemaKey::from_cardinality_build_cursor(),
1116                RawSchemaValue::from_encoded_control_record(encoded_cursor),
1117            ),
1118        })
1119    }
1120
1121    /// Mechanically publish one fully preflighted count-and-cursor page.
1122    pub(in crate::db) fn apply_prepared_cardinality_build_page(
1123        &mut self,
1124        prepared: PreparedCardinalityBuildPage,
1125    ) -> Result<(), InternalError> {
1126        let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1127            return Err(InternalError::store_invariant());
1128        };
1129        for (key, value) in prepared.count_entries {
1130            canonical.insert(key, value);
1131        }
1132        canonical.insert(prepared.cursor.0, prepared.cursor.1);
1133        Ok(())
1134    }
1135
1136    /// Load one exact nonzero count from an isolated generation.
1137    pub(in crate::db) fn cardinality_count(
1138        &self,
1139        slot: CardinalityCountSlot,
1140        generation: CardinalityGenerationId,
1141        digest: CardinalityCountDigest,
1142    ) -> Result<Option<u64>, InternalError> {
1143        let key = RawSchemaKey::from_cardinality_count(slot, digest);
1144        self.get_canonical_raw_value(&key)?
1145            .map(|raw| {
1146                CardinalityCountRecord::decode(raw.as_bytes())?
1147                    .validate_identity(generation, digest)
1148                    .map_err(|_| InternalError::store_corruption())
1149            })
1150            .transpose()
1151    }
1152
1153    /// Preflight exact coalesced count changes and the next complete fold watermark.
1154    pub(in crate::db) fn prepare_cardinality_maintenance(
1155        &self,
1156        current: CardinalityGenerationHeader,
1157        current_source: CardinalitySourceIdentity,
1158        next_source: CardinalitySourceIdentity,
1159        changes: &[(CardinalityCountDigest, i64)],
1160    ) -> Result<PreparedCardinalityMaintenance, InternalError> {
1161        if current.state() != CardinalityGenerationState::Ready
1162            || current.validate_source(current_source).is_err()
1163            || self.cardinality_generation_header()? != Some(current)
1164            || self.cardinality_build_cursor()?.is_some()
1165        {
1166            return Err(InternalError::store_corruption());
1167        }
1168        let next = CardinalityGenerationHeader::new(
1169            current.generation(),
1170            CardinalityGenerationState::Ready,
1171            current.slot(),
1172            next_source,
1173        );
1174        let mut count_entries = Vec::new();
1175        count_entries
1176            .try_reserve_exact(changes.len())
1177            .map_err(|_| InternalError::store_unsupported())?;
1178        let mut physical_keys = StdBTreeMap::new();
1179        for (digest, delta) in changes {
1180            if *delta == 0 {
1181                return Err(InternalError::store_invariant());
1182            }
1183            let key = RawSchemaKey::from_cardinality_count(current.slot(), *digest);
1184            if let Some(previous_digest) = physical_keys.insert(key, *digest) {
1185                return Err(if previous_digest == *digest {
1186                    InternalError::store_invariant()
1187                } else {
1188                    InternalError::store_corruption()
1189                });
1190            }
1191            let base = self
1192                .cardinality_count(current.slot(), current.generation(), *digest)?
1193                .unwrap_or(0);
1194            let count = if *delta > 0 {
1195                base.checked_add(
1196                    u64::try_from(*delta).map_err(|_| InternalError::store_invariant())?,
1197                )
1198            } else {
1199                base.checked_sub(delta.unsigned_abs())
1200            }
1201            .ok_or_else(InternalError::store_corruption)?;
1202            let value = if count == 0 {
1203                None
1204            } else {
1205                Some(RawSchemaValue::from_encoded_control_record(
1206                    CardinalityCountRecord::new(current.generation(), *digest, count)?
1207                        .encode()
1208                        .to_vec(),
1209                ))
1210            };
1211            count_entries.push((key, value));
1212        }
1213        Ok(PreparedCardinalityMaintenance {
1214            count_entries,
1215            header: (
1216                RawSchemaKey::from_cardinality_generation_header(),
1217                RawSchemaValue::from_encoded_control_record(next.encode()),
1218            ),
1219        })
1220    }
1221
1222    /// Mechanically publish one completely preflighted count/watermark transition.
1223    pub(in crate::db) fn apply_prepared_cardinality_maintenance(
1224        &mut self,
1225        prepared: PreparedCardinalityMaintenance,
1226    ) -> Result<(), InternalError> {
1227        let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1228            return Err(InternalError::store_invariant());
1229        };
1230        for (key, value) in prepared.count_entries {
1231            if let Some(value) = value {
1232                canonical.insert(key, value);
1233            } else {
1234                canonical.remove(&key);
1235            }
1236        }
1237        canonical.insert(prepared.header.0, prepared.header.1);
1238        Ok(())
1239    }
1240
1241    pub(in crate::db) fn cardinality_count_slot_is_empty(
1242        &self,
1243        slot: CardinalityCountSlot,
1244    ) -> Result<bool, InternalError> {
1245        let SchemaStoreBackend::Journaled { canonical, .. } = &self.backend else {
1246            return Err(InternalError::store_invariant());
1247        };
1248        Ok(canonical
1249            .range(RawSchemaKey::cardinality_count_range_bounds(slot))
1250            .next()
1251            .is_none())
1252    }
1253
1254    /// Prove that recovered journal metadata can fold into canonical storage.
1255    pub(in crate::db) fn preflight_fold_recovered_journal(&self) -> Result<(), InternalError> {
1256        match self.backend {
1257            SchemaStoreBackend::Journaled { .. } => Ok(()),
1258            SchemaStoreBackend::Heap(_) => Err(InternalError::store_invariant()),
1259        }
1260    }
1261
1262    fn prepare_identity_state_transition(
1263        &self,
1264        incarnation: DatabaseIncarnationId,
1265        candidate: &CandidateSchemaRevision,
1266        view: IdentityStateStorageView,
1267    ) -> Result<IdentityStateTransition, InternalError> {
1268        let current = match view {
1269            IdentityStateStorageView::Effective => self
1270                .current_accepted_schema_bundle_ref()?
1271                .as_ref()
1272                .map(|bundle| (*bundle).clone()),
1273            IdentityStateStorageView::Canonical => {
1274                self.current_canonical_accepted_schema_bundle()?
1275            }
1276        };
1277        let inventory = self.identity_state_inventory(view)?;
1278        prepare_identity_state_transition(
1279            incarnation,
1280            current.as_ref(),
1281            candidate.bundle(),
1282            inventory,
1283        )
1284    }
1285
1286    fn validate_identity_state_closure(
1287        &self,
1288        bundle: &AcceptedSchemaRevisionBundle,
1289    ) -> Result<(), InternalError> {
1290        let inventory = self.identity_state_inventory(IdentityStateStorageView::Effective)?;
1291        validate_identity_state_closure(bundle, &inventory)
1292    }
1293
1294    /// Read one accepted active Identity owner into statement-local allocation state.
1295    pub(in crate::db) fn identity_statement_cursor(
1296        &self,
1297        database_incarnation_id: DatabaseIncarnationId,
1298        entity_tag: EntityTag,
1299        field_id: FieldId,
1300        accepted_kind: &AcceptedFieldKind,
1301    ) -> Result<IdentityStatementCursor, InternalError> {
1302        let key = RawSchemaKey::from_identity_state(entity_tag, field_id);
1303        let raw = self
1304            .get_raw_value(&key)
1305            .ok_or_else(InternalError::identity_state_corruption)?;
1306        let state = decode_identity_state(raw.as_bytes())?;
1307        let owner = state.owner();
1308        if owner.database_incarnation_id() != database_incarnation_id
1309            || owner.entity_tag() != entity_tag
1310            || owner.field_id() != field_id
1311            || state.accepted_kind() != accepted_kind
1312            || state.lifecycle() != IdentityStateLifecycle::Active
1313        {
1314            return Err(InternalError::identity_state_corruption());
1315        }
1316        IdentityStatementCursor::from_active_state(&state)
1317    }
1318
1319    /// Read one quiescent materialized high-water for bounded row integrity.
1320    pub(in crate::db) fn identity_high_water_for_integrity(
1321        &self,
1322        database_incarnation_id: DatabaseIncarnationId,
1323        entity_tag: EntityTag,
1324        field_id: FieldId,
1325        accepted_kind: &AcceptedFieldKind,
1326    ) -> Result<u128, InternalError> {
1327        let key = RawSchemaKey::from_identity_state(entity_tag, field_id);
1328        let raw = self
1329            .get_raw_value(&key)
1330            .ok_or_else(InternalError::identity_state_corruption)?;
1331        let state = decode_identity_state(raw.as_bytes())?;
1332        let owner = state.owner();
1333        if owner.database_incarnation_id() != database_incarnation_id
1334            || owner.entity_tag() != entity_tag
1335            || owner.field_id() != field_id
1336            || state.accepted_kind() != accepted_kind
1337            || state.lifecycle() != IdentityStateLifecycle::Active
1338        {
1339            return Err(InternalError::identity_state_corruption());
1340        }
1341        Ok(state.materialized_high_water())
1342    }
1343
1344    /// Revalidate one tentative range against the quiescent effective state.
1345    pub(in crate::db) fn preflight_identity_range_advance(
1346        &self,
1347        range: IdentityRangeAdvance,
1348    ) -> Result<(), InternalError> {
1349        let state =
1350            self.identity_state_for_owner(range.owner(), IdentityStateStorageView::Effective)?;
1351        state.preflight_range_advance(range)
1352    }
1353
1354    /// Materialize one marker-owned range in the effective live projection.
1355    pub(in crate::db) fn apply_identity_range_advance(
1356        &mut self,
1357        range: IdentityRangeAdvance,
1358        advance_id: IdentityAdvanceId,
1359    ) -> Result<(), InternalError> {
1360        self.apply_identity_range_advance_to(
1361            range,
1362            advance_id,
1363            IdentityStateStorageView::Effective,
1364            IdentityStateWriteTarget::Materialized,
1365        )
1366    }
1367
1368    /// Fold one marker-owned range into canonical journaled state.
1369    pub(in crate::db) fn fold_identity_range_advance(
1370        &mut self,
1371        range: IdentityRangeAdvance,
1372        advance_id: IdentityAdvanceId,
1373    ) -> Result<(), InternalError> {
1374        self.apply_identity_range_advance_to(
1375            range,
1376            advance_id,
1377            IdentityStateStorageView::Canonical,
1378            IdentityStateWriteTarget::Canonical,
1379        )
1380    }
1381
1382    /// Preflight one canonical Identity range fold without changing storage.
1383    pub(in crate::db) fn preflight_fold_identity_range_advance(
1384        &self,
1385        range: IdentityRangeAdvance,
1386        advance_id: IdentityAdvanceId,
1387    ) -> Result<(), InternalError> {
1388        if !matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
1389            return Err(InternalError::store_invariant());
1390        }
1391        let state =
1392            self.identity_state_for_owner(range.owner(), IdentityStateStorageView::Canonical)?;
1393        let advanced = state.apply_range_advance(range, advance_id)?;
1394        let _encoded = encode_identity_state(&advanced)?;
1395        Ok(())
1396    }
1397
1398    /// Verify one exact range identity against effective state.
1399    pub(in crate::db) fn verify_identity_range_advance(
1400        &self,
1401        range: IdentityRangeAdvance,
1402        advance_id: IdentityAdvanceId,
1403    ) -> Result<(), InternalError> {
1404        let state =
1405            self.identity_state_for_owner(range.owner(), IdentityStateStorageView::Effective)?;
1406        if state.materialized_high_water() != range.new_high_water()
1407            || state.last_applied_advance() != Some(advance_id)
1408        {
1409            return Err(InternalError::recovery_effect_verification_failed());
1410        }
1411        Ok(())
1412    }
1413
1414    /// Resolve committed versus materialized range state without changing it.
1415    pub(in crate::db) fn identity_range_commit_state(
1416        &self,
1417        range: IdentityRangeAdvance,
1418        advance_id: IdentityAdvanceId,
1419        canonical: bool,
1420    ) -> Result<IdentityRangeCommitState, InternalError> {
1421        let view = if canonical {
1422            IdentityStateStorageView::Canonical
1423        } else {
1424            IdentityStateStorageView::Effective
1425        };
1426        self.identity_state_for_owner(range.owner(), view)?
1427            .range_commit_state(range, advance_id)
1428    }
1429
1430    /// Enumerate and validate the complete current-form active/retired state
1431    /// inventory for bounded database-wide integrity inspection.
1432    pub(in crate::db) fn identity_state_inventory_for_integrity(
1433        &self,
1434        incarnation: DatabaseIncarnationId,
1435    ) -> Result<Vec<IdentityState>, InternalError> {
1436        let has_accepted_bundle = self.current_accepted_schema_bundle_ref()?.is_some();
1437        let inventory = self.identity_state_inventory(IdentityStateStorageView::Effective)?;
1438        if !has_accepted_bundle && !inventory.is_empty() {
1439            return Err(InternalError::identity_state_corruption());
1440        }
1441        if inventory
1442            .values()
1443            .any(|state| state.owner().database_incarnation_id() != incarnation)
1444        {
1445            return Err(InternalError::identity_state_corruption());
1446        }
1447        Ok(inventory.into_values().collect())
1448    }
1449
1450    fn identity_state_for_owner(
1451        &self,
1452        owner: crate::db::schema::identity_state::IdentityStateOwner,
1453        view: IdentityStateStorageView,
1454    ) -> Result<IdentityState, InternalError> {
1455        let key = RawSchemaKey::from_identity_state(owner.entity_tag(), owner.field_id());
1456        let raw = match view {
1457            IdentityStateStorageView::Effective => self.get_raw_value(&key),
1458            IdentityStateStorageView::Canonical => self.get_canonical_raw_value(&key)?,
1459        }
1460        .ok_or_else(InternalError::identity_state_corruption)?;
1461        let state = decode_identity_state(raw.as_bytes())?;
1462        if state.owner() != owner {
1463            return Err(InternalError::identity_state_corruption());
1464        }
1465        Ok(state)
1466    }
1467
1468    fn apply_identity_range_advance_to(
1469        &mut self,
1470        range: IdentityRangeAdvance,
1471        advance_id: IdentityAdvanceId,
1472        view: IdentityStateStorageView,
1473        target: IdentityStateWriteTarget,
1474    ) -> Result<(), InternalError> {
1475        let state = self.identity_state_for_owner(range.owner(), view)?;
1476        let advanced = state.apply_range_advance(range, advance_id)?;
1477        let key = RawSchemaKey::from_identity_state(
1478            advanced.owner().entity_tag(),
1479            advanced.owner().field_id(),
1480        );
1481        let bytes = encode_identity_state(&advanced)?;
1482        match target {
1483            IdentityStateWriteTarget::Materialized => {
1484                self.insert_raw_value(key, RawSchemaValue::from_encoded_control_record(bytes));
1485            }
1486            IdentityStateWriteTarget::Canonical => {
1487                self.insert_canonical_raw_value(key, bytes)?;
1488            }
1489            IdentityStateWriteTarget::Durable => {
1490                return Err(InternalError::store_invariant());
1491            }
1492        }
1493        Ok(())
1494    }
1495
1496    fn identity_state_inventory(
1497        &self,
1498        view: IdentityStateStorageView,
1499    ) -> Result<IdentityStateInventory, InternalError> {
1500        let bounds = RawSchemaKey::all_identity_state_range_bounds();
1501        let mut inventory = StdBTreeMap::new();
1502        let mut collect =
1503            |key: &RawSchemaKey, raw: &RawSchemaValue| -> Result<SchemaStoreVisit, InternalError> {
1504                if inventory.len() >= MAX_IDENTITY_STATE_RECORDS_PER_DATABASE {
1505                    return Err(InternalError::identity_state_corruption());
1506                }
1507                let state = decode_identity_state(raw.as_bytes())?;
1508                let state_key = (key.entity_tag(), FieldId::new(key.version()));
1509                if !key.is_identity_state()
1510                    || state.owner().entity_tag() != state_key.0
1511                    || state.owner().field_id() != state_key.1
1512                    || inventory.insert(state_key, state).is_some()
1513                {
1514                    return Err(InternalError::identity_state_corruption());
1515                }
1516                Ok(SchemaStoreVisit::Continue)
1517            };
1518
1519        match (&self.backend, view) {
1520            (SchemaStoreBackend::Heap(map), IdentityStateStorageView::Effective) => {
1521                for (key, raw) in map.range((bounds.0, bounds.1)) {
1522                    collect(key, raw)?;
1523                }
1524            }
1525            (
1526                SchemaStoreBackend::Journaled {
1527                    canonical,
1528                    live,
1529                    tombstones,
1530                    ..
1531                },
1532                IdentityStateStorageView::Effective,
1533            ) => Self::visit_journaled_raw_value_range(
1534                canonical,
1535                live,
1536                tombstones,
1537                bounds,
1538                Direction::Asc,
1539                &mut collect,
1540            )?,
1541            (
1542                SchemaStoreBackend::Journaled { canonical, .. },
1543                IdentityStateStorageView::Canonical,
1544            ) => {
1545                for entry in canonical.range((bounds.0, bounds.1)) {
1546                    collect(entry.key(), &entry.value())?;
1547                }
1548            }
1549            (SchemaStoreBackend::Heap(_), IdentityStateStorageView::Canonical) => {
1550                return Err(InternalError::store_invariant());
1551            }
1552        }
1553
1554        Ok(inventory)
1555    }
1556
1557    fn apply_identity_state_transition(
1558        &mut self,
1559        transition: IdentityStateTransition,
1560        target: IdentityStateWriteTarget,
1561    ) -> Result<(), InternalError> {
1562        let (updates, removals) = transition.into_effects();
1563        for owner in removals {
1564            self.remove_identity_state_key(
1565                RawSchemaKey::from_identity_state(owner.entity_tag(), owner.field_id()),
1566                target,
1567            )?;
1568        }
1569        for state in updates {
1570            let key = RawSchemaKey::from_identity_state(
1571                state.owner().entity_tag(),
1572                state.owner().field_id(),
1573            );
1574            let bytes = encode_identity_state(&state)?;
1575            match target {
1576                IdentityStateWriteTarget::Durable => {
1577                    self.insert_durable_raw_value(key, bytes);
1578                }
1579                IdentityStateWriteTarget::Materialized => {
1580                    self.insert_raw_value(key, RawSchemaValue::from_encoded_control_record(bytes));
1581                }
1582                IdentityStateWriteTarget::Canonical => {
1583                    self.insert_canonical_raw_value(key, bytes)?;
1584                }
1585            }
1586        }
1587        Ok(())
1588    }
1589
1590    // A retained allocator moves keys with its accepted field. These deletes
1591    // follow the same destination authority as schema publication; canonical
1592    // folding must not erase a newer positioned live state.
1593    fn remove_identity_state_key(
1594        &mut self,
1595        key: RawSchemaKey,
1596        target: IdentityStateWriteTarget,
1597    ) -> Result<(), InternalError> {
1598        match (&mut self.backend, target) {
1599            (
1600                SchemaStoreBackend::Heap(map),
1601                IdentityStateWriteTarget::Durable | IdentityStateWriteTarget::Materialized,
1602            ) => {
1603                map.remove(&key);
1604            }
1605            (
1606                SchemaStoreBackend::Journaled {
1607                    live, tombstones, ..
1608                },
1609                IdentityStateWriteTarget::Materialized,
1610            ) => {
1611                live.remove(&key);
1612                tombstones.insert(key);
1613            }
1614            (
1615                SchemaStoreBackend::Journaled {
1616                    canonical,
1617                    live,
1618                    tombstones,
1619                    ..
1620                },
1621                IdentityStateWriteTarget::Durable,
1622            ) => {
1623                live.remove(&key);
1624                tombstones.remove(&key);
1625                canonical.remove(&key);
1626            }
1627            (
1628                SchemaStoreBackend::Journaled { canonical, .. },
1629                IdentityStateWriteTarget::Canonical,
1630            ) => {
1631                canonical.remove(&key);
1632            }
1633            (SchemaStoreBackend::Heap(_), IdentityStateWriteTarget::Canonical) => {
1634                return Err(InternalError::store_invariant());
1635            }
1636        }
1637        Ok(())
1638    }
1639
1640    pub(in crate::db) fn current_canonical_accepted_schema_bundle(
1641        &self,
1642    ) -> Result<Option<AcceptedSchemaRevisionBundle>, InternalError> {
1643        self.current_canonical_accepted_schema_authority()
1644            .map(|authority| authority.map(|(_, bundle)| bundle))
1645    }
1646
1647    /// Load one canonical accepted root and its verified immutable bundle.
1648    pub(in crate::db) fn current_canonical_accepted_schema_authority(
1649        &self,
1650    ) -> Result<Option<(AcceptedSchemaRootSelection, AcceptedSchemaRevisionBundle)>, InternalError>
1651    {
1652        #[cfg(test)]
1653        CANONICAL_SCHEMA_BUNDLE_LOADS.with(|loads| loads.set(loads.get() + 1));
1654        let Some(selection) = self.current_canonical_accepted_schema_root()? else {
1655            return Ok(None);
1656        };
1657        let bundle_key = RawSchemaKey::from_accepted_bundle(selection.root().bundle_key());
1658        let raw = self
1659            .get_canonical_raw_value(&bundle_key)?
1660            .ok_or_else(InternalError::store_corruption)?;
1661        let bundle =
1662            decode_verified_accepted_schema_revision_bundle(selection.root(), raw.as_bytes())?;
1663        Ok(Some((selection, bundle)))
1664    }
1665
1666    // Count real canonical authority loads so batch tests detect per-key reloads
1667    // without replacing the production decoder or relying on native timings.
1668    #[cfg(test)]
1669    pub(in crate::db) fn take_canonical_bundle_load_count_for_tests() -> u64 {
1670        CANONICAL_SCHEMA_BUNDLE_LOADS.with(|loads| loads.replace(0))
1671    }
1672
1673    /// Return the accepted root selected only from canonical predecessor slots.
1674    pub(in crate::db) fn current_canonical_accepted_schema_root(
1675        &self,
1676    ) -> Result<Option<AcceptedSchemaRootSelection>, InternalError> {
1677        let first = self.canonical_root_slot_bytes(0)?;
1678        let second = self.canonical_root_slot_bytes(1)?;
1679        select_current_accepted_schema_root([first.as_deref(), second.as_deref()])
1680    }
1681
1682    /// Select effective and canonical roots from one canonical slot read.
1683    pub(in crate::db) fn current_effective_and_canonical_accepted_schema_roots(
1684        &self,
1685    ) -> Result<
1686        (
1687            Option<AcceptedSchemaRootSelection>,
1688            Option<AcceptedSchemaRootSelection>,
1689        ),
1690        InternalError,
1691    > {
1692        let SchemaStoreBackend::Journaled {
1693            canonical,
1694            live,
1695            tombstones,
1696            ..
1697        } = &self.backend
1698        else {
1699            return Err(InternalError::store_invariant());
1700        };
1701        let first_key = RawSchemaKey::from_accepted_root_slot(0)?;
1702        let second_key = RawSchemaKey::from_accepted_root_slot(1)?;
1703        let mut canonical_first = None;
1704        let mut canonical_second = None;
1705        for entry in canonical.range(first_key..=second_key) {
1706            if *entry.key() == first_key {
1707                canonical_first = Some(entry.value().clone());
1708            } else if *entry.key() == second_key {
1709                canonical_second = Some(entry.value().clone());
1710            } else {
1711                return Err(InternalError::store_corruption());
1712            }
1713        }
1714        let effective_first = if tombstones.contains(&first_key) {
1715            None
1716        } else {
1717            live.get(&first_key)
1718                .cloned()
1719                .or_else(|| canonical_first.clone())
1720        };
1721        let effective_second = if tombstones.contains(&second_key) {
1722            None
1723        } else {
1724            live.get(&second_key)
1725                .cloned()
1726                .or_else(|| canonical_second.clone())
1727        };
1728        let effective_first = effective_first.map(RawSchemaValue::into_bytes);
1729        let effective_second = effective_second.map(RawSchemaValue::into_bytes);
1730        let canonical_first = canonical_first.map(RawSchemaValue::into_bytes);
1731        let canonical_second = canonical_second.map(RawSchemaValue::into_bytes);
1732        Ok((
1733            select_current_accepted_schema_root([
1734                effective_first.as_deref(),
1735                effective_second.as_deref(),
1736            ])?,
1737            select_current_accepted_schema_root([
1738                canonical_first.as_deref(),
1739                canonical_second.as_deref(),
1740            ])?,
1741        ))
1742    }
1743
1744    /// Load one schema-owned constraint validation job.
1745    pub(in crate::db) fn constraint_validation_job(
1746        &self,
1747        entity: EntityTag,
1748        constraint_id: ConstraintId,
1749    ) -> Result<Option<ConstraintValidationJob>, InternalError> {
1750        let key = RawSchemaKey::from_constraint_validation_job(entity, constraint_id);
1751        self.get_raw_value(&key)
1752            .map(|raw| decode_constraint_validation_job(raw.as_bytes()))
1753            .transpose()
1754    }
1755
1756    /// Apply one marker-authorized validation job to the live schema projection.
1757    pub(in crate::db) fn apply_constraint_validation_job(
1758        &mut self,
1759        job: &ConstraintValidationJob,
1760    ) -> Result<(), InternalError> {
1761        let key =
1762            RawSchemaKey::from_constraint_validation_job(job.entity_tag(), job.constraint_id());
1763        let bytes = encode_constraint_validation_job(job)?;
1764        let _ = self.insert_raw_value(key, RawSchemaValue::from_encoded_control_record(bytes));
1765        Ok(())
1766    }
1767
1768    /// Remove one marker-authorized validation job from the live projection.
1769    #[expect(
1770        clippy::unnecessary_wraps,
1771        reason = "marker apply operations share one fallible callback contract"
1772    )]
1773    pub(in crate::db) fn apply_constraint_validation_job_removal(
1774        &mut self,
1775        entity: EntityTag,
1776        constraint_id: ConstraintId,
1777    ) -> Result<(), InternalError> {
1778        let key = RawSchemaKey::from_constraint_validation_job(entity, constraint_id);
1779        match &mut self.backend {
1780            SchemaStoreBackend::Heap(map) => {
1781                map.remove(&key);
1782            }
1783            SchemaStoreBackend::Journaled {
1784                live, tombstones, ..
1785            } => {
1786                live.remove(&key);
1787                tombstones.insert(key);
1788            }
1789        }
1790        Ok(())
1791    }
1792
1793    /// Fold one committed validation job into the canonical stable base.
1794    pub(in crate::db) fn fold_constraint_validation_job(
1795        &mut self,
1796        job: &ConstraintValidationJob,
1797    ) -> Result<(), InternalError> {
1798        let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1799            return Err(InternalError::store_invariant());
1800        };
1801        let key =
1802            RawSchemaKey::from_constraint_validation_job(job.entity_tag(), job.constraint_id());
1803        let bytes = encode_constraint_validation_job(job)?;
1804        canonical.insert(key, RawSchemaValue::from_encoded_control_record(bytes));
1805        Ok(())
1806    }
1807
1808    /// Preflight one canonical validation-job fold without changing storage.
1809    pub(in crate::db) fn preflight_fold_constraint_validation_job(
1810        &self,
1811        job: &ConstraintValidationJob,
1812    ) -> Result<(), InternalError> {
1813        if !matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
1814            return Err(InternalError::store_invariant());
1815        }
1816        let _encoded = encode_constraint_validation_job(job)?;
1817        Ok(())
1818    }
1819
1820    /// Fold one committed validation-job removal into the canonical stable base.
1821    pub(in crate::db) fn fold_constraint_validation_job_removal(
1822        &mut self,
1823        entity: EntityTag,
1824        constraint_id: ConstraintId,
1825    ) -> Result<(), InternalError> {
1826        let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
1827            return Err(InternalError::store_invariant());
1828        };
1829        canonical.remove(&RawSchemaKey::from_constraint_validation_job(
1830            entity,
1831            constraint_id,
1832        ));
1833        Ok(())
1834    }
1835
1836    /// Preflight one canonical validation-job removal without changing storage.
1837    pub(in crate::db) fn preflight_fold_constraint_validation_job_removal(
1838        &self,
1839    ) -> Result<(), InternalError> {
1840        match self.backend {
1841            SchemaStoreBackend::Journaled { .. } => Ok(()),
1842            SchemaStoreBackend::Heap(_) => Err(InternalError::store_invariant()),
1843        }
1844    }
1845
1846    /// Reset the volatile projection for journaled recovery without mutating
1847    /// the canonical stable schema base.
1848    pub(in crate::db) fn reset_journaled_live_projection(&mut self) -> Result<(), InternalError> {
1849        let SchemaStoreBackend::Journaled {
1850            live,
1851            tombstones,
1852            positions,
1853            ..
1854        } = &mut self.backend
1855        else {
1856            return Err(InternalError::store_invariant());
1857        };
1858
1859        live.clear();
1860        tombstones.clear();
1861        positions.clear();
1862        self.accepted_bundle_cache.get_mut().take();
1863
1864        Ok(())
1865    }
1866
1867    /// Preflight every schema/control position represented by one online batch.
1868    pub(in crate::db) fn prepare_positioned_journal_batch_publication(
1869        &self,
1870        incarnation: DatabaseIncarnationId,
1871        batch: &JournalBatch,
1872        position: JournalOverlayPosition,
1873    ) -> Result<PreparedSchemaPositionPublication, InternalError> {
1874        let SchemaStoreBackend::Journaled { positions, .. } = &self.backend else {
1875            return Err(InternalError::store_invariant());
1876        };
1877        let keys = self.positioned_journal_batch_keys(
1878            incarnation,
1879            batch,
1880            IdentityStateStorageView::Effective,
1881        )?;
1882        for key in &keys {
1883            positions.preflight_publish(key, position)?;
1884        }
1885        Ok(PreparedSchemaPositionPublication {
1886            keys: keys.into_iter().collect(),
1887            position,
1888        })
1889    }
1890
1891    /// Preflight exact schema/control retirement before canonical mutation.
1892    pub(in crate::db) fn prepare_positioned_journal_batch_retirement(
1893        &self,
1894        incarnation: DatabaseIncarnationId,
1895        batch: &JournalBatch,
1896        position: JournalOverlayPosition,
1897    ) -> Result<PreparedSchemaPositionRetirement, InternalError> {
1898        let keys = self.positioned_journal_batch_keys(
1899            incarnation,
1900            batch,
1901            IdentityStateStorageView::Canonical,
1902        )?;
1903        self.prepare_positioned_key_retirements(keys, position)
1904    }
1905
1906    fn prepare_positioned_key_retirements(
1907        &self,
1908        keys: impl IntoIterator<Item = RawSchemaKey>,
1909        position: JournalOverlayPosition,
1910    ) -> Result<PreparedSchemaPositionRetirement, InternalError> {
1911        let SchemaStoreBackend::Journaled {
1912            live,
1913            tombstones,
1914            positions,
1915            ..
1916        } = &self.backend
1917        else {
1918            return Err(InternalError::store_invariant());
1919        };
1920        let mut entries = Vec::new();
1921        for key in keys {
1922            if !positions.is_positioned(&key) {
1923                // A prior row fold can create canonical-only derived metadata
1924                // after this older schema batch publishes. Its canonical fold
1925                // owns that key; there is no overlay for this batch to retire.
1926                if live.contains_key(&key) || tombstones.contains(&key) {
1927                    return Err(InternalError::store_invariant());
1928                }
1929                continue;
1930            }
1931            let retirement = positions.preflight_retirement(&key, position)?;
1932            entries.push((key, retirement));
1933        }
1934        Ok(PreparedSchemaPositionRetirement { entries })
1935    }
1936
1937    /// Publish schema positions after their values have been mechanically applied.
1938    pub(in crate::db) fn publish_prepared_journal_batch_positions(
1939        &mut self,
1940        prepared: PreparedSchemaPositionPublication,
1941    ) {
1942        let SchemaStoreBackend::Journaled { positions, .. } = &mut self.backend else {
1943            debug_assert!(
1944                false,
1945                "preflighted schema positions require a journaled store"
1946            );
1947            return;
1948        };
1949        for key in prepared.keys {
1950            positions.publish_preflighted(key, prepared.position);
1951        }
1952    }
1953
1954    /// Retire only exact schema/control overlays after canonical mutation.
1955    pub(in crate::db) fn apply_prepared_journal_batch_retirement(
1956        &mut self,
1957        prepared: PreparedSchemaPositionRetirement,
1958    ) {
1959        for (key, retirement) in prepared.entries {
1960            if retirement != PositionedOverlayRetirement::Exact {
1961                continue;
1962            }
1963            self.invalidate_accepted_bundle_cache_for_key(key);
1964            let SchemaStoreBackend::Journaled {
1965                live,
1966                tombstones,
1967                positions,
1968                ..
1969            } = &mut self.backend
1970            else {
1971                debug_assert!(
1972                    false,
1973                    "preflighted schema retirement requires a journaled store"
1974                );
1975                return;
1976            };
1977            live.remove(&key);
1978            tombstones.remove(&key);
1979            positions.retire_preflighted(&key, retirement);
1980        }
1981    }
1982
1983    #[cfg(test)]
1984    fn publish_positioned_journal_entry(
1985        &mut self,
1986        key: RawSchemaKey,
1987        snapshot: Option<RawSchemaValue>,
1988        position: JournalOverlayPosition,
1989    ) -> Result<Option<RawSchemaValue>, InternalError> {
1990        let SchemaStoreBackend::Journaled { positions, .. } = &self.backend else {
1991            return Err(InternalError::store_invariant());
1992        };
1993        positions.preflight_publish(&key, position)?;
1994        self.invalidate_accepted_bundle_cache_for_key(key);
1995        let SchemaStoreBackend::Journaled {
1996            canonical,
1997            live,
1998            tombstones,
1999            positions,
2000        } = &mut self.backend
2001        else {
2002            return Err(InternalError::store_invariant());
2003        };
2004        let previous = if tombstones.contains(&key) {
2005            None
2006        } else {
2007            live.get(&key).cloned().or_else(|| canonical.get(&key))
2008        };
2009        if let Some(snapshot) = snapshot {
2010            tombstones.remove(&key);
2011            live.insert(key, snapshot);
2012        } else {
2013            live.remove(&key);
2014            tombstones.insert(key);
2015        }
2016        positions.publish_preflighted(key, position);
2017        Ok(previous)
2018    }
2019
2020    /// Return the current accepted store root selected from its two checksummed slots.
2021    pub(in crate::db) fn current_accepted_schema_root(
2022        &self,
2023    ) -> Result<Option<AcceptedSchemaRootSelection>, InternalError> {
2024        let first = self.accepted_root_slot_bytes(0)?;
2025        let second = self.accepted_root_slot_bytes(1)?;
2026        select_current_accepted_schema_root([first.as_deref(), second.as_deref()])
2027    }
2028
2029    /// Load and verify the immutable bundle referenced by the current root.
2030    pub(in crate::db) fn current_accepted_schema_bundle(
2031        &self,
2032    ) -> Result<Option<AcceptedSchemaRevisionBundle>, InternalError> {
2033        self.borrow_current_accepted_schema_bundle()
2034            .map(|bundle| bundle.map(|bundle| bundle.clone()))
2035    }
2036
2037    /// Borrow the current verified bundle, rechecking durable job closure even
2038    /// on a cache hit. The borrow must end before mutating schema authority.
2039    pub(in crate::db) fn borrow_current_accepted_schema_bundle(
2040        &self,
2041    ) -> Result<Option<Ref<'_, AcceptedSchemaRevisionBundle>>, InternalError> {
2042        let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2043            return Ok(None);
2044        };
2045        self.validate_constraint_validation_job_closure(&bundle)?;
2046
2047        Ok(Some(bundle))
2048    }
2049
2050    /// Borrow the verified bundle selected by a current catalog operation.
2051    /// Root publication invalidates this store-owned cache before replacement;
2052    /// exact authority equality also binds store scope, revision and fingerprint.
2053    /// Mutable identity and validation-job records still require live checks.
2054    pub(in crate::db) fn borrow_accepted_schema_bundle_for_authority(
2055        &self,
2056        expected: &AcceptedSchemaAuthority,
2057    ) -> Result<Option<Ref<'_, AcceptedSchemaRevisionBundle>>, InternalError> {
2058        let Some(selection) = self.current_accepted_selection_for_authority(expected)? else {
2059            return Ok(None);
2060        };
2061        let Some((_, bundle)) =
2062            self.accepted_schema_authority_ref_for_selection(Some(selection))?
2063        else {
2064            return Ok(None);
2065        };
2066        self.validate_constraint_validation_job_closure(&bundle)?;
2067
2068        Ok(Some(bundle))
2069    }
2070
2071    /// Project current accepted entity identity onto one registry-owned store path.
2072    pub(in crate::db) fn current_accepted_runtime_entities(
2073        &self,
2074        registered_store_path: &'static str,
2075    ) -> Result<Vec<AcceptedRuntimeEntity>, InternalError> {
2076        let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2077            return Ok(Vec::new());
2078        };
2079        if bundle.store_path() != registered_store_path {
2080            return Err(InternalError::store_corruption());
2081        }
2082
2083        bundle
2084            .entity_snapshots()
2085            .iter()
2086            .map(|(entity_tag, snapshot)| {
2087                AcceptedRuntimeEntity::from_accepted_snapshot(
2088                    &bundle,
2089                    *entity_tag,
2090                    snapshot,
2091                    registered_store_path,
2092                )
2093            })
2094            .collect()
2095    }
2096
2097    /// Resolve one accepted entity tag without materializing the full store catalog.
2098    pub(in crate::db) fn current_accepted_runtime_entity_for_tag(
2099        &self,
2100        registered_store_path: &'static str,
2101        entity_tag: EntityTag,
2102    ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2103        let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2104            return Ok(None);
2105        };
2106        if bundle.store_path() != registered_store_path {
2107            return Err(InternalError::store_corruption());
2108        }
2109        let Some(snapshot) = bundle.entity_snapshots().get(&entity_tag) else {
2110            return Ok(None);
2111        };
2112
2113        AcceptedRuntimeEntity::from_accepted_snapshot(
2114            &bundle,
2115            entity_tag,
2116            snapshot,
2117            registered_store_path,
2118        )
2119        .map(Some)
2120    }
2121
2122    /// Resolve one entity tag from the canonical accepted predecessor.
2123    pub(in crate::db) fn current_canonical_accepted_runtime_entity_for_tag(
2124        &self,
2125        registered_store_path: &'static str,
2126        entity_tag: EntityTag,
2127    ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2128        let Some(bundle) = self.current_canonical_accepted_schema_bundle()? else {
2129            return Ok(None);
2130        };
2131        if bundle.store_path() != registered_store_path {
2132            return Err(InternalError::store_corruption());
2133        }
2134        let Some(snapshot) = bundle.entity_snapshots().get(&entity_tag) else {
2135            return Ok(None);
2136        };
2137
2138        AcceptedRuntimeEntity::from_accepted_snapshot(
2139            &bundle,
2140            entity_tag,
2141            snapshot,
2142            registered_store_path,
2143        )
2144        .map(Some)
2145    }
2146
2147    /// Resolve one accepted entity source path without materializing the full store catalog.
2148    pub(in crate::db) fn current_accepted_runtime_entity_for_path(
2149        &self,
2150        registered_store_path: &'static str,
2151        entity_path: &str,
2152    ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2153        self.current_accepted_runtime_entity_matching(registered_store_path, |snapshot_path, _| {
2154            snapshot_path == entity_path
2155        })
2156    }
2157
2158    /// Resolve one entity path from the canonical accepted predecessor.
2159    pub(in crate::db) fn current_canonical_accepted_runtime_entity_for_path(
2160        &self,
2161        registered_store_path: &'static str,
2162        entity_path: &str,
2163    ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2164        let Some(bundle) = self.current_canonical_accepted_schema_bundle()? else {
2165            return Ok(None);
2166        };
2167        if bundle.store_path() != registered_store_path {
2168            return Err(InternalError::store_corruption());
2169        }
2170
2171        let mut matched = None;
2172        for (entity_tag, snapshot) in bundle.entity_snapshots() {
2173            if snapshot.entity_path() != entity_path {
2174                continue;
2175            }
2176            let entity = AcceptedRuntimeEntity::from_accepted_snapshot(
2177                &bundle,
2178                *entity_tag,
2179                snapshot,
2180                registered_store_path,
2181            )?;
2182            if matched.replace(entity).is_some() {
2183                return Err(InternalError::store_corruption());
2184            }
2185        }
2186
2187        Ok(matched)
2188    }
2189
2190    /// Resolve one accepted entity display name without materializing the full store catalog.
2191    #[cfg(test)]
2192    pub(in crate::db) fn current_accepted_runtime_entity_for_name(
2193        &self,
2194        registered_store_path: &'static str,
2195        entity_name: &str,
2196    ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2197        self.current_accepted_runtime_entity_matching(registered_store_path, |_, snapshot_name| {
2198            snapshot_name == entity_name
2199        })
2200    }
2201
2202    fn current_accepted_runtime_entity_matching(
2203        &self,
2204        registered_store_path: &'static str,
2205        mut predicate: impl FnMut(&str, &str) -> bool,
2206    ) -> Result<Option<AcceptedRuntimeEntity>, InternalError> {
2207        let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2208            return Ok(None);
2209        };
2210        if bundle.store_path() != registered_store_path {
2211            return Err(InternalError::store_corruption());
2212        }
2213
2214        let mut matched = None;
2215        for (entity_tag, snapshot) in bundle.entity_snapshots() {
2216            if !predicate(snapshot.entity_path(), snapshot.entity_name()) {
2217                continue;
2218            }
2219            let entity = AcceptedRuntimeEntity::from_accepted_snapshot(
2220                &bundle,
2221                *entity_tag,
2222                snapshot,
2223                registered_store_path,
2224            )?;
2225            if matched.replace(entity).is_some() {
2226                return Err(InternalError::store_corruption());
2227            }
2228        }
2229
2230        Ok(matched)
2231    }
2232
2233    /// Return the current accepted revision without decoding its bundle.
2234    pub(in crate::db) fn current_accepted_schema_revision(
2235        &self,
2236    ) -> Result<Option<AcceptedSchemaRevision>, InternalError> {
2237        Ok(self
2238            .current_accepted_schema_root()?
2239            .map(|selection| selection.root().revision()))
2240    }
2241
2242    /// Return the pending relation activation that blocks deletes from one target.
2243    ///
2244    /// This reads the immutable accepted-bundle cache directly so ordinary
2245    /// deletes do not decode and clone every store catalog merely to prove that
2246    /// no candidate reverse generation targets the deleted entity.
2247    pub(in crate::db) fn pending_relation_activation_for_target(
2248        &self,
2249        target_path: &str,
2250    ) -> Result<Option<PendingRelationActivationDeleteBarrier>, InternalError> {
2251        let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2252            return Ok(None);
2253        };
2254        for (entity_tag, snapshot) in bundle.entity_snapshots() {
2255            let Some(candidate) = snapshot
2256                .candidate_relations()
2257                .iter()
2258                .find(|candidate| candidate.target_path() == target_path)
2259            else {
2260                continue;
2261            };
2262            let activation = snapshot
2263                .constraint_activations()
2264                .iter()
2265                .find(|activation| {
2266                    matches!(
2267                        activation.kind(),
2268                        ConstraintActivationKind::Relation { relation_id }
2269                            if *relation_id == candidate.id()
2270                    )
2271                })
2272                .ok_or_else(InternalError::store_corruption)?;
2273            return Ok(Some(PendingRelationActivationDeleteBarrier {
2274                accepted_schema_fingerprint:
2275                    accepted_schema_cache_fingerprint_for_persisted_snapshot(snapshot)?,
2276                source_entity_tag: *entity_tag,
2277                constraint_id: activation.id(),
2278            }));
2279        }
2280
2281        Ok(None)
2282    }
2283
2284    /// Return whether one accepted source entity owns a live relation to a target.
2285    pub(in crate::db) fn entity_has_relation_to_target(
2286        &self,
2287        source_entity: EntityTag,
2288        target_path: &str,
2289    ) -> Result<bool, InternalError> {
2290        let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
2291            return Ok(false);
2292        };
2293        let Some(snapshot) = bundle.entity_snapshots().get(&source_entity) else {
2294            return Ok(false);
2295        };
2296
2297        Ok(snapshot
2298            .relations()
2299            .iter()
2300            .any(|relation| relation.target_path() == target_path))
2301    }
2302
2303    /// Reject any same-entity schema change beside one exact activation lifecycle step.
2304    pub(in crate::db) fn validate_live_activation_transition(
2305        &self,
2306        candidate: &AcceptedSchemaRevisionBundle,
2307    ) -> Result<(), InternalError> {
2308        let Some(current) = self.current_accepted_schema_bundle()? else {
2309            return Ok(());
2310        };
2311        Self::validate_activation_transition_from(&current, candidate)
2312    }
2313
2314    /// Validate one transition against the canonical accepted predecessor.
2315    pub(in crate::db) fn validate_canonical_activation_transition(
2316        &self,
2317        candidate: &AcceptedSchemaRevisionBundle,
2318    ) -> Result<(), InternalError> {
2319        let Some(current) = self.current_canonical_accepted_schema_bundle()? else {
2320            return Ok(());
2321        };
2322        Self::validate_activation_transition_from(&current, candidate)
2323    }
2324
2325    fn validate_activation_transition_from(
2326        current: &AcceptedSchemaRevisionBundle,
2327        candidate: &AcceptedSchemaRevisionBundle,
2328    ) -> Result<(), InternalError> {
2329        for (entity_tag, before) in current.entity_snapshots() {
2330            if before.constraint_activations().is_empty() {
2331                continue;
2332            }
2333            let after = candidate
2334                .entity_snapshots()
2335                .get(entity_tag)
2336                .ok_or_else(InternalError::store_invariant)?;
2337            if before == after {
2338                continue;
2339            }
2340            let expected_shape = before
2341                .clone()
2342                .with_constraint_catalog(after.constraint_catalog().clone());
2343            let catalog_only_transition = expected_shape == *after
2344                && before
2345                    .constraint_catalog()
2346                    .permits_live_activation_transition_to(after.constraint_catalog());
2347            let sql_row_local_abort_with_version =
2348                before.constraint_activations().iter().any(|activation| {
2349                    activation.origin() == ConstraintOrigin::SqlDdl
2350                        && matches!(
2351                            activation.kind(),
2352                            ConstraintActivationKind::Check { .. }
2353                                | ConstraintActivationKind::NotNull { .. }
2354                        )
2355                        && before.version().get().checked_add(1) == Some(after.version().get())
2356                        && before
2357                            .constraint_catalog()
2358                            .clone()
2359                            .with_aborted_activation(activation.id())
2360                            .is_ok_and(|catalog| catalog == *after.constraint_catalog())
2361                        && before
2362                            .clone()
2363                            .with_constraint_catalog(after.constraint_catalog().clone())
2364                            .with_schema_version(after.version())
2365                            == *after
2366                });
2367            let sql_unique_abort_with_version =
2368                before.constraint_activations().iter().any(|activation| {
2369                    activation.origin() == ConstraintOrigin::SqlDdl
2370                        && matches!(activation.kind(), ConstraintActivationKind::Unique { .. })
2371                        && before.version().get().checked_add(1) == Some(after.version().get())
2372                        && before
2373                            .with_aborted_unique_activation(activation.id(), after.version())
2374                            .is_ok_and(|expected| expected == *after)
2375                });
2376            let not_null_promotion = before.constraint_activations().iter().any(|activation| {
2377                matches!(activation.kind(), ConstraintActivationKind::NotNull { .. })
2378                    && before
2379                        .with_promoted_not_null_activation(activation.id(), after.version())
2380                        .is_ok_and(|expected| expected == *after)
2381            });
2382            let unique_promotion = before.constraint_activations().iter().any(|activation| {
2383                matches!(activation.kind(), ConstraintActivationKind::Unique { .. })
2384                    && before
2385                        .with_promoted_unique_activation(activation.id(), after.version())
2386                        .is_ok_and(|expected| expected == *after)
2387            });
2388            let relation_promotion = before.constraint_activations().iter().any(|activation| {
2389                matches!(activation.kind(), ConstraintActivationKind::Relation { .. })
2390                    && before
2391                        .with_promoted_relation_activation(activation.id(), after.version())
2392                        .is_ok_and(|expected| expected == *after)
2393            });
2394            if !catalog_only_transition
2395                && !sql_row_local_abort_with_version
2396                && !sql_unique_abort_with_version
2397                && !not_null_promotion
2398                && !unique_promotion
2399                && !relation_promotion
2400            {
2401                return Err(InternalError::store_invariant());
2402            }
2403        }
2404        Ok(())
2405    }
2406
2407    /// Prove exact pairing between live activations and durable validation jobs.
2408    pub(in crate::db) fn validate_constraint_validation_job_closure(
2409        &self,
2410        bundle: &AcceptedSchemaRevisionBundle,
2411    ) -> Result<(), InternalError> {
2412        self.validate_constraint_validation_job_closure_with_change(bundle, None, None)
2413    }
2414
2415    /// Prove the activation/job closure that would exist after one bounded
2416    /// marker-owned job replacement or removal.
2417    pub(in crate::db) fn validate_constraint_validation_job_closure_with_change(
2418        &self,
2419        bundle: &AcceptedSchemaRevisionBundle,
2420        replacement: Option<&ConstraintValidationJob>,
2421        removal: Option<(EntityTag, ConstraintId)>,
2422    ) -> Result<(), InternalError> {
2423        self.validate_constraint_validation_job_closure_with_change_in_view(
2424            bundle,
2425            replacement,
2426            removal,
2427            IdentityStateStorageView::Effective,
2428        )
2429    }
2430
2431    /// Prove activation/job closure against the canonical predecessor view.
2432    pub(in crate::db) fn validate_canonical_constraint_validation_job_closure_with_change(
2433        &self,
2434        bundle: &AcceptedSchemaRevisionBundle,
2435        replacement: Option<&ConstraintValidationJob>,
2436        removal: Option<(EntityTag, ConstraintId)>,
2437    ) -> Result<(), InternalError> {
2438        self.validate_constraint_validation_job_closure_with_change_in_view(
2439            bundle,
2440            replacement,
2441            removal,
2442            IdentityStateStorageView::Canonical,
2443        )
2444    }
2445
2446    fn validate_constraint_validation_job_closure_with_change_in_view(
2447        &self,
2448        bundle: &AcceptedSchemaRevisionBundle,
2449        replacement: Option<&ConstraintValidationJob>,
2450        removal: Option<(EntityTag, ConstraintId)>,
2451        view: IdentityStateStorageView,
2452    ) -> Result<(), InternalError> {
2453        if replacement.is_some() && removal.is_some() {
2454            return Err(InternalError::store_invariant());
2455        }
2456        let replacement_key = replacement.map(|job| {
2457            RawSchemaKey::from_constraint_validation_job(job.entity_tag(), job.constraint_id())
2458        });
2459        let removal_key = removal.map(|(entity_tag, constraint_id)| {
2460            RawSchemaKey::from_constraint_validation_job(entity_tag, constraint_id)
2461        });
2462        let mut expected = BTreeSet::new();
2463        for (entity_tag, snapshot) in bundle.entity_snapshots() {
2464            for activation in snapshot.constraint_activations() {
2465                let key =
2466                    RawSchemaKey::from_constraint_validation_job(*entity_tag, activation.id());
2467                match activation.state() {
2468                    ConstraintActivationState::EnforcingNewWrites => {
2469                        if self
2470                            .constraint_validation_job_after_change(
2471                                key,
2472                                replacement,
2473                                replacement_key,
2474                                removal_key,
2475                                view,
2476                            )?
2477                            .is_some()
2478                        {
2479                            return Err(InternalError::store_corruption());
2480                        }
2481                    }
2482                    ConstraintActivationState::Validating => {
2483                        let job = self
2484                            .constraint_validation_job_after_change(
2485                                key,
2486                                replacement,
2487                                replacement_key,
2488                                removal_key,
2489                                view,
2490                            )?
2491                            .ok_or_else(InternalError::store_corruption)?;
2492                        if job.entity_tag() != *entity_tag
2493                            || job.entity_path() != snapshot.entity_path()
2494                        {
2495                            return Err(InternalError::store_corruption());
2496                        }
2497                        job.validate(Some(activation))?;
2498                        expected.insert(key);
2499                    }
2500                }
2501            }
2502        }
2503
2504        self.visit_constraint_validation_jobs_in_view(view, |key, raw| {
2505            if removal_key == Some(*key) || replacement_key == Some(*key) {
2506                return Ok(SchemaStoreVisit::Continue);
2507            }
2508            if !expected.contains(key) {
2509                return Err(InternalError::store_corruption());
2510            }
2511            let job = decode_constraint_validation_job(raw.as_bytes())?;
2512            if job.entity_tag() != key.entity_tag()
2513                || key.constraint_id() != Some(job.constraint_id())
2514            {
2515                return Err(InternalError::store_corruption());
2516            }
2517            Ok(SchemaStoreVisit::Continue)
2518        })?;
2519
2520        if let Some(key) = replacement_key
2521            && !expected.contains(&key)
2522        {
2523            return Err(InternalError::store_corruption());
2524        }
2525        if let Some(key) = removal_key
2526            && expected.contains(&key)
2527        {
2528            return Err(InternalError::store_corruption());
2529        }
2530
2531        Ok(())
2532    }
2533
2534    fn constraint_validation_job_after_change(
2535        &self,
2536        key: RawSchemaKey,
2537        replacement: Option<&ConstraintValidationJob>,
2538        replacement_key: Option<RawSchemaKey>,
2539        removal_key: Option<RawSchemaKey>,
2540        view: IdentityStateStorageView,
2541    ) -> Result<Option<ConstraintValidationJob>, InternalError> {
2542        if removal_key == Some(key) {
2543            return Ok(None);
2544        }
2545        if replacement_key == Some(key) {
2546            return Ok(replacement.cloned());
2547        }
2548        let raw = match view {
2549            IdentityStateStorageView::Effective => self.get_raw_value(&key),
2550            IdentityStateStorageView::Canonical => self.get_canonical_raw_value(&key)?,
2551        };
2552        raw.map(|raw| decode_constraint_validation_job(raw.as_bytes()))
2553            .transpose()
2554    }
2555
2556    /// Return whether one retained schema authority still names this store's
2557    /// current immutable accepted root.
2558    pub(in crate::db) fn current_accepted_schema_authority_matches(
2559        &self,
2560        expected: &AcceptedSchemaAuthority,
2561    ) -> Result<bool, InternalError> {
2562        self.current_accepted_selection_for_authority(expected)
2563            .map(|selection| selection.is_some())
2564    }
2565
2566    // One store-scoped authority check for execution and borrowed metadata.
2567    // A dropped cache is not lost authority: reload its durable selection.
2568    fn current_accepted_selection_for_authority(
2569        &self,
2570        expected: &AcceptedSchemaAuthority,
2571    ) -> Result<Option<AcceptedSchemaRootSelection>, InternalError> {
2572        let Some(store_scope) = self.accepted_catalog_scope.get() else {
2573            return Ok(None);
2574        };
2575
2576        // Root-writing primitives invalidate this cache before publication,
2577        // so a retained selection is the current in-memory authority.
2578        let cached = self
2579            .accepted_bundle_cache
2580            .try_borrow()
2581            .map_err(|_| InternalError::store_invariant())?
2582            .as_ref()
2583            .map(|cached| cached.selection);
2584        let selection = match cached {
2585            Some(selection) => Some(selection),
2586            None => self.current_accepted_schema_root()?,
2587        };
2588        Ok(selection.filter(|selection| {
2589            let root = selection.root();
2590            expected.matches_store_root(store_scope, root.revision(), root.fingerprint())
2591        }))
2592    }
2593
2594    /// Publish a candidate directly into its canonical schema allocation.
2595    ///
2596    /// Journaled online revisions must use
2597    /// `apply_journaled_accepted_schema_candidate`; this path owns initial
2598    /// bootstrap and marker-owned live-projection updates.
2599    pub(in crate::db) fn publish_accepted_schema_candidate(
2600        &mut self,
2601        incarnation: DatabaseIncarnationId,
2602        expected_revision: AcceptedSchemaRevision,
2603        candidate: &CandidateSchemaRevision,
2604    ) -> Result<(), InternalError> {
2605        let identity_transition = self.prepare_identity_state_transition(
2606            incarnation,
2607            candidate,
2608            IdentityStateStorageView::Effective,
2609        )?;
2610        if self.current_root_matches_candidate(candidate)? {
2611            if !identity_transition.is_empty() {
2612                return Err(InternalError::identity_state_corruption());
2613            }
2614            let selection = self
2615                .current_accepted_schema_root()?
2616                .ok_or_else(InternalError::store_corruption)?;
2617            self.retain_durable_candidate_entries(candidate, selection.slot())?;
2618            return Ok(());
2619        }
2620        let first = self.accepted_root_slot_bytes(0)?;
2621        let second = self.accepted_root_slot_bytes(1)?;
2622        prepare_accepted_schema_root_publication(
2623            [first.as_deref(), second.as_deref()],
2624            expected_revision,
2625            candidate,
2626        )
2627        .map_err(map_schema_publication_error)?;
2628
2629        let bundle_key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
2630        self.insert_durable_raw_value(bundle_key, candidate.encoded_bundle().to_vec());
2631        let persisted_bundle = self
2632            .get_raw_value(&bundle_key)
2633            .ok_or_else(InternalError::store_corruption)?;
2634        let _verified = decode_verified_accepted_schema_revision_bundle(
2635            candidate.root(),
2636            persisted_bundle.as_bytes(),
2637        )?;
2638        self.apply_identity_state_transition(
2639            identity_transition,
2640            IdentityStateWriteTarget::Durable,
2641        )?;
2642
2643        // Re-read the root immediately before the inactive-slot write. This is
2644        // the compare-and-swap check after candidate persistence.
2645        let first = self.accepted_root_slot_bytes(0)?;
2646        let second = self.accepted_root_slot_bytes(1)?;
2647        let publication = prepare_accepted_schema_root_publication(
2648            [first.as_deref(), second.as_deref()],
2649            expected_revision,
2650            candidate,
2651        )
2652        .map_err(map_schema_publication_error)?;
2653        let root_key = RawSchemaKey::from_accepted_root_slot(publication.target_slot())?;
2654        self.insert_durable_raw_value(root_key, publication.encoded_root().to_vec());
2655
2656        let selected = self
2657            .current_accepted_schema_root()?
2658            .ok_or_else(InternalError::store_corruption)?;
2659        if selected.root() != candidate.root() {
2660            return Err(InternalError::store_corruption());
2661        }
2662        self.retain_durable_candidate_entries(candidate, selected.slot())?;
2663        Ok(())
2664    }
2665
2666    /// Restore one current accepted candidate into an empty live-only schema
2667    /// store from its durable database-control checkpoint.
2668    pub(in crate::db) fn restore_live_accepted_schema_checkpoint(
2669        &mut self,
2670        incarnation: DatabaseIncarnationId,
2671        candidate: &CandidateSchemaRevision,
2672        checkpoint_identity_states: &IdentityStateInventory,
2673    ) -> Result<(), InternalError> {
2674        if !matches!(self.backend, SchemaStoreBackend::Heap(_)) {
2675            return Err(InternalError::store_invariant());
2676        }
2677        let checkpoint_validation = prepare_identity_state_transition(
2678            incarnation,
2679            Some(candidate.bundle()),
2680            candidate.bundle(),
2681            checkpoint_identity_states.clone(),
2682        )?;
2683        if !checkpoint_validation.is_empty() {
2684            return Err(InternalError::identity_state_corruption());
2685        }
2686        if self.current_root_matches_candidate(candidate)? {
2687            for state in checkpoint_identity_states.values() {
2688                let key = RawSchemaKey::from_identity_state(
2689                    state.owner().entity_tag(),
2690                    state.owner().field_id(),
2691                );
2692                self.insert_durable_raw_value(key, encode_identity_state(state)?);
2693            }
2694            if self.identity_state_inventory(IdentityStateStorageView::Effective)?
2695                != *checkpoint_identity_states
2696            {
2697                return Err(InternalError::identity_state_corruption());
2698            }
2699            let selection = self
2700                .current_accepted_schema_root()?
2701                .ok_or_else(InternalError::store_corruption)?;
2702            self.retain_durable_candidate_entries(candidate, selection.slot())?;
2703            return Ok(());
2704        }
2705        if self.current_accepted_schema_root()?.is_some()
2706            || !self
2707                .identity_state_inventory(IdentityStateStorageView::Effective)?
2708                .is_empty()
2709        {
2710            return Err(InternalError::store_corruption());
2711        }
2712
2713        let bundle_key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
2714        self.insert_durable_raw_value(bundle_key, candidate.encoded_bundle().to_vec());
2715        for state in checkpoint_identity_states.values() {
2716            let key = RawSchemaKey::from_identity_state(
2717                state.owner().entity_tag(),
2718                state.owner().field_id(),
2719            );
2720            self.insert_durable_raw_value(key, encode_identity_state(state)?);
2721        }
2722        let root_key = RawSchemaKey::from_accepted_root_slot(0)?;
2723        self.insert_durable_raw_value(root_key, candidate.encoded_root().to_vec());
2724
2725        let selected = self
2726            .current_accepted_schema_root()?
2727            .ok_or_else(InternalError::store_corruption)?;
2728        if selected.root() != candidate.root() {
2729            return Err(InternalError::store_corruption());
2730        }
2731        self.retain_durable_candidate_entries(candidate, selected.slot())?;
2732        Ok(())
2733    }
2734
2735    /// Preflight one accepted candidate without changing durable or live
2736    /// schema state.
2737    ///
2738    /// Returns `true` only when this exact candidate is already authoritative.
2739    /// Multi-store publication uses that distinction to reject partial replay
2740    /// before opening one marker-owned commit window.
2741    pub(in crate::db) fn preflight_accepted_schema_candidate(
2742        &self,
2743        incarnation: DatabaseIncarnationId,
2744        expected_revision: AcceptedSchemaRevision,
2745        candidate: &CandidateSchemaRevision,
2746    ) -> Result<bool, InternalError> {
2747        let identity_transition = self.prepare_identity_state_transition(
2748            incarnation,
2749            candidate,
2750            IdentityStateStorageView::Effective,
2751        )?;
2752        if self.current_root_matches_candidate(candidate)? {
2753            if !identity_transition.is_empty() {
2754                return Err(InternalError::identity_state_corruption());
2755            }
2756            return Ok(true);
2757        }
2758        let first = self.accepted_root_slot_bytes(0)?;
2759        let second = self.accepted_root_slot_bytes(1)?;
2760        prepare_accepted_schema_root_publication(
2761            [first.as_deref(), second.as_deref()],
2762            expected_revision,
2763            candidate,
2764        )
2765        .map_err(map_schema_publication_error)?;
2766
2767        Ok(false)
2768    }
2769
2770    /// Prepare one accepted candidate against canonical journaled authority.
2771    pub(in crate::db) fn prepare_fold_journaled_accepted_schema_candidate(
2772        &self,
2773        incarnation: DatabaseIncarnationId,
2774        expected_revision: AcceptedSchemaRevision,
2775        candidate: CandidateSchemaRevision,
2776    ) -> Result<PreparedAcceptedSchemaFold, InternalError> {
2777        if !matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
2778            return Err(InternalError::store_invariant());
2779        }
2780        let identity_transition = self.prepare_identity_state_transition(
2781            incarnation,
2782            &candidate,
2783            IdentityStateStorageView::Canonical,
2784        )?;
2785        let candidate_is_current = self.canonical_root_matches_candidate(&candidate)?;
2786        if candidate_is_current && !identity_transition.is_empty() {
2787            return Err(InternalError::identity_state_corruption());
2788        }
2789        let (updates, removals) = identity_transition.into_effects();
2790        let identity_removals = removals
2791            .into_iter()
2792            .map(|owner| RawSchemaKey::from_identity_state(owner.entity_tag(), owner.field_id()))
2793            .collect();
2794        let identity_updates = updates
2795            .into_iter()
2796            .map(|state| {
2797                Ok((
2798                    RawSchemaKey::from_identity_state(
2799                        state.owner().entity_tag(),
2800                        state.owner().field_id(),
2801                    ),
2802                    encode_identity_state(&state)?,
2803                ))
2804            })
2805            .collect::<Result<Vec<_>, InternalError>>()?;
2806        let first = self.canonical_root_slot_bytes(0)?;
2807        let second = self.canonical_root_slot_bytes(1)?;
2808        let root_slot = if candidate_is_current {
2809            select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
2810                .ok_or_else(InternalError::store_corruption)?
2811                .slot()
2812        } else {
2813            prepare_accepted_schema_root_publication(
2814                [first.as_deref(), second.as_deref()],
2815                expected_revision,
2816                &candidate,
2817            )
2818            .map_err(map_schema_publication_error)?
2819            .target_slot()
2820        };
2821        let retained = Self::candidate_entry_keys(&candidate, root_slot)?;
2822        Ok(PreparedAcceptedSchemaFold {
2823            candidate,
2824            expected_revision,
2825            identity_updates,
2826            identity_removals,
2827            retained,
2828            root_slot,
2829        })
2830    }
2831
2832    /// Return the retained Identity owner count after admitting one candidate.
2833    pub(in crate::db) fn projected_identity_state_count(
2834        &self,
2835        incarnation: DatabaseIncarnationId,
2836        candidate: &CandidateSchemaRevision,
2837    ) -> Result<usize, InternalError> {
2838        Ok(self
2839            .prepare_identity_state_transition(
2840                incarnation,
2841                candidate,
2842                IdentityStateStorageView::Effective,
2843            )?
2844            .projected_inventory_len())
2845    }
2846
2847    /// Apply one marker-bound schema candidate to the journaled live projection.
2848    pub(in crate::db) fn apply_journaled_accepted_schema_candidate(
2849        &mut self,
2850        incarnation: DatabaseIncarnationId,
2851        expected_revision: AcceptedSchemaRevision,
2852        candidate: &CandidateSchemaRevision,
2853    ) -> Result<(), InternalError> {
2854        if !matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
2855            return Err(InternalError::store_invariant());
2856        }
2857        let identity_transition = self.prepare_identity_state_transition(
2858            incarnation,
2859            candidate,
2860            IdentityStateStorageView::Effective,
2861        )?;
2862        if self.current_root_matches_candidate(candidate)? {
2863            if !identity_transition.is_empty() {
2864                return Err(InternalError::identity_state_corruption());
2865            }
2866            let selection = self
2867                .current_accepted_schema_root()?
2868                .ok_or_else(InternalError::store_corruption)?;
2869            self.retain_materialized_candidate_entries(candidate, selection.slot())?;
2870            return Ok(());
2871        }
2872
2873        let first = self.accepted_root_slot_bytes(0)?;
2874        let second = self.accepted_root_slot_bytes(1)?;
2875        prepare_accepted_schema_root_publication(
2876            [first.as_deref(), second.as_deref()],
2877            expected_revision,
2878            candidate,
2879        )
2880        .map_err(map_schema_publication_error)?;
2881
2882        let bundle_key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
2883        self.insert_raw_value(
2884            bundle_key,
2885            RawSchemaValue::from_encoded_control_record(candidate.encoded_bundle().to_vec()),
2886        );
2887        let persisted_bundle = self
2888            .get_raw_value(&bundle_key)
2889            .ok_or_else(InternalError::store_corruption)?;
2890        let _verified = decode_verified_accepted_schema_revision_bundle(
2891            candidate.root(),
2892            persisted_bundle.as_bytes(),
2893        )?;
2894        self.apply_identity_state_transition(
2895            identity_transition,
2896            IdentityStateWriteTarget::Materialized,
2897        )?;
2898
2899        let first = self.accepted_root_slot_bytes(0)?;
2900        let second = self.accepted_root_slot_bytes(1)?;
2901        let publication = prepare_accepted_schema_root_publication(
2902            [first.as_deref(), second.as_deref()],
2903            expected_revision,
2904            candidate,
2905        )
2906        .map_err(map_schema_publication_error)?;
2907        let root_key = RawSchemaKey::from_accepted_root_slot(publication.target_slot())?;
2908        self.insert_raw_value(
2909            root_key,
2910            RawSchemaValue::from_encoded_control_record(publication.encoded_root().to_vec()),
2911        );
2912
2913        if !self.current_root_matches_candidate(candidate)? {
2914            return Err(InternalError::store_corruption());
2915        }
2916        let selection = self
2917            .current_accepted_schema_root()?
2918            .ok_or_else(InternalError::store_corruption)?;
2919        self.retain_materialized_candidate_entries(candidate, selection.slot())?;
2920        Ok(())
2921    }
2922
2923    /// Consume one candidate's preflight payloads in the same atomic fold callback.
2924    pub(in crate::db) fn apply_prepared_accepted_schema_fold(
2925        &mut self,
2926        prepared: PreparedAcceptedSchemaFold,
2927    ) -> Result<(), InternalError> {
2928        let PreparedAcceptedSchemaFold {
2929            candidate,
2930            expected_revision,
2931            identity_updates,
2932            identity_removals,
2933            retained,
2934            root_slot,
2935        } = prepared;
2936        if self.canonical_root_matches_candidate(&candidate)? {
2937            if !identity_updates.is_empty() || !identity_removals.is_empty() {
2938                return Err(InternalError::identity_state_corruption());
2939            }
2940            let first = self.canonical_root_slot_bytes(0)?;
2941            let second = self.canonical_root_slot_bytes(1)?;
2942            let selection =
2943                select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
2944                    .ok_or_else(InternalError::store_corruption)?;
2945            if selection.slot() != root_slot {
2946                return Err(InternalError::store_invariant());
2947            }
2948            self.retain_canonical_candidate_entries(&retained)?;
2949            return Ok(());
2950        }
2951
2952        let first = self.canonical_root_slot_bytes(0)?;
2953        let second = self.canonical_root_slot_bytes(1)?;
2954        let publication = prepare_accepted_schema_root_publication(
2955            [first.as_deref(), second.as_deref()],
2956            expected_revision,
2957            &candidate,
2958        )
2959        .map_err(map_schema_publication_error)?;
2960        if publication.target_slot() != root_slot {
2961            return Err(InternalError::store_invariant());
2962        }
2963
2964        let bundle_key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
2965        self.insert_canonical_raw_value(bundle_key, candidate.encoded_bundle().to_vec())?;
2966        let persisted_bundle = self
2967            .get_canonical_raw_value(&bundle_key)?
2968            .ok_or_else(InternalError::store_corruption)?;
2969        let _verified = decode_verified_accepted_schema_revision_bundle(
2970            candidate.root(),
2971            persisted_bundle.as_bytes(),
2972        )?;
2973        for key in identity_removals {
2974            self.remove_identity_state_key(key, IdentityStateWriteTarget::Canonical)?;
2975        }
2976        for (key, bytes) in identity_updates {
2977            self.insert_canonical_raw_value(key, bytes)?;
2978        }
2979
2980        let first = self.canonical_root_slot_bytes(0)?;
2981        let second = self.canonical_root_slot_bytes(1)?;
2982        let publication = prepare_accepted_schema_root_publication(
2983            [first.as_deref(), second.as_deref()],
2984            expected_revision,
2985            &candidate,
2986        )
2987        .map_err(map_schema_publication_error)?;
2988        let root_key = RawSchemaKey::from_accepted_root_slot(publication.target_slot())?;
2989        self.insert_canonical_raw_value(root_key, publication.encoded_root().to_vec())?;
2990
2991        if !self.canonical_root_matches_candidate(&candidate)? {
2992            return Err(InternalError::store_corruption());
2993        }
2994        let first = self.canonical_root_slot_bytes(0)?;
2995        let second = self.canonical_root_slot_bytes(1)?;
2996        let selection = select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
2997            .ok_or_else(InternalError::store_corruption)?;
2998        if selection.slot() != root_slot {
2999            return Err(InternalError::store_invariant());
3000        }
3001        self.retain_canonical_candidate_entries(&retained)?;
3002        Ok(())
3003    }
3004
3005    /// Load one entity snapshot from the immutable bundle selected by the
3006    /// current accepted root.
3007    pub(in crate::db) fn current_accepted_persisted_snapshot(
3008        &self,
3009        entity: EntityTag,
3010    ) -> Result<Option<PersistedSchemaSnapshot>, InternalError> {
3011        let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
3012            return Ok(None);
3013        };
3014
3015        Ok(bundle.entity_snapshots().get(&entity).cloned())
3016    }
3017
3018    /// Return one accepted catalog selection from the current immutable root.
3019    pub(in crate::db) fn current_accepted_catalog_selection(
3020        &self,
3021        entity: EntityTag,
3022        entity_path: &str,
3023        store_path: &'static str,
3024    ) -> Result<Option<AcceptedCatalogSnapshotSelection>, InternalError> {
3025        let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
3026            return Ok(None);
3027        };
3028        if bundle.store_path() != store_path {
3029            return Err(InternalError::store_corruption());
3030        }
3031        let Some(snapshot) = bundle.entity_snapshots().get(&entity) else {
3032            return Ok(None);
3033        };
3034        if snapshot.entity_path() != entity_path {
3035            return Err(InternalError::store_corruption());
3036        }
3037
3038        let cache = self
3039            .accepted_bundle_cache
3040            .try_borrow()
3041            .map_err(|_| InternalError::store_invariant())?;
3042        let cached = cache.as_ref().ok_or_else(InternalError::store_invariant)?;
3043        if let Some(selection) = cached
3044            .entity_selections
3045            .try_borrow()
3046            .map_err(|_| InternalError::store_invariant())?
3047            .get(&entity)
3048            .cloned()
3049        {
3050            return Ok(Some(selection));
3051        }
3052
3053        let selected = AcceptedCatalogSnapshotSelection::from_verified_snapshot(
3054            entity,
3055            store_path,
3056            snapshot,
3057            cached.value_catalog.clone(),
3058        )?;
3059        cached
3060            .entity_selections
3061            .try_borrow_mut()
3062            .map_err(|_| InternalError::store_invariant())?
3063            .insert(entity, selected.clone());
3064
3065        Ok(Some(selected))
3066    }
3067
3068    /// Return one accepted catalog selection from the canonical journal base.
3069    /// Recovery uses this while folding historical row batches whose schema
3070    /// revision can precede the current live accepted root.
3071    pub(in crate::db) fn current_canonical_accepted_catalog_selection(
3072        &self,
3073        entity: EntityTag,
3074        entity_path: &str,
3075        store_path: &'static str,
3076    ) -> Result<Option<AcceptedCatalogSnapshotSelection>, InternalError> {
3077        let first = self.canonical_root_slot_bytes(0)?;
3078        let second = self.canonical_root_slot_bytes(1)?;
3079        let Some(selection) =
3080            select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
3081        else {
3082            return Ok(None);
3083        };
3084        let bundle_key = RawSchemaKey::from_accepted_bundle(selection.root().bundle_key());
3085        let raw_bundle = self
3086            .get_canonical_raw_value(&bundle_key)?
3087            .ok_or_else(InternalError::store_corruption)?;
3088        let bundle = decode_verified_accepted_schema_revision_bundle(
3089            selection.root(),
3090            raw_bundle.as_bytes(),
3091        )?;
3092        if bundle.store_path() != store_path {
3093            return Err(InternalError::store_corruption());
3094        }
3095        let Some(snapshot) = bundle.entity_snapshots().get(&entity) else {
3096            return Ok(None);
3097        };
3098        if snapshot.entity_path() != entity_path {
3099            return Err(InternalError::store_corruption());
3100        }
3101
3102        AcceptedCatalogSnapshotSelection::from_verified_snapshot(
3103            entity,
3104            store_path,
3105            snapshot,
3106            AcceptedValueCatalogHandle::new(
3107                bundle.enum_catalog().clone(),
3108                bundle.composite_catalog().clone(),
3109                self.accepted_catalog_scope
3110                    .get_or_init(AcceptedStoreCatalogScope::new)
3111                    .clone(),
3112                bundle.revision(),
3113                selection.root().fingerprint(),
3114            ),
3115        )
3116        .map(Some)
3117    }
3118
3119    /// Derive accepted catalog metadata from the selected accepted bundle.
3120    ///
3121    /// This function intentionally reads only the persisted schema store. It
3122    /// does not reconstruct metadata from generated models when the store has
3123    /// no accepted bundle.
3124    #[cfg(test)]
3125    pub(in crate::db) fn catalog_metadata(
3126        &self,
3127    ) -> Result<Option<SchemaStoreCatalogMetadata>, InternalError> {
3128        Ok(self
3129            .allocation_metadata()?
3130            .map(SchemaStoreAllocationMetadata::schema))
3131    }
3132
3133    /// Derive role-specific allocation metadata from the selected accepted bundle.
3134    ///
3135    /// This function intentionally reads only accepted schema-store payloads.
3136    /// It never reconstructs metadata from generated models when the store has
3137    /// no accepted bundle.
3138    pub(in crate::db) fn allocation_metadata(
3139        &self,
3140    ) -> Result<Option<SchemaStoreAllocationMetadata>, InternalError> {
3141        let Some(bundle) = self.current_accepted_schema_bundle_ref()? else {
3142            return Ok(None);
3143        };
3144        let snapshots = bundle.entity_snapshots();
3145        if snapshots.is_empty() {
3146            return Ok(None);
3147        }
3148
3149        Ok(Some(SchemaStoreAllocationMetadata::new(
3150            derive_data_allocation_metadata(snapshots)?,
3151            derive_index_allocation_metadata(snapshots)?,
3152            derive_schema_catalog_metadata(snapshots)?,
3153        )))
3154    }
3155
3156    /// Insert or replace one opaque schema/control value.
3157    fn insert_raw_value(
3158        &mut self,
3159        key: RawSchemaKey,
3160        snapshot: RawSchemaValue,
3161    ) -> Option<RawSchemaValue> {
3162        self.invalidate_accepted_bundle_cache_for_key(key);
3163        let previous_journaled = if matches!(self.backend, SchemaStoreBackend::Journaled { .. }) {
3164            self.get_raw_value_for_backend(&key)
3165        } else {
3166            None
3167        };
3168        match &mut self.backend {
3169            SchemaStoreBackend::Heap(map) => map.insert(key, snapshot),
3170            SchemaStoreBackend::Journaled {
3171                live, tombstones, ..
3172            } => {
3173                tombstones.remove(&key);
3174                live.insert(key, snapshot);
3175                previous_journaled
3176            }
3177        }
3178    }
3179
3180    /// Load one opaque schema/control value by key.
3181    #[must_use]
3182    fn get_raw_value(&self, key: &RawSchemaKey) -> Option<RawSchemaValue> {
3183        match &self.backend {
3184            SchemaStoreBackend::Heap(map) => map.get(key).cloned(),
3185            SchemaStoreBackend::Journaled { .. } => self.get_raw_value_for_backend(key),
3186        }
3187    }
3188
3189    fn accepted_root_slot_bytes(&self, slot: usize) -> Result<Option<Vec<u8>>, InternalError> {
3190        let key = RawSchemaKey::from_accepted_root_slot(slot)?;
3191        Ok(self.get_raw_value(&key).map(RawSchemaValue::into_bytes))
3192    }
3193
3194    fn canonical_root_slot_bytes(&self, slot: usize) -> Result<Option<Vec<u8>>, InternalError> {
3195        let key = RawSchemaKey::from_accepted_root_slot(slot)?;
3196        Ok(self
3197            .get_canonical_raw_value(&key)?
3198            .map(RawSchemaValue::into_bytes))
3199    }
3200
3201    fn current_root_matches_candidate(
3202        &self,
3203        candidate: &CandidateSchemaRevision,
3204    ) -> Result<bool, InternalError> {
3205        let Some(selection) = self.current_accepted_schema_root()? else {
3206            return Ok(false);
3207        };
3208        if selection.root() != candidate.root() {
3209            return Ok(false);
3210        }
3211        let key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
3212        let bundle = self
3213            .get_raw_value(&key)
3214            .ok_or_else(InternalError::store_corruption)?;
3215        let _verified =
3216            decode_verified_accepted_schema_revision_bundle(candidate.root(), bundle.as_bytes())?;
3217        Ok(true)
3218    }
3219
3220    fn canonical_root_matches_candidate(
3221        &self,
3222        candidate: &CandidateSchemaRevision,
3223    ) -> Result<bool, InternalError> {
3224        let first = self.canonical_root_slot_bytes(0)?;
3225        let second = self.canonical_root_slot_bytes(1)?;
3226        let Some(selection) =
3227            select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
3228        else {
3229            return Ok(false);
3230        };
3231        if selection.root() != candidate.root() {
3232            return Ok(false);
3233        }
3234        let key = RawSchemaKey::from_accepted_bundle(candidate.root().bundle_key());
3235        let bundle = self
3236            .get_canonical_raw_value(&key)?
3237            .ok_or_else(InternalError::store_corruption)?;
3238        let _verified =
3239            decode_verified_accepted_schema_revision_bundle(candidate.root(), bundle.as_bytes())?;
3240        Ok(true)
3241    }
3242
3243    fn get_canonical_raw_value(
3244        &self,
3245        key: &RawSchemaKey,
3246    ) -> Result<Option<RawSchemaValue>, InternalError> {
3247        match &self.backend {
3248            SchemaStoreBackend::Journaled { canonical, .. } => Ok(canonical.get(key)),
3249            SchemaStoreBackend::Heap(_) => Err(InternalError::store_invariant()),
3250        }
3251    }
3252
3253    fn insert_canonical_raw_value(
3254        &mut self,
3255        key: RawSchemaKey,
3256        bytes: Vec<u8>,
3257    ) -> Result<(), InternalError> {
3258        self.invalidate_accepted_bundle_cache_for_key(key);
3259        let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
3260            return Err(InternalError::store_invariant());
3261        };
3262        canonical.insert(key, RawSchemaValue::from_encoded_control_record(bytes));
3263        Ok(())
3264    }
3265
3266    // Initial accepted-catalog bootstrap persists immutable bundle/root values
3267    // directly in the schema allocation. Later online schema mutation will
3268    // carry the same values through the journal before calling this primitive.
3269    fn insert_durable_raw_value(&mut self, key: RawSchemaKey, bytes: Vec<u8>) {
3270        self.invalidate_accepted_bundle_cache_for_key(key);
3271        let value = RawSchemaValue::from_encoded_control_record(bytes);
3272        match &mut self.backend {
3273            SchemaStoreBackend::Heap(map) => {
3274                map.insert(key, value);
3275            }
3276            SchemaStoreBackend::Journaled {
3277                canonical,
3278                live,
3279                tombstones,
3280                ..
3281            } => {
3282                live.remove(&key);
3283                tombstones.remove(&key);
3284                canonical.insert(key, value);
3285            }
3286        }
3287    }
3288
3289    fn invalidate_accepted_bundle_cache_for_key(&mut self, key: RawSchemaKey) {
3290        if key.is_accepted_root() {
3291            self.accepted_bundle_cache.get_mut().take();
3292        }
3293    }
3294
3295    fn candidate_entry_keys(
3296        candidate: &CandidateSchemaRevision,
3297        root_slot: usize,
3298    ) -> Result<BTreeSet<RawSchemaKey>, InternalError> {
3299        let mut keys = BTreeSet::new();
3300        keys.insert(RawSchemaKey::from_accepted_bundle(
3301            candidate.root().bundle_key(),
3302        ));
3303        keys.insert(RawSchemaKey::from_accepted_root_slot(root_slot)?);
3304        for (entity_tag, snapshot) in candidate.bundle().entity_snapshots() {
3305            for activation in snapshot
3306                .constraint_activations()
3307                .iter()
3308                .filter(|activation| activation.state() == ConstraintActivationState::Validating)
3309            {
3310                keys.insert(RawSchemaKey::from_constraint_validation_job(
3311                    *entity_tag,
3312                    activation.id(),
3313                ));
3314            }
3315        }
3316        Ok(keys)
3317    }
3318
3319    fn positioned_candidate_effect_keys(
3320        &self,
3321        incarnation: DatabaseIncarnationId,
3322        expected_revision: AcceptedSchemaRevision,
3323        candidate: &CandidateSchemaRevision,
3324        view: IdentityStateStorageView,
3325    ) -> Result<BTreeSet<RawSchemaKey>, InternalError> {
3326        let identity_transition =
3327            self.prepare_identity_state_transition(incarnation, candidate, view)?;
3328        let (first, second, candidate_is_current) = match view {
3329            IdentityStateStorageView::Effective => (
3330                self.accepted_root_slot_bytes(0)?,
3331                self.accepted_root_slot_bytes(1)?,
3332                self.current_root_matches_candidate(candidate)?,
3333            ),
3334            IdentityStateStorageView::Canonical => (
3335                self.canonical_root_slot_bytes(0)?,
3336                self.canonical_root_slot_bytes(1)?,
3337                self.canonical_root_matches_candidate(candidate)?,
3338            ),
3339        };
3340        let root_slot = if candidate_is_current {
3341            select_current_accepted_schema_root([first.as_deref(), second.as_deref()])?
3342                .ok_or_else(InternalError::store_corruption)?
3343                .slot()
3344        } else {
3345            prepare_accepted_schema_root_publication(
3346                [first.as_deref(), second.as_deref()],
3347                expected_revision,
3348                candidate,
3349            )
3350            .map_err(map_schema_publication_error)?
3351            .target_slot()
3352        };
3353        let mut keys = Self::candidate_entry_keys(candidate, root_slot)?;
3354        let (updates, removals) = identity_transition.into_effects();
3355        for owner in removals {
3356            keys.insert(RawSchemaKey::from_identity_state(
3357                owner.entity_tag(),
3358                owner.field_id(),
3359            ));
3360        }
3361        for state in updates {
3362            keys.insert(RawSchemaKey::from_identity_state(
3363                state.owner().entity_tag(),
3364                state.owner().field_id(),
3365            ));
3366        }
3367
3368        let SchemaStoreBackend::Journaled {
3369            canonical,
3370            live,
3371            tombstones,
3372            positions,
3373        } = &self.backend
3374        else {
3375            return Err(InternalError::store_invariant());
3376        };
3377        for entry in canonical.range(RawSchemaKey::all_catalog_range_bounds()) {
3378            let has_relevant_overlay = matches!(view, IdentityStateStorageView::Effective)
3379                || positions.is_positioned(entry.key())
3380                || live.contains_key(entry.key())
3381                || tombstones.contains(entry.key());
3382            if has_relevant_overlay {
3383                keys.insert(*entry.key());
3384            }
3385        }
3386        if matches!(view, IdentityStateStorageView::Effective) {
3387            for (key, _) in live.range(RawSchemaKey::all_catalog_range_bounds()) {
3388                keys.insert(*key);
3389            }
3390        }
3391        Ok(keys)
3392    }
3393
3394    fn positioned_journal_batch_keys(
3395        &self,
3396        incarnation: DatabaseIncarnationId,
3397        batch: &JournalBatch,
3398        view: IdentityStateStorageView,
3399    ) -> Result<BTreeSet<RawSchemaKey>, InternalError> {
3400        let mut keys = BTreeSet::new();
3401        for record in batch.records() {
3402            match record {
3403                JournalRecord::AcceptedSchemaPublish {
3404                    expected_revision,
3405                    schema_bundle_bytes,
3406                    schema_root_bytes,
3407                    ..
3408                } => {
3409                    let candidate = CandidateSchemaRevision::from_encoded(
3410                        schema_bundle_bytes.clone(),
3411                        schema_root_bytes.clone(),
3412                    )?;
3413                    keys.extend(self.positioned_candidate_effect_keys(
3414                        incarnation,
3415                        *expected_revision,
3416                        &candidate,
3417                        view,
3418                    )?);
3419                }
3420                JournalRecord::ConstraintValidationJobPut {
3421                    entity_tag,
3422                    constraint_id,
3423                    ..
3424                }
3425                | JournalRecord::ConstraintValidationJobDelete {
3426                    entity_tag,
3427                    constraint_id,
3428                    ..
3429                } => {
3430                    keys.insert(RawSchemaKey::from_constraint_validation_job(
3431                        *entity_tag,
3432                        *constraint_id,
3433                    ));
3434                }
3435                JournalRecord::IdentityRangeAdvance { range } => {
3436                    keys.insert(RawSchemaKey::from_identity_state(
3437                        range.owner().entity_tag(),
3438                        range.owner().field_id(),
3439                    ));
3440                }
3441                JournalRecord::RowPut { .. }
3442                | JournalRecord::RowDelete { .. }
3443                | JournalRecord::AcceptedSchemaIndexDelete { .. }
3444                | JournalRecord::AcceptedSchemaIndexPut { .. }
3445                | JournalRecord::ConstraintValidationIndexPut { .. } => {}
3446                #[cfg(any(test, feature = "migration"))]
3447                JournalRecord::SchemaMigrationRowPut { .. }
3448                | JournalRecord::SchemaMigrationIndexPut { .. } => {}
3449            }
3450        }
3451        Ok(keys)
3452    }
3453
3454    // Keep only the current catalog entries. Range before collecting or removing
3455    // keys so publication work cannot scale with cardinality count records.
3456    // Identity and cardinality retain their own exact/paged lifecycle effects.
3457    fn retain_durable_candidate_entries(
3458        &mut self,
3459        candidate: &CandidateSchemaRevision,
3460        root_slot: usize,
3461    ) -> Result<(), InternalError> {
3462        let keep = Self::candidate_entry_keys(candidate, root_slot)?;
3463        self.accepted_bundle_cache.get_mut().take();
3464        match &mut self.backend {
3465            SchemaStoreBackend::Heap(map) => {
3466                Self::retain_heap_catalog_entries(map, &keep);
3467            }
3468            SchemaStoreBackend::Journaled {
3469                canonical,
3470                live,
3471                tombstones,
3472                ..
3473            } => {
3474                let stale = canonical
3475                    .range(RawSchemaKey::all_catalog_range_bounds())
3476                    .filter_map(|entry| (!keep.contains(entry.key())).then_some(*entry.key()))
3477                    .collect::<Vec<_>>();
3478                for key in stale {
3479                    canonical.remove(&key);
3480                }
3481                Self::retain_heap_catalog_entries(live, &keep);
3482                let cleared = tombstones
3483                    .range(RawSchemaKey::all_catalog_range_bounds())
3484                    .copied()
3485                    .collect::<Vec<_>>();
3486                for key in cleared {
3487                    tombstones.remove(&key);
3488                }
3489            }
3490        }
3491        Ok(())
3492    }
3493
3494    // Use the same bounded catalog range for heap storage and live overlays.
3495    fn retain_heap_catalog_entries(
3496        map: &mut StdBTreeMap<RawSchemaKey, RawSchemaValue>,
3497        keep: &BTreeSet<RawSchemaKey>,
3498    ) {
3499        let stale = map
3500            .range(RawSchemaKey::all_catalog_range_bounds())
3501            .filter_map(|(key, _)| (!keep.contains(key)).then_some(*key))
3502            .collect::<Vec<_>>();
3503        for key in stale {
3504            map.remove(&key);
3505        }
3506    }
3507
3508    fn retain_materialized_candidate_entries(
3509        &mut self,
3510        candidate: &CandidateSchemaRevision,
3511        root_slot: usize,
3512    ) -> Result<(), InternalError> {
3513        let keep = Self::candidate_entry_keys(candidate, root_slot)?;
3514        self.accepted_bundle_cache.get_mut().take();
3515        let SchemaStoreBackend::Journaled {
3516            canonical,
3517            live,
3518            tombstones,
3519            ..
3520        } = &mut self.backend
3521        else {
3522            return Err(InternalError::store_invariant());
3523        };
3524        Self::retain_heap_catalog_entries(live, &keep);
3525        let canonical_keys = canonical
3526            .range(RawSchemaKey::all_catalog_range_bounds())
3527            .map(|entry| *entry.key())
3528            .collect::<Vec<_>>();
3529        for key in canonical_keys {
3530            if keep.contains(&key) {
3531                tombstones.remove(&key);
3532            } else {
3533                tombstones.insert(key);
3534            }
3535        }
3536        Ok(())
3537    }
3538
3539    fn retain_canonical_candidate_entries(
3540        &mut self,
3541        keep: &BTreeSet<RawSchemaKey>,
3542    ) -> Result<(), InternalError> {
3543        self.accepted_bundle_cache.get_mut().take();
3544        let SchemaStoreBackend::Journaled { canonical, .. } = &mut self.backend else {
3545            return Err(InternalError::store_invariant());
3546        };
3547        let stale = canonical
3548            .range(RawSchemaKey::all_catalog_range_bounds())
3549            .filter_map(|entry| (!keep.contains(entry.key())).then_some(*entry.key()))
3550            .collect::<Vec<_>>();
3551        for key in stale {
3552            canonical.remove(&key);
3553        }
3554        Ok(())
3555    }
3556
3557    fn current_accepted_schema_bundle_ref(
3558        &self,
3559    ) -> Result<Option<Ref<'_, AcceptedSchemaRevisionBundle>>, InternalError> {
3560        self.current_accepted_schema_authority_ref()
3561            .map(|authority| authority.map(|(_selection, bundle)| bundle))
3562    }
3563
3564    /// Borrow the effective accepted root and its cached, verified immutable bundle together.
3565    pub(in crate::db) fn current_accepted_schema_authority_ref(
3566        &self,
3567    ) -> Result<
3568        Option<(
3569            AcceptedSchemaRootSelection,
3570            Ref<'_, AcceptedSchemaRevisionBundle>,
3571        )>,
3572        InternalError,
3573    > {
3574        let selection = self.current_accepted_schema_root()?;
3575        self.accepted_schema_authority_ref_for_selection(selection)
3576    }
3577
3578    fn accepted_schema_authority_ref_for_selection(
3579        &self,
3580        selection: Option<AcceptedSchemaRootSelection>,
3581    ) -> Result<
3582        Option<(
3583            AcceptedSchemaRootSelection,
3584            Ref<'_, AcceptedSchemaRevisionBundle>,
3585        )>,
3586        InternalError,
3587    > {
3588        let Some(selection) = selection else {
3589            self.accepted_bundle_cache
3590                .try_borrow_mut()
3591                .map_err(|_| InternalError::store_invariant())?
3592                .take();
3593            return Ok(None);
3594        };
3595
3596        let cache_matches = self
3597            .accepted_bundle_cache
3598            .try_borrow()
3599            .map_err(|_| InternalError::store_invariant())?
3600            .as_ref()
3601            .is_some_and(|cached| cached.selection == selection);
3602        if !cache_matches {
3603            let key = RawSchemaKey::from_accepted_bundle(selection.root().bundle_key());
3604            let raw = self
3605                .get_raw_value(&key)
3606                .ok_or_else(InternalError::store_corruption)?;
3607            let bundle =
3608                decode_verified_accepted_schema_revision_bundle(selection.root(), raw.as_bytes())?;
3609            self.validate_constraint_validation_job_closure(&bundle)?;
3610            #[cfg(test)]
3611            ACCEPTED_SCHEMA_BUNDLE_CACHE_MISSES
3612                .with(|misses| misses.set(misses.get().saturating_add(1)));
3613            let value_catalog = AcceptedValueCatalogHandle::new(
3614                bundle.enum_catalog().clone(),
3615                bundle.composite_catalog().clone(),
3616                self.accepted_catalog_scope
3617                    .get_or_init(AcceptedStoreCatalogScope::new)
3618                    .clone(),
3619                bundle.revision(),
3620                selection.root().fingerprint(),
3621            );
3622            let cardinality_domain = Rc::new(CardinalityAcceptedDomain::derive(&bundle)?);
3623            *self
3624                .accepted_bundle_cache
3625                .try_borrow_mut()
3626                .map_err(|_| InternalError::store_invariant())? = Some(AcceptedSchemaBundleCache {
3627                selection,
3628                bundle,
3629                cardinality_domain,
3630                value_catalog,
3631                entity_selections: RefCell::new(StdBTreeMap::new()),
3632            });
3633        }
3634
3635        let cache = self
3636            .accepted_bundle_cache
3637            .try_borrow()
3638            .map_err(|_| InternalError::store_invariant())?;
3639        let bundle = Ref::filter_map(cache, |cache| {
3640            cache
3641                .as_ref()
3642                .filter(|cached| cached.selection == selection)
3643                .map(|cached| &cached.bundle)
3644        })
3645        .map_err(|_| InternalError::store_invariant())?;
3646        self.validate_identity_state_closure(&bundle)?;
3647        Ok(Some((selection, bundle)))
3648    }
3649
3650    /// Reuse the accepted-domain projection for one already-selected effective root.
3651    pub(in crate::db) fn accepted_cardinality_domain_for_selection(
3652        &self,
3653        selection: Option<AcceptedSchemaRootSelection>,
3654    ) -> Result<Option<(AcceptedSchemaRootSelection, Rc<CardinalityAcceptedDomain>)>, InternalError>
3655    {
3656        let Some(selection) = selection else {
3657            self.accepted_bundle_cache
3658                .try_borrow_mut()
3659                .map_err(|_| InternalError::store_invariant())?
3660                .take();
3661            return Ok(None);
3662        };
3663        let cache_matches = self
3664            .accepted_bundle_cache
3665            .try_borrow()
3666            .map_err(|_| InternalError::store_invariant())?
3667            .as_ref()
3668            .is_some_and(|cached| cached.selection == selection);
3669        if !cache_matches {
3670            let authority = self
3671                .accepted_schema_authority_ref_for_selection(Some(selection))?
3672                .ok_or_else(InternalError::store_invariant)?;
3673            drop(authority);
3674        }
3675        let cache = self
3676            .accepted_bundle_cache
3677            .try_borrow()
3678            .map_err(|_| InternalError::store_invariant())?;
3679        let domain = cache
3680            .as_ref()
3681            .filter(|cached| cached.selection == selection)
3682            .map(|cached| Rc::clone(&cached.cardinality_domain))
3683            .ok_or_else(InternalError::store_invariant)?;
3684        Ok(Some((selection, domain)))
3685    }
3686
3687    /// Borrow the cached accepted-domain projection for an already-admitted root.
3688    pub(in crate::db) fn cached_cardinality_domain_for_root(
3689        &self,
3690        root: CardinalityAcceptedRootIdentity,
3691    ) -> Result<Option<Rc<CardinalityAcceptedDomain>>, InternalError> {
3692        let cache = self
3693            .accepted_bundle_cache
3694            .try_borrow()
3695            .map_err(|_| InternalError::store_invariant())?;
3696        Ok(cache
3697            .as_ref()
3698            .filter(|cached| root.matches(cached.selection.root()))
3699            .map(|cached| Rc::clone(&cached.cardinality_domain)))
3700    }
3701
3702    fn visit_constraint_validation_jobs_in_view<E>(
3703        &self,
3704        view: IdentityStateStorageView,
3705        visitor: impl FnMut(&RawSchemaKey, &RawSchemaValue) -> Result<SchemaStoreVisit, E>,
3706    ) -> Result<(), E> {
3707        let bounds = RawSchemaKey::all_constraint_validation_job_range_bounds();
3708        match (&self.backend, view) {
3709            (SchemaStoreBackend::Heap(map), _) => {
3710                let mut visitor = visitor;
3711                for (key, snapshot) in map.range((bounds.0, bounds.1)) {
3712                    if visitor(key, snapshot)?.should_stop() {
3713                        break;
3714                    }
3715                }
3716            }
3717            (
3718                SchemaStoreBackend::Journaled {
3719                    canonical,
3720                    live,
3721                    tombstones,
3722                    ..
3723                },
3724                IdentityStateStorageView::Effective,
3725            ) => Self::visit_journaled_raw_value_range(
3726                canonical,
3727                live,
3728                tombstones,
3729                bounds,
3730                Direction::Asc,
3731                visitor,
3732            )?,
3733            (
3734                SchemaStoreBackend::Journaled { canonical, .. },
3735                IdentityStateStorageView::Canonical,
3736            ) => {
3737                let mut visitor = visitor;
3738                for entry in canonical.range((bounds.0, bounds.1)) {
3739                    if visitor(entry.key(), &entry.value())?.should_stop() {
3740                        break;
3741                    }
3742                }
3743            }
3744        }
3745        Ok(())
3746    }
3747
3748    #[cfg(test)]
3749    #[must_use]
3750    pub(in crate::db) fn canonical_len_for_tests(&self) -> u64 {
3751        match &self.backend {
3752            SchemaStoreBackend::Journaled { canonical: map, .. } => map.len(),
3753            SchemaStoreBackend::Heap(_) => 0,
3754        }
3755    }
3756
3757    /// Damage the selected root so public consumers exercise fallible inspection.
3758    #[cfg(all(test, feature = "sql"))]
3759    pub(in crate::db) fn corrupt_current_accepted_schema_root_for_tests(
3760        &mut self,
3761    ) -> Result<(), InternalError> {
3762        let selected = self
3763            .current_accepted_schema_root()?
3764            .ok_or_else(InternalError::store_corruption)?;
3765        let key = RawSchemaKey::from_accepted_root_slot(selected.slot())?;
3766        self.insert_durable_raw_value(key, vec![0xff]);
3767        Ok(())
3768    }
3769
3770    fn get_raw_value_for_backend(&self, key: &RawSchemaKey) -> Option<RawSchemaValue> {
3771        let SchemaStoreBackend::Journaled {
3772            canonical,
3773            live,
3774            tombstones,
3775            ..
3776        } = &self.backend
3777        else {
3778            return None;
3779        };
3780
3781        if tombstones.contains(key) {
3782            return None;
3783        }
3784        live.get(key).cloned().or_else(|| canonical.get(key))
3785    }
3786
3787    fn visit_journaled_raw_value_range<E>(
3788        canonical: &StableBTreeMap<RawSchemaKey, RawSchemaValue, DefaultMemoryImpl>,
3789        live: &StdBTreeMap<RawSchemaKey, RawSchemaValue>,
3790        tombstones: &BTreeSet<RawSchemaKey>,
3791        bounds: (RangeBound<RawSchemaKey>, RangeBound<RawSchemaKey>),
3792        direction: Direction,
3793        mut visitor: impl FnMut(&RawSchemaKey, &RawSchemaValue) -> Result<SchemaStoreVisit, E>,
3794    ) -> Result<(), E> {
3795        match direction {
3796            Direction::Asc => {
3797                for entry in ordered_overlay_entries(
3798                    canonical.range((bounds.0, bounds.1)),
3799                    live.range((bounds.0, bounds.1)),
3800                    Direction::Asc,
3801                    |entry| entry.key(),
3802                    |entry| entry.0,
3803                    tombstones,
3804                ) {
3805                    let visit = match entry {
3806                        OrderedOverlayEntry::Canonical(canonical_entry) => {
3807                            visitor(canonical_entry.key(), &canonical_entry.value())?
3808                        }
3809                        OrderedOverlayEntry::Live((key, snapshot)) => visitor(key, snapshot)?,
3810                    };
3811                    if visit.should_stop() {
3812                        return Ok(());
3813                    }
3814                }
3815            }
3816            Direction::Desc => {
3817                for entry in ordered_overlay_entries(
3818                    canonical.range((bounds.0, bounds.1)).rev(),
3819                    live.range((bounds.0, bounds.1)).rev(),
3820                    Direction::Desc,
3821                    |entry| entry.key(),
3822                    |entry| entry.0,
3823                    tombstones,
3824                ) {
3825                    let visit = match entry {
3826                        OrderedOverlayEntry::Canonical(canonical_entry) => {
3827                            visitor(canonical_entry.key(), &canonical_entry.value())?
3828                        }
3829                        OrderedOverlayEntry::Live((key, snapshot)) => visitor(key, snapshot)?,
3830                    };
3831                    if visit.should_stop() {
3832                        return Ok(());
3833                    }
3834                }
3835            }
3836        }
3837
3838        Ok(())
3839    }
3840}
3841
3842fn map_schema_publication_error(error: AcceptedSchemaPublicationError) -> InternalError {
3843    match error {
3844        AcceptedSchemaPublicationError::StaleSchemaRevision { .. }
3845        | AcceptedSchemaPublicationError::RevisionExhausted => InternalError::store_unsupported(),
3846        AcceptedSchemaPublicationError::InvalidCandidate => InternalError::store_invariant(),
3847        AcceptedSchemaPublicationError::CorruptRootSlots => InternalError::store_corruption(),
3848    }
3849}
3850
3851fn derive_data_allocation_metadata(
3852    snapshots: &StdBTreeMap<EntityTag, PersistedSchemaSnapshot>,
3853) -> Result<SchemaStoreCatalogMetadata, InternalError> {
3854    let mut max_version = SchemaVersion::initial();
3855    let mut hasher = new_hash_sha256();
3856    write_hash_tag_u8(&mut hasher, SCHEMA_STORE_DATA_ALLOCATION_FINGERPRINT_DOMAIN);
3857
3858    for (entity, persisted) in snapshots {
3859        if persisted.version() > max_version {
3860            max_version = persisted.version();
3861        }
3862
3863        let data_projection = PersistedSchemaSnapshot::new_with_primary_key_fields_and_indexes(
3864            persisted.version(),
3865            persisted.entity_path().to_string(),
3866            persisted.entity_name().to_string(),
3867            persisted.primary_key_field_ids().to_vec(),
3868            persisted.row_layout().clone(),
3869            persisted.fields().to_vec(),
3870            Vec::new(),
3871        );
3872        let constraint_catalog = crate::db::schema::AcceptedConstraintCatalog::initial(
3873            data_projection.fields(),
3874            data_projection.indexes(),
3875            data_projection.relations(),
3876        )
3877        .map_err(|_| InternalError::store_invariant())?;
3878        let data_projection = data_projection.with_constraint_catalog(constraint_catalog);
3879        let encoded = encode_persisted_schema_snapshot(&data_projection)?;
3880
3881        write_hash_u64(&mut hasher, entity.value());
3882        write_hash_u32(&mut hasher, persisted.version().get());
3883        write_hash_len_u32(&mut hasher, encoded.len());
3884        hasher.update(encoded);
3885    }
3886
3887    Ok(finalize_schema_metadata(
3888        max_version,
3889        SCHEMA_STORE_FINGERPRINT_METHOD_VERSION,
3890        hasher,
3891        snapshots.len(),
3892    ))
3893}
3894
3895fn derive_index_allocation_metadata(
3896    snapshots: &StdBTreeMap<EntityTag, PersistedSchemaSnapshot>,
3897) -> Result<SchemaStoreCatalogMetadata, InternalError> {
3898    let mut max_version = SchemaVersion::initial();
3899    let mut hasher = new_hash_sha256();
3900    write_hash_tag_u8(
3901        &mut hasher,
3902        SCHEMA_STORE_INDEX_ALLOCATION_FINGERPRINT_DOMAIN,
3903    );
3904
3905    for (entity, persisted) in snapshots {
3906        if persisted.version() > max_version {
3907            max_version = persisted.version();
3908        }
3909
3910        write_hash_u64(&mut hasher, entity.value());
3911        write_hash_u32(&mut hasher, persisted.version().get());
3912        write_hash_len_u32(&mut hasher, persisted.indexes().len());
3913        for index in persisted.indexes() {
3914            write_hash_u32(&mut hasher, u32::from(index.ordinal()));
3915            write_hash_str_u32(&mut hasher, index.name());
3916            write_hash_str_u32(&mut hasher, index.store());
3917            write_hash_tag_u8(&mut hasher, u8::from(index.unique()));
3918            write_hash_str_u32(&mut hasher, persisted_index_origin_name(index.origin()));
3919            match index.predicate() {
3920                Some(predicate) => {
3921                    write_hash_tag_u8(&mut hasher, 1);
3922                    let bytes = predicate.canonical_bytes()?;
3923                    write_hash_len_u32(&mut hasher, bytes.len());
3924                    hasher.update(&bytes);
3925                }
3926                None => write_hash_tag_u8(&mut hasher, 0),
3927            }
3928            hash_persisted_index_key(&mut hasher, index.key());
3929        }
3930    }
3931
3932    Ok(finalize_schema_metadata(
3933        max_version,
3934        SCHEMA_STORE_FINGERPRINT_METHOD_VERSION,
3935        hasher,
3936        snapshots.len(),
3937    ))
3938}
3939
3940fn derive_schema_catalog_metadata(
3941    snapshots: &StdBTreeMap<EntityTag, PersistedSchemaSnapshot>,
3942) -> Result<SchemaStoreCatalogMetadata, InternalError> {
3943    let mut max_version = SchemaVersion::initial();
3944    let mut hasher = new_hash_sha256();
3945    write_hash_tag_u8(&mut hasher, SCHEMA_STORE_CATALOG_FINGERPRINT_DOMAIN);
3946
3947    for (entity, persisted) in snapshots {
3948        if persisted.version() > max_version {
3949            max_version = persisted.version();
3950        }
3951
3952        write_hash_u64(&mut hasher, entity.value());
3953        let encoded = encode_persisted_schema_snapshot(persisted)?;
3954        write_hash_u32(&mut hasher, persisted.version().get());
3955        write_hash_len_u32(&mut hasher, encoded.len());
3956        hasher.update(encoded);
3957    }
3958
3959    Ok(finalize_schema_metadata(
3960        max_version,
3961        SCHEMA_STORE_FINGERPRINT_METHOD_VERSION,
3962        hasher,
3963        snapshots.len(),
3964    ))
3965}
3966
3967fn finalize_schema_metadata(
3968    schema_version: SchemaVersion,
3969    schema_fingerprint_method_version: u8,
3970    hasher: sha2::Sha256,
3971    entity_count: usize,
3972) -> SchemaStoreCatalogMetadata {
3973    let digest = finalize_hash_sha256(hasher);
3974    let mut schema_fingerprint = [0u8; 16];
3975    schema_fingerprint.copy_from_slice(&digest[..16]);
3976
3977    SchemaStoreCatalogMetadata::new(
3978        schema_version,
3979        schema_fingerprint_method_version,
3980        schema_fingerprint,
3981        u64::try_from(entity_count).unwrap_or(u64::MAX),
3982    )
3983}
3984
3985fn hash_persisted_index_key(hasher: &mut sha2::Sha256, key: &PersistedIndexKeySnapshot) {
3986    write_hash_len_u32(hasher, key.component_count());
3987    for item in key.items() {
3988        match item {
3989            PersistedIndexKeyItemSnapshot::FieldPath(path) => {
3990                write_hash_tag_u8(hasher, 1);
3991                hash_persisted_index_field_path(hasher, path);
3992            }
3993            PersistedIndexKeyItemSnapshot::Expression(expression) => {
3994                write_hash_tag_u8(hasher, 2);
3995                write_hash_str_u32(hasher, persisted_expression_op_name(expression.op()));
3996                hash_persisted_index_field_path(hasher, expression.source());
3997                hash_accepted_field_kind(hasher, expression.input_kind());
3998                hash_accepted_field_kind(hasher, expression.output_kind());
3999                write_hash_str_u32(hasher, expression.canonical_text());
4000            }
4001        }
4002    }
4003}
4004
4005fn hash_persisted_index_field_path(
4006    hasher: &mut sha2::Sha256,
4007    path: &crate::db::schema::PersistedIndexFieldPathSnapshot,
4008) {
4009    write_hash_u32(hasher, path.field_id().get());
4010    write_hash_u32(hasher, u32::from(path.slot().get()));
4011    write_hash_len_u32(hasher, path.path().len());
4012    for segment in path.path() {
4013        write_hash_str_u32(hasher, segment);
4014    }
4015    hash_accepted_field_kind(hasher, path.kind());
4016    write_hash_tag_u8(hasher, u8::from(path.nullable()));
4017}
4018
4019fn hash_accepted_field_kind(hasher: &mut sha2::Sha256, kind: &AcceptedFieldKind) {
4020    match kind {
4021        AcceptedFieldKind::Account => write_hash_tag_u8(hasher, 1),
4022        AcceptedFieldKind::Blob { max_len } => {
4023            write_hash_tag_u8(hasher, 2);
4024            hash_optional_u32(hasher, *max_len);
4025        }
4026        AcceptedFieldKind::Bool => {
4027            write_hash_tag_u8(hasher, ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_BOOL);
4028        }
4029        AcceptedFieldKind::Date => write_hash_tag_u8(hasher, 4),
4030        AcceptedFieldKind::Decimal { scale } => {
4031            write_hash_tag_u8(hasher, 5);
4032            write_hash_u32(hasher, *scale);
4033        }
4034        AcceptedFieldKind::Duration => write_hash_tag_u8(hasher, 6),
4035        AcceptedFieldKind::Enum { type_id } => {
4036            write_hash_tag_u8(hasher, 7);
4037            write_hash_u32(hasher, type_id.get());
4038        }
4039        AcceptedFieldKind::Float32 => write_hash_tag_u8(hasher, 8),
4040        AcceptedFieldKind::Float64 => write_hash_tag_u8(hasher, 9),
4041        AcceptedFieldKind::Int8 => write_hash_tag_u8(hasher, 10),
4042        AcceptedFieldKind::Int16 => write_hash_tag_u8(hasher, 11),
4043        AcceptedFieldKind::Int32 => write_hash_tag_u8(hasher, 12),
4044        AcceptedFieldKind::Int64 => write_hash_tag_u8(hasher, 13),
4045        AcceptedFieldKind::Int128 => write_hash_tag_u8(hasher, 14),
4046        AcceptedFieldKind::Principal => write_hash_tag_u8(hasher, 16),
4047        AcceptedFieldKind::Subaccount => write_hash_tag_u8(hasher, 17),
4048        AcceptedFieldKind::Text { max_len } => {
4049            write_hash_tag_u8(hasher, 18);
4050            hash_optional_u32(hasher, *max_len);
4051        }
4052        AcceptedFieldKind::Timestamp => write_hash_tag_u8(hasher, 19),
4053        AcceptedFieldKind::Nat8 => write_hash_tag_u8(hasher, 20),
4054        AcceptedFieldKind::Nat16 => write_hash_tag_u8(hasher, 21),
4055        AcceptedFieldKind::Nat32 => write_hash_tag_u8(hasher, 22),
4056        AcceptedFieldKind::Nat64 => write_hash_tag_u8(hasher, 23),
4057        AcceptedFieldKind::Nat128 => write_hash_tag_u8(hasher, 24),
4058        AcceptedFieldKind::Ulid => write_hash_tag_u8(hasher, 26),
4059        AcceptedFieldKind::Unit => write_hash_tag_u8(hasher, 27),
4060        AcceptedFieldKind::Relation {
4061            target_path,
4062            target_entity_name,
4063            target_entity_tag,
4064            target_store_path,
4065            key_kind,
4066        } => {
4067            write_hash_tag_u8(hasher, 28);
4068            write_hash_str_u32(hasher, target_path);
4069            write_hash_str_u32(hasher, target_entity_name);
4070            write_hash_u64(hasher, target_entity_tag.value());
4071            write_hash_str_u32(hasher, target_store_path);
4072            hash_accepted_field_kind(hasher, key_kind);
4073        }
4074        AcceptedFieldKind::List(inner) => {
4075            write_hash_tag_u8(hasher, ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_LIST);
4076            hash_accepted_field_kind(hasher, inner);
4077        }
4078        AcceptedFieldKind::Set(inner) => {
4079            write_hash_tag_u8(hasher, ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_SET);
4080            hash_accepted_field_kind(hasher, inner);
4081        }
4082        AcceptedFieldKind::Map { key, value } => {
4083            write_hash_tag_u8(hasher, ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_MAP);
4084            hash_accepted_field_kind(hasher, key);
4085            hash_accepted_field_kind(hasher, value);
4086        }
4087        AcceptedFieldKind::Composite { type_id } => {
4088            write_hash_tag_u8(hasher, ACCEPTED_FIELD_KIND_FINGERPRINT_TAG_COMPOSITE);
4089            write_hash_u32(hasher, type_id.get());
4090        }
4091        AcceptedFieldKind::U256 => write_hash_tag_u8(hasher, 33),
4092    }
4093}
4094
4095fn hash_optional_u32(hasher: &mut sha2::Sha256, value: Option<u32>) {
4096    match value {
4097        Some(value) => {
4098            write_hash_tag_u8(hasher, 1);
4099            write_hash_u32(hasher, value);
4100        }
4101        None => write_hash_tag_u8(hasher, 0),
4102    }
4103}
4104
4105const fn persisted_index_origin_name(
4106    origin: crate::db::schema::PersistedIndexOrigin,
4107) -> &'static str {
4108    match origin {
4109        crate::db::schema::PersistedIndexOrigin::Generated => "generated",
4110        crate::db::schema::PersistedIndexOrigin::SqlDdl => "sql_ddl",
4111    }
4112}
4113
4114const fn persisted_expression_op_name(
4115    op: crate::db::schema::PersistedIndexExpressionOp,
4116) -> &'static str {
4117    match op {
4118        crate::db::schema::PersistedIndexExpressionOp::Lower => "lower",
4119        crate::db::schema::PersistedIndexExpressionOp::Upper => "upper",
4120        crate::db::schema::PersistedIndexExpressionOp::Trim => "trim",
4121        crate::db::schema::PersistedIndexExpressionOp::LowerTrim => "lower_trim",
4122        crate::db::schema::PersistedIndexExpressionOp::Date => "date",
4123        crate::db::schema::PersistedIndexExpressionOp::Year => "year",
4124        crate::db::schema::PersistedIndexExpressionOp::Month => "month",
4125        crate::db::schema::PersistedIndexExpressionOp::Day => "day",
4126    }
4127}
4128
4129///
4130/// TESTS
4131///
4132
4133#[cfg(test)]
4134mod tests;