Skip to main content

icydb_core/db/session/
write.rs

1//! Module: db::session::write
2//! Responsibility: session-owned typed write APIs for insert, replace, update,
3//! and structural mutation entrypoints over the shared save pipeline.
4//! Does not own: commit staging, mutation execution, or persistence encoding.
5//! Boundary: keeps public session write semantics above the executor save surface.
6
7use super::AcceptedSchemaCatalogContext;
8use crate::{
9    db::{
10        DbSession, DynamicMutation, DynamicMutationResult, DynamicStructuralPatch,
11        DynamicTypedBindingError, DynamicTypedEntityBinding, DynamicTypedMutation,
12        DynamicTypedStructuralPatch, DynamicWriteCell, TypedEntityDescriptor, TypedFieldType,
13        commit::{CommitRowOp, database_incarnation_id},
14        data::{
15            AcceptedMutationIntentPatch, AcceptedPreKeyInsert, DecodedDataStoreKey, FieldSlot,
16            RawRow, StructuralRowContract, StructuralSlotReader,
17            canonical_row_from_raw_row_with_accepted_decode_contract,
18            resolve_existing_replace_structural_patch_with_accepted_contract,
19            resolve_insert_structural_patch_with_accepted_contract,
20            resolve_update_structural_patch_with_accepted_contract,
21        },
22        executor::{
23            AcceptedMutationConstraintContext, AcceptedMutationConstraintScheduler,
24            budget::finish_current_execution_instruction_watermark,
25            commit_structural_row_ops_with_mutation_progress,
26            commit_structural_row_ops_with_window, mutation_key_exists_error,
27        },
28        integrity::MutationProgressRecordOp,
29        schema::{
30            AcceptedFieldKind, AcceptedIdentityAllocation, AcceptedRowLayoutRuntimeContract,
31            FieldId, FieldInsertGeneration, IdentityStatementCursor, lower_field_type,
32            output_value_from_runtime,
33        },
34        write_context::{AcceptedWriteContext, MutationMode},
35    },
36    error::{InternalError, MutationDiagnosticContext},
37    metrics::sink::{MetricsEvent, SaveMutationKind, record},
38    traits::CanisterKind,
39    types::{CurrentTimestamp, Timestamp},
40    value::{InputValue, Value},
41};
42use icydb_schema::{EntitySourceKey, FieldSourceKey, FieldType, TypeSourceKey};
43
44#[derive(Clone, Debug, Eq, PartialEq)]
45struct AcceptedIdentityInsertField {
46    field_id: FieldId,
47    field_slot: usize,
48    accepted_kind: AcceptedFieldKind,
49}
50
51struct AcceptedStructuralMutationCommitOptions {
52    capture_output_values: bool,
53    packing: AcceptedStructuralMutationPacking,
54}
55
56impl AcceptedStructuralMutationCommitOptions {
57    const fn standard() -> Self {
58        Self {
59            capture_output_values: true,
60            packing: AcceptedStructuralMutationPacking::Complete,
61        }
62    }
63
64    #[cfg(test)]
65    const fn with_mutation_progress() -> Self {
66        Self {
67            capture_output_values: false,
68            packing: AcceptedStructuralMutationPacking::Complete,
69        }
70    }
71
72    const fn bounded_prefix() -> Self {
73        Self {
74            capture_output_values: false,
75            packing: AcceptedStructuralMutationPacking::BoundedPrefix,
76        }
77    }
78}
79
80#[derive(Clone, Copy)]
81enum AcceptedStructuralMutationPacking {
82    Complete,
83    BoundedPrefix,
84}
85
86pub(in crate::db::session) enum AcceptedStructuralMutationCommitDirective {
87    Standard,
88    WithMutationProgress(MutationProgressRecordOp),
89    Skip,
90}
91
92/// Accepted row identity carried by a structural mutation after frontend
93/// lowering but before the canonical after-image exists.
94pub(in crate::db::session) enum AcceptedStructuralMutationTarget {
95    ResolveFromAfterImage,
96    Expected(Box<DecodedDataStoreKey>),
97    ExpectedLoaded(AcceptedLoadedStructuralRow),
98}
99
100/// One retained row whose accepted key relationship was validated by the
101/// synchronous operation that loaded it.
102pub(in crate::db::session) struct AcceptedLoadedStructuralRow {
103    key: Box<DecodedDataStoreKey>,
104    row: RawRow,
105}
106
107impl AcceptedLoadedStructuralRow {
108    pub(in crate::db::session) fn from_validated_parts(
109        key: DecodedDataStoreKey,
110        row: RawRow,
111    ) -> Self {
112        Self {
113            key: Box::new(key),
114            row,
115        }
116    }
117
118    fn into_parts(self) -> (DecodedDataStoreKey, RawRow) {
119        (*self.key, self.row)
120    }
121}
122
123impl AcceptedStructuralMutationTarget {
124    pub(in crate::db::session) fn expected(key: DecodedDataStoreKey) -> Self {
125        Self::Expected(Box::new(key))
126    }
127
128    /// Retain a row loaded by the same synchronous operation so mutation
129    /// materialization does not perform a duplicate backend point read.
130    pub(in crate::db::session) const fn expected_loaded(row: AcceptedLoadedStructuralRow) -> Self {
131        Self::ExpectedLoaded(row)
132    }
133}
134
135/// One accepted structural mutation intent ready for shared batch
136/// materialization.
137pub(in crate::db::session) enum AcceptedStructuralMutation {
138    Save {
139        mode: MutationMode,
140        target: AcceptedStructuralMutationTarget,
141        patch: AcceptedMutationIntentPatch,
142    },
143    Delete {
144        key: Box<DecodedDataStoreKey>,
145    },
146}
147
148impl AcceptedStructuralMutation {
149    pub(in crate::db::session) const fn save(
150        mode: MutationMode,
151        target: AcceptedStructuralMutationTarget,
152        patch: AcceptedMutationIntentPatch,
153    ) -> Self {
154        Self::Save {
155            mode,
156            target,
157            patch,
158        }
159    }
160
161    pub(in crate::db::session) fn delete(key: DecodedDataStoreKey) -> Self {
162        Self::Delete { key: Box::new(key) }
163    }
164}
165
166const MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS: usize = 4_096;
167const MAX_STRUCTURAL_MUTATION_BATCH_ENTITIES: usize = 64;
168pub(in crate::db::session) const STRUCTURAL_MUTATION_BATCH_STAGED_BYTES_POLICY: u32 =
169    16 * 1024 * 1024;
170pub(in crate::db::session) const MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES: usize =
171    STRUCTURAL_MUTATION_BATCH_STAGED_BYTES_POLICY as usize;
172const MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES: usize = 1024 * 1024;
173
174struct AcceptedStructuralMutationBatchItem {
175    catalog: AcceptedSchemaCatalogContext,
176    mutation: AcceptedStructuralMutation,
177}
178
179struct AcceptedStructuralMutationEntityState {
180    entity_tag: crate::types::EntityTag,
181    identity_field: Option<AcceptedIdentityInsertField>,
182    identity_incarnation: Option<crate::db::integrity::DatabaseIncarnationId>,
183    identity_cursor: Option<IdentityStatementCursor>,
184    identity_insert_ordinal: u32,
185}
186
187#[derive(Clone, Copy, Debug, Eq, PartialEq)]
188pub(in crate::db::session) struct AcceptedStructuralMutationPackingReport {
189    admitted_mutations: usize,
190    staged_bytes: usize,
191    stopped_before_candidate: bool,
192    candidate_exceeds_batch_policy: bool,
193}
194
195impl AcceptedStructuralMutationPackingReport {
196    #[must_use]
197    pub(in crate::db::session) const fn admitted_mutations(self) -> usize {
198        self.admitted_mutations
199    }
200
201    #[must_use]
202    pub(in crate::db::session) const fn staged_bytes(self) -> usize {
203        self.staged_bytes
204    }
205
206    #[must_use]
207    pub(in crate::db::session) const fn stopped_before_candidate(self) -> bool {
208        self.stopped_before_candidate
209    }
210
211    #[must_use]
212    pub(in crate::db::session) const fn candidate_exceeds_batch_policy(self) -> bool {
213        self.candidate_exceeds_batch_policy
214    }
215}
216
217fn structural_mutation_staged_charge(
218    lengths: impl IntoIterator<Item = usize>,
219) -> Result<usize, InternalError> {
220    lengths.into_iter().try_fold(0_usize, |total, length| {
221        total.checked_add(length).ok_or_else(|| {
222            InternalError::mutation_batch_staged_bytes_exceeded(
223                None,
224                MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES,
225            )
226        })
227    })
228}
229
230fn add_structural_mutation_staged_bytes(
231    total: &mut usize,
232    lengths: impl IntoIterator<Item = usize>,
233) -> Result<(), InternalError> {
234    let charge = structural_mutation_staged_charge(lengths)?;
235    *total = total.checked_add(charge).ok_or_else(|| {
236        InternalError::mutation_batch_staged_bytes_exceeded(
237            None,
238            MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES,
239        )
240    })?;
241    if *total > MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES {
242        return Err(InternalError::mutation_batch_staged_bytes_exceeded(
243            Some(*total),
244            MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES,
245        ));
246    }
247    Ok(())
248}
249
250fn admit_structural_mutation_staged_charge(
251    total: &mut usize,
252    lengths: impl IntoIterator<Item = usize>,
253    packing: AcceptedStructuralMutationPacking,
254) -> Result<AcceptedStructuralMutationStagedAdmission, InternalError> {
255    if matches!(packing, AcceptedStructuralMutationPacking::Complete) {
256        add_structural_mutation_staged_bytes(total, lengths)?;
257        return Ok(AcceptedStructuralMutationStagedAdmission::Admitted);
258    }
259
260    let charge = structural_mutation_staged_charge(lengths)?;
261    if charge > MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES {
262        return Ok(AcceptedStructuralMutationStagedAdmission::CandidateExceedsPolicy);
263    }
264    let Some(next_total) = total.checked_add(charge) else {
265        return Ok(AcceptedStructuralMutationStagedAdmission::PageFull);
266    };
267    if next_total > MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES {
268        return Ok(AcceptedStructuralMutationStagedAdmission::PageFull);
269    }
270    *total = next_total;
271    Ok(AcceptedStructuralMutationStagedAdmission::Admitted)
272}
273
274#[derive(Clone, Copy, Debug, Eq, PartialEq)]
275enum AcceptedStructuralMutationStagedAdmission {
276    Admitted,
277    PageFull,
278    CandidateExceedsPolicy,
279}
280
281fn validate_structural_mutation_result_bytes(encoded_bytes: usize) -> Result<(), InternalError> {
282    if encoded_bytes > MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES {
283        return Err(InternalError::mutation_batch_result_bytes_exceeded(
284            encoded_bytes,
285            MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES,
286        ));
287    }
288    Ok(())
289}
290
291/// One canonical row produced by structural mutation materialization.
292pub(in crate::db::session) struct AcceptedStructuralMutationRow {
293    values: Vec<Value>,
294    logical_changed: bool,
295}
296
297impl AcceptedStructuralMutationRow {
298    #[cfg(any(feature = "sql", test))]
299    pub(in crate::db::session) fn into_values(self) -> Vec<Value> {
300        self.values
301    }
302
303    pub(in crate::db::session) const fn logical_changed(&self) -> bool {
304        self.logical_changed
305    }
306}
307
308const fn dynamic_mutation_mode(request: &DynamicMutation) -> Option<MutationMode> {
309    match request {
310        DynamicMutation::Insert { .. } => Some(MutationMode::Insert),
311        DynamicMutation::Update { .. } => Some(MutationMode::Update),
312        DynamicMutation::Replace { .. } => Some(MutationMode::Replace),
313        DynamicMutation::Delete { .. } => None,
314    }
315}
316
317const fn dynamic_typed_mutation_mode(request: &DynamicTypedMutation) -> Option<MutationMode> {
318    match request {
319        DynamicTypedMutation::Insert { .. } => Some(MutationMode::Insert),
320        DynamicTypedMutation::Update { .. } => Some(MutationMode::Update),
321        DynamicTypedMutation::Replace { .. } => Some(MutationMode::Replace),
322        DynamicTypedMutation::Delete { .. } => None,
323    }
324}
325
326const fn save_mutation_kind(mode: MutationMode) -> SaveMutationKind {
327    match mode {
328        MutationMode::Insert => SaveMutationKind::Insert,
329        MutationMode::Replace => SaveMutationKind::Replace,
330        MutationMode::Update => SaveMutationKind::Update,
331    }
332}
333
334const fn diagnostic_mutation_operation(
335    mode: MutationMode,
336) -> icydb_diagnostic_code::DiagnosticMutationOperation {
337    match mode {
338        MutationMode::Insert => icydb_diagnostic_code::DiagnosticMutationOperation::Insert,
339        MutationMode::Replace => icydb_diagnostic_code::DiagnosticMutationOperation::Replace,
340        MutationMode::Update => icydb_diagnostic_code::DiagnosticMutationOperation::Update,
341    }
342}
343
344const fn mutation_diagnostic_context(
345    entity_tag: crate::types::EntityTag,
346    mode: MutationMode,
347    batch_position: u32,
348) -> MutationDiagnosticContext {
349    MutationDiagnosticContext::new(
350        entity_tag.value(),
351        diagnostic_mutation_operation(mode),
352        batch_position,
353    )
354}
355
356const fn dynamic_write_context(operation_timestamp: Timestamp) -> AcceptedWriteContext {
357    AcceptedWriteContext::new(operation_timestamp)
358}
359
360fn insert_key_exists_after_generation(identity_generated: bool) -> InternalError {
361    if identity_generated {
362        InternalError::identity_state_corruption()
363    } else {
364        mutation_key_exists_error()
365    }
366}
367
368fn dynamic_key(
369    entity_tag: crate::types::EntityTag,
370    key: &InputValue,
371) -> Result<DecodedDataStoreKey, InternalError> {
372    let value = key
373        .clone()
374        .try_into_runtime_non_enum()
375        .ok_or_else(InternalError::executor_unsupported)?;
376    DecodedDataStoreKey::try_from_structural_key(entity_tag, &value)
377}
378
379fn lower_dynamic_patch(
380    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
381    patch: &DynamicStructuralPatch,
382    mode: MutationMode,
383    mutation_context: MutationDiagnosticContext,
384) -> Result<AcceptedMutationIntentPatch, InternalError> {
385    let mut lowered = AcceptedMutationIntentPatch::new();
386    for (field_name, cell) in patch.fields() {
387        let slot = descriptor
388            .field_slot_index_by_name(field_name)
389            .ok_or_else(InternalError::executor_unsupported)?;
390        let field = descriptor
391            .field_for_slot_index(slot)
392            .ok_or_else(InternalError::executor_invariant)?;
393        if !matches!(cell, DynamicWriteCell::Omitted)
394            && (field.write_policy().insert_generation().is_some()
395                || field.write_policy().write_management().is_some())
396        {
397            return Err(InternalError::mutation_database_owned_field_explicit(
398                mutation_context,
399                field.field_id().get(),
400            ));
401        }
402        let slot = FieldSlot::from_validated_index(slot);
403        lowered = match cell {
404            DynamicWriteCell::Omitted => lowered,
405            DynamicWriteCell::Default => match mode {
406                MutationMode::Insert | MutationMode::Replace => {
407                    lowered.set_explicit_insert_default(slot)
408                }
409                MutationMode::Update => lowered.set_explicit_update_default(slot),
410            },
411            DynamicWriteCell::Null => lowered.set_authored(slot, InputValue::null()),
412            DynamicWriteCell::Value(value) => lowered.set_authored(slot, value.clone()),
413        };
414    }
415    Ok(lowered)
416}
417
418fn lower_dynamic_mutation_intent(
419    entity_tag: crate::types::EntityTag,
420    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
421    request: &DynamicMutation,
422    batch_position: u32,
423) -> Result<(AcceptedStructuralMutation, Option<SaveMutationKind>), InternalError> {
424    match request {
425        DynamicMutation::Insert { patch, .. } => {
426            let mode = MutationMode::Insert;
427            Ok((
428                AcceptedStructuralMutation::save(
429                    mode,
430                    AcceptedStructuralMutationTarget::ResolveFromAfterImage,
431                    lower_dynamic_patch(
432                        descriptor,
433                        patch,
434                        mode,
435                        mutation_diagnostic_context(entity_tag, mode, batch_position),
436                    )?,
437                ),
438                Some(SaveMutationKind::Insert),
439            ))
440        }
441        DynamicMutation::Update { key, patch, .. }
442        | DynamicMutation::Replace { key, patch, .. } => {
443            let mode =
444                dynamic_mutation_mode(request).ok_or_else(InternalError::executor_invariant)?;
445            let kind = match mode {
446                MutationMode::Insert => SaveMutationKind::Insert,
447                MutationMode::Replace => SaveMutationKind::Replace,
448                MutationMode::Update => SaveMutationKind::Update,
449            };
450            Ok((
451                AcceptedStructuralMutation::save(
452                    mode,
453                    AcceptedStructuralMutationTarget::expected(dynamic_key(entity_tag, key)?),
454                    lower_dynamic_patch(
455                        descriptor,
456                        patch,
457                        mode,
458                        mutation_diagnostic_context(entity_tag, mode, batch_position),
459                    )?,
460                ),
461                Some(kind),
462            ))
463        }
464        DynamicMutation::Delete { key, .. } => Ok((
465            AcceptedStructuralMutation::delete(dynamic_key(entity_tag, key)?),
466            None,
467        )),
468    }
469}
470
471fn lower_typed_patch(
472    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
473    binding: &DynamicTypedEntityBinding,
474    patch: &DynamicTypedStructuralPatch,
475    mode: MutationMode,
476    mutation_context: MutationDiagnosticContext,
477) -> Result<AcceptedMutationIntentPatch, InternalError> {
478    let mut lowered = AcceptedMutationIntentPatch::new();
479    for (descriptor_ordinal, cell) in patch.fields() {
480        let (field_id, slot) = binding
481            .field_identity_binding(*descriptor_ordinal)
482            .ok_or_else(InternalError::store_invariant)?;
483        let slot_index = usize::from(slot);
484        let field = descriptor
485            .field_for_slot_index(slot_index)
486            .ok_or_else(InternalError::store_invariant)?;
487        if field.field_id().get() != field_id {
488            return Err(InternalError::store_invariant());
489        }
490        if !matches!(cell, DynamicWriteCell::Omitted)
491            && (field.write_policy().insert_generation().is_some()
492                || field.write_policy().write_management().is_some())
493        {
494            return Err(InternalError::mutation_database_owned_field_explicit(
495                mutation_context,
496                field.field_id().get(),
497            ));
498        }
499        let slot = FieldSlot::from_validated_index(slot_index);
500        lowered = match cell {
501            DynamicWriteCell::Omitted => lowered,
502            DynamicWriteCell::Default => match mode {
503                MutationMode::Insert | MutationMode::Replace => {
504                    lowered.set_explicit_insert_default(slot)
505                }
506                MutationMode::Update => lowered.set_explicit_update_default(slot),
507            },
508            DynamicWriteCell::Null => lowered.set_authored(slot, InputValue::null()),
509            DynamicWriteCell::Value(value) => lowered.set_authored(slot, value.clone()),
510        };
511    }
512    Ok(lowered)
513}
514
515fn lower_typed_mutation_intent(
516    entity_tag: crate::types::EntityTag,
517    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
518    binding: &DynamicTypedEntityBinding,
519    request: &DynamicTypedMutation,
520    batch_position: u32,
521) -> Result<Option<(AcceptedStructuralMutation, Option<SaveMutationKind>)>, InternalError> {
522    let (target, patch) = match request {
523        DynamicTypedMutation::Insert { patch } => (
524            AcceptedStructuralMutationTarget::ResolveFromAfterImage,
525            patch,
526        ),
527        DynamicTypedMutation::Update { key, patch }
528        | DynamicTypedMutation::Replace { key, patch } => (
529            AcceptedStructuralMutationTarget::expected(dynamic_key(entity_tag, key)?),
530            patch,
531        ),
532        DynamicTypedMutation::Delete { key } => {
533            return Ok(Some((
534                AcceptedStructuralMutation::delete(dynamic_key(entity_tag, key)?),
535                None,
536            )));
537        }
538    };
539    if !patch.is_bound_to(binding) {
540        return Ok(None);
541    }
542    let mode =
543        dynamic_typed_mutation_mode(request).ok_or_else(InternalError::executor_invariant)?;
544    let patch = lower_typed_patch(
545        descriptor,
546        binding,
547        patch,
548        mode,
549        mutation_diagnostic_context(entity_tag, mode, batch_position),
550    )?;
551    Ok(Some((
552        AcceptedStructuralMutation::save(mode, target, patch),
553        Some(save_mutation_kind(mode)),
554    )))
555}
556
557fn preserve_dynamic_replacement_identity(
558    key: &DecodedDataStoreKey,
559    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
560    mut patch: AcceptedMutationIntentPatch,
561) -> Result<AcceptedMutationIntentPatch, InternalError> {
562    let primary_key_slots = descriptor.primary_key_slot_indices();
563    let runtime_key = key.primary_key_runtime_value();
564    let components = match runtime_key {
565        Value::List(values) if primary_key_slots.len() > 1 => values,
566        value if primary_key_slots.len() == 1 => vec![value],
567        _ => return Err(InternalError::executor_invariant()),
568    };
569    if components.len() != primary_key_slots.len() {
570        return Err(InternalError::executor_invariant());
571    }
572
573    for (slot, value) in primary_key_slots.iter().copied().zip(components) {
574        let _ = descriptor
575            .field_for_slot_index(slot)
576            .ok_or_else(InternalError::executor_invariant)?;
577        let has_explicit_intent = patch
578            .entries()
579            .iter()
580            .any(|entry| entry.slot().index() == slot);
581        if has_explicit_intent {
582            continue;
583        }
584        let value = InputValue::try_from_runtime_non_enum(&value)
585            .ok_or_else(InternalError::executor_invariant)?;
586        patch =
587            patch.set_preserved_replacement_identity(FieldSlot::from_validated_index(slot), value);
588    }
589
590    Ok(patch)
591}
592
593// Locate the sole accepted Identity owner that is eligible to resolve a
594// keyless insert. Accepted-schema integrity already freezes the exact shape;
595// this runtime check fails closed if a malformed contract reaches execution.
596fn accepted_identity_insert_field(
597    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
598) -> Result<Option<AcceptedIdentityInsertField>, InternalError> {
599    let mut identity = None;
600    for field in descriptor.fields() {
601        if field.write_policy().insert_generation() != Some(FieldInsertGeneration::Identity) {
602            continue;
603        }
604        let field_slot = usize::from(field.slot().get());
605        if identity
606            .replace(AcceptedIdentityInsertField {
607                field_id: field.field_id(),
608                field_slot,
609                accepted_kind: field.kind().clone(),
610            })
611            .is_some()
612            || descriptor.primary_key_slot_indices() != [field_slot]
613        {
614            return Err(InternalError::identity_corruption());
615        }
616    }
617    Ok(identity)
618}
619
620fn checked_pre_key_candidate_count(count: usize) -> Result<u32, InternalError> {
621    u32::try_from(count).map_err(|_| InternalError::identity_candidate_count_exhausted())
622}
623
624fn validate_identity_materialization(
625    entity_tag: crate::types::EntityTag,
626    identity_field: &AcceptedIdentityInsertField,
627    candidate: &AcceptedPreKeyInsert,
628    allocation: &AcceptedIdentityAllocation,
629    data_key: &DecodedDataStoreKey,
630    reader: &StructuralSlotReader<'_>,
631) -> Result<(), InternalError> {
632    let owner = allocation.owner();
633    let slot_value = reader.required_cached_value(identity_field.field_slot)?;
634    if candidate.entity_tag() != entity_tag
635        || candidate.input_ordinal() != allocation.input_ordinal()
636        || owner.entity_tag() != entity_tag
637        || owner.field_id() != identity_field.field_id
638        || allocation.field_slot() != identity_field.field_slot
639        || slot_value != allocation.value()
640        || data_key.primary_key_runtime_value() != *allocation.value()
641    {
642        return Err(InternalError::identity_corruption());
643    }
644    Ok(())
645}
646
647fn data_key_from_row(
648    entity_tag: crate::types::EntityTag,
649    contract: &StructuralRowContract,
650    row: &RawRow,
651) -> Result<DecodedDataStoreKey, InternalError> {
652    let reader =
653        StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(row, contract)?;
654    let values = contract
655        .primary_key_slot_indices()
656        .iter()
657        .map(|slot| reader.required_cached_value(*slot).cloned())
658        .collect::<Result<Vec<_>, _>>()?;
659    let value = match values.as_slice() {
660        [value] => value.clone(),
661        _ => Value::List(values),
662    };
663    DecodedDataStoreKey::try_from_structural_key(entity_tag, &value)
664}
665
666#[cfg(feature = "sql")]
667pub(in crate::db::session) fn structural_data_key_from_runtime_values(
668    entity_tag: crate::types::EntityTag,
669    values: Vec<Value>,
670) -> Result<DecodedDataStoreKey, InternalError> {
671    let value = match values.as_slice() {
672        [value] => value.clone(),
673        _ => Value::List(values),
674    };
675    DecodedDataStoreKey::try_from_structural_key(entity_tag, &value)
676}
677
678fn validated_existing_row(
679    store: crate::db::registry::StoreHandle,
680    data_key: &DecodedDataStoreKey,
681    contract: &StructuralRowContract,
682) -> Result<Option<RawRow>, InternalError> {
683    let raw_key = data_key.to_raw()?;
684    let row = store.with_data(|data| data.get(&raw_key));
685    if let Some(row) = row.as_ref() {
686        let reader =
687            StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(row, contract)?;
688        reader.validate_primary_key(data_key)?;
689    }
690    Ok(row)
691}
692
693fn prepare_dynamic_mutation_result(
694    catalog: &AcceptedSchemaCatalogContext,
695    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
696    rows: Vec<AcceptedStructuralMutationRow>,
697    enforce_mixed_batch_result_bound: bool,
698) -> Result<DynamicMutationResult, InternalError> {
699    let affected_rows = rows.iter().try_fold(0_u32, |total, row| {
700        total
701            .checked_add(u32::from(row.logical_changed()))
702            .ok_or_else(InternalError::executor_invariant)
703    })?;
704    let columns = descriptor
705        .fields()
706        .iter()
707        .map(|field| field.name().to_string())
708        .collect();
709    let rows = rows
710        .into_iter()
711        .map(|row| {
712            row.values
713                .iter()
714                .map(|value| {
715                    output_value_from_runtime(catalog.enum_catalog(), value)
716                        .map_err(|_| InternalError::store_invariant())
717                })
718                .collect::<Result<Vec<_>, _>>()
719        })
720        .collect::<Result<Vec<_>, _>>()?;
721    let result = DynamicMutationResult {
722        entity: catalog.snapshot().entity_name().to_string(),
723        columns,
724        rows,
725        affected_rows,
726    };
727    if enforce_mixed_batch_result_bound {
728        let encoded =
729            candid::encode_one(&result).map_err(|_| InternalError::executor_invariant())?;
730        validate_structural_mutation_result_bytes(encoded.len())?;
731    }
732    Ok(result)
733}
734
735fn typed_descriptor_field_type(
736    field_type: TypedFieldType,
737) -> Result<FieldType, DynamicTypedBindingError> {
738    match field_type {
739        TypedFieldType::Scalar(scalar) => Ok(FieldType::Scalar(scalar)),
740        TypedFieldType::List(item) => Ok(FieldType::List(Box::new(typed_descriptor_field_type(
741            *item,
742        )?))),
743        TypedFieldType::Named(source_key) => TypeSourceKey::try_new(source_key.to_string())
744            .map(FieldType::Named)
745            .map_err(|_| DynamicTypedBindingError::FieldUnavailable),
746    }
747}
748
749fn typed_adapter_field_kind_matches(
750    accepted: &AcceptedFieldKind,
751    expected: &AcceptedFieldKind,
752) -> bool {
753    if accepted == expected {
754        return true;
755    }
756    match (accepted, expected) {
757        (AcceptedFieldKind::Relation { key_kind, .. }, expected) => {
758            typed_adapter_field_kind_matches(key_kind, expected)
759        }
760        (AcceptedFieldKind::List(accepted), AcceptedFieldKind::List(expected)) => {
761            typed_adapter_field_kind_matches(accepted, expected)
762        }
763        _ => false,
764    }
765}
766
767impl<C: CanisterKind> DbSession<C> {
768    /// Issue one opaque accepted binding for immutable generated source keys.
769    pub fn issue_typed_entity_binding(
770        &self,
771        descriptor: &TypedEntityDescriptor,
772    ) -> Result<DynamicTypedEntityBinding, DynamicTypedBindingError> {
773        let entity_source = EntitySourceKey::try_new(descriptor.entity_source_key)
774            .map_err(|_| DynamicTypedBindingError::FieldUnavailable)?;
775        let catalog = self
776            .find_accepted_schema_catalog_context_for_entity_source_key(entity_source.as_str())?
777            .ok_or(DynamicTypedBindingError::FieldUnavailable)?;
778        let identity = catalog.identity();
779        if identity.entity_path() != entity_source.as_str() {
780            return Err(InternalError::store_invariant().into());
781        }
782        let store = self.db.recovered_store(identity.store_path())?;
783        let bundle = store
784            .with_schema(crate::db::schema::SchemaStore::current_accepted_schema_bundle)?
785            .ok_or_else(InternalError::store_invariant)?;
786        let entity_tag = identity.entity_tag();
787        if bundle.source_bindings().entity(&entity_source) != Some(entity_tag)
788            || bundle.revision() != catalog.revision()
789        {
790            return Err(InternalError::store_invariant().into());
791        }
792        let snapshot = bundle
793            .entity_snapshots()
794            .get(&entity_tag)
795            .ok_or_else(InternalError::store_invariant)?;
796        if descriptor.primary_key_source_keys.len() != snapshot.primary_key_field_ids().len() {
797            return Err(DynamicTypedBindingError::IncompatibleField);
798        }
799        for (source_key, accepted_field_id) in descriptor
800            .primary_key_source_keys
801            .iter()
802            .zip(snapshot.primary_key_field_ids())
803        {
804            let source = FieldSourceKey::try_new((*source_key).to_string())
805                .map_err(|_| DynamicTypedBindingError::FieldUnavailable)?;
806            let descriptor_field_id = bundle
807                .source_bindings()
808                .field(entity_tag, &source)
809                .ok_or(DynamicTypedBindingError::FieldUnavailable)?;
810            if descriptor_field_id != *accepted_field_id {
811                return Err(DynamicTypedBindingError::IncompatibleField);
812            }
813        }
814        let row_contract = catalog.inspection_plan().row_contract();
815        let mut fields = Vec::with_capacity(descriptor.fields.len());
816        for field_descriptor in descriptor.fields {
817            let source = FieldSourceKey::try_new(field_descriptor.source_key.to_string())
818                .map_err(|_| DynamicTypedBindingError::FieldUnavailable)?;
819            let field_id = bundle
820                .source_bindings()
821                .field(entity_tag, &source)
822                .ok_or(DynamicTypedBindingError::FieldUnavailable)?;
823            let field = snapshot
824                .fields()
825                .iter()
826                .find(|field| field.id() == field_id)
827                .ok_or_else(InternalError::store_invariant)?;
828            let runtime_field =
829                row_contract.required_accepted_field_contract(usize::from(field.slot().get()))?;
830            if runtime_field.field_id() != field_id {
831                return Err(InternalError::store_invariant().into());
832            }
833            let field_type = typed_descriptor_field_type(field_descriptor.field_type)?;
834            let expected_kind = lower_field_type(&field_type, bundle.source_bindings())
835                .map_err(|_| DynamicTypedBindingError::IncompatibleField)?;
836            if field.nullable() != field_descriptor.nullable
837                || !typed_adapter_field_kind_matches(field.kind(), &expected_kind)
838            {
839                return Err(DynamicTypedBindingError::IncompatibleField);
840            }
841            fields.push((
842                source.as_str().to_string(),
843                field_id.get(),
844                field.slot().get(),
845                field.name().to_string(),
846            ));
847        }
848        let adapter_names = bundle.typed_adapter_names()?;
849
850        DynamicTypedEntityBinding::new(
851            database_incarnation_id()?.to_bytes(),
852            entity_source.as_str().to_string(),
853            snapshot.entity_name().to_string(),
854            entity_tag.value(),
855            catalog.revision().get(),
856            catalog.fingerprint(),
857            row_contract.current_layout_version().get(),
858            fields,
859            adapter_names.named_types,
860            adapter_names.enum_variants,
861            adapter_names.composite_fields,
862        )
863        .map_err(Into::into)
864    }
865
866    pub(in crate::db::session) fn current_typed_entity_binding_catalog(
867        &self,
868        binding: &DynamicTypedEntityBinding,
869    ) -> Result<Option<AcceptedSchemaCatalogContext>, InternalError> {
870        if database_incarnation_id()?.to_bytes() != binding.database_incarnation {
871            return Ok(None);
872        }
873        let Some(catalog) = self.find_accepted_schema_catalog_context_for_entity_source_key(
874            binding.entity_source.as_str(),
875        )?
876        else {
877            return Ok(None);
878        };
879        self.typed_entity_binding_matches_catalog(binding, &catalog)
880            .map(|current| current.then_some(catalog))
881    }
882
883    fn typed_entity_binding_matches_catalog(
884        &self,
885        binding: &DynamicTypedEntityBinding,
886        catalog: &AcceptedSchemaCatalogContext,
887    ) -> Result<bool, InternalError> {
888        if database_incarnation_id()?.to_bytes() != binding.database_incarnation {
889            return Ok(false);
890        }
891        let row_contract = catalog.inspection_plan().row_contract();
892        let identity = catalog.identity();
893        if identity.entity_path() != binding.entity_source.as_str()
894            || identity.entity_tag().value() != binding.entity_tag
895            || catalog.revision().get() != binding.accepted_revision
896            || catalog.fingerprint() != binding.accepted_fingerprint
897            || row_contract.current_layout_version().get() != binding.entity_generation
898        {
899            return Ok(false);
900        }
901        let entity_source = EntitySourceKey::try_new(binding.entity_source.clone())
902            .map_err(|_| InternalError::store_invariant())?;
903        let store = self.db.recovered_store(identity.store_path())?;
904        let bundle = store
905            .with_schema(crate::db::schema::SchemaStore::current_accepted_schema_bundle)?
906            .ok_or_else(InternalError::store_invariant)?;
907        if bundle.revision() != catalog.revision()
908            || bundle.source_bindings().entity(&entity_source) != Some(identity.entity_tag())
909        {
910            return Ok(false);
911        }
912        let snapshot = bundle
913            .entity_snapshots()
914            .get(&identity.entity_tag())
915            .ok_or_else(InternalError::store_invariant)?;
916        for (source_key, expected_field_id, expected_slot) in binding.field_identity_bindings() {
917            let source = FieldSourceKey::try_new(source_key)
918                .map_err(|_| InternalError::store_invariant())?;
919            let Some(field_id) = bundle
920                .source_bindings()
921                .field(identity.entity_tag(), &source)
922            else {
923                return Ok(false);
924            };
925            let Some(field) = snapshot
926                .fields()
927                .iter()
928                .find(|field| field.id() == field_id)
929            else {
930                return Err(InternalError::store_invariant());
931            };
932            if field_id.get() != expected_field_id || field.slot().get() != expected_slot {
933                return Ok(false);
934            }
935        }
936        Ok(true)
937    }
938
939    /// Verify that an opaque typed binding still names the exact accepted authority.
940    pub fn typed_entity_binding_is_current(
941        &self,
942        binding: &DynamicTypedEntityBinding,
943    ) -> Result<bool, InternalError> {
944        self.current_typed_entity_binding_catalog(binding)
945            .map(|catalog| catalog.is_some())
946    }
947
948    /// Materialize one accepted delete batch, run bounded frontend validation,
949    /// then commit it atomically.
950    #[cfg(feature = "sql")]
951    pub(in crate::db::session) fn execute_accepted_structural_delete_batch(
952        &self,
953        catalog: &AcceptedSchemaCatalogContext,
954        _descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
955        keys: Vec<DecodedDataStoreKey>,
956        precommit_validation: impl FnOnce(&[Vec<Value>]) -> Result<(), InternalError>,
957    ) -> Result<Vec<Vec<Value>>, InternalError> {
958        let mutations = keys
959            .into_iter()
960            .map(AcceptedStructuralMutation::delete)
961            .collect::<Vec<_>>();
962        let mutation_capacity = mutations.len();
963        let mut mutations = mutations.into_iter();
964        self.execute_accepted_structural_mutation_batch_inner(
965            catalog,
966            mutation_capacity,
967            0,
968            || {
969                Ok(mutations
970                    .next()
971                    .map(|mutation| AcceptedStructuralMutationBatchItem {
972                        catalog: catalog.clone(),
973                        mutation,
974                    }))
975            },
976            Timestamp::now(),
977            AcceptedStructuralMutationCommitOptions::standard(),
978            |rows, _report| {
979                let rows = rows
980                    .into_iter()
981                    .map(AcceptedStructuralMutationRow::into_values)
982                    .collect::<Vec<_>>();
983                precommit_validation(rows.as_slice())?;
984                Ok((rows, AcceptedStructuralMutationCommitDirective::Standard))
985            },
986        )
987    }
988
989    /// Materialize one accepted structural batch, let its caller prepare and
990    /// validate the final after-images, then commit atomically.
991    ///
992    /// The caller freezes one operation timestamp and supplies frontend-lowered
993    /// intent only. Accepted defaults, generated values, managed timestamps,
994    /// constraints, relations, row encoding, and commit preparation remain
995    /// owned by this database boundary.
996    pub(in crate::db::session) fn execute_accepted_structural_save_batch<T>(
997        &self,
998        catalog: &AcceptedSchemaCatalogContext,
999        _descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
1000        mutations: Vec<AcceptedStructuralMutation>,
1001        operation_timestamp: Timestamp,
1002        precommit_preparation: impl FnOnce(
1003            Vec<AcceptedStructuralMutationRow>,
1004        ) -> Result<T, InternalError>,
1005    ) -> Result<T, InternalError> {
1006        let mutation_capacity = mutations.len();
1007        let identity_candidate_count = mutations
1008            .iter()
1009            .filter(|mutation| {
1010                matches!(
1011                    mutation,
1012                    AcceptedStructuralMutation::Save {
1013                        mode: MutationMode::Insert,
1014                        target: AcceptedStructuralMutationTarget::ResolveFromAfterImage,
1015                        ..
1016                    }
1017                )
1018            })
1019            .count();
1020        let mut mutations = mutations.into_iter();
1021        self.execute_accepted_structural_mutation_batch_inner(
1022            catalog,
1023            mutation_capacity,
1024            identity_candidate_count,
1025            || {
1026                Ok(mutations
1027                    .next()
1028                    .map(|mutation| AcceptedStructuralMutationBatchItem {
1029                        catalog: catalog.clone(),
1030                        mutation,
1031                    }))
1032            },
1033            operation_timestamp,
1034            AcceptedStructuralMutationCommitOptions::standard(),
1035            |rows, _report| {
1036                precommit_preparation(rows).map(|prepared| {
1037                    (
1038                        prepared,
1039                        AcceptedStructuralMutationCommitDirective::Standard,
1040                    )
1041                })
1042            },
1043        )
1044    }
1045
1046    /// Commit one complete accepted update page and its exact durable progress successor.
1047    #[cfg(test)]
1048    pub(in crate::db::session) fn execute_accepted_structural_update_with_mutation_progress(
1049        &self,
1050        catalog: &AcceptedSchemaCatalogContext,
1051        _descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
1052        mutations: Vec<AcceptedStructuralMutation>,
1053        operation_timestamp: Timestamp,
1054        mutation_progress: MutationProgressRecordOp,
1055    ) -> Result<usize, InternalError> {
1056        let mutation_capacity = mutations.len();
1057        let mut mutations = mutations.into_iter();
1058        self.execute_accepted_structural_mutation_batch_inner(
1059            catalog,
1060            mutation_capacity,
1061            0,
1062            || {
1063                Ok(mutations
1064                    .next()
1065                    .map(|mutation| AcceptedStructuralMutationBatchItem {
1066                        catalog: catalog.clone(),
1067                        mutation,
1068                    }))
1069            },
1070            operation_timestamp,
1071            AcceptedStructuralMutationCommitOptions::with_mutation_progress(),
1072            |rows, _report| {
1073                Ok((
1074                    rows.len(),
1075                    AcceptedStructuralMutationCommitDirective::WithMutationProgress(
1076                        mutation_progress,
1077                    ),
1078                ))
1079            },
1080        )
1081    }
1082
1083    /// Pack a checkpoint-aware update prefix using the writer's exact staging
1084    /// charge, then apply the caller's atomic commit decision.
1085    #[cfg(any(feature = "sql", test))]
1086    pub(in crate::db::session) fn execute_accepted_structural_update_bounded_prefix<T>(
1087        &self,
1088        catalog: &AcceptedSchemaCatalogContext,
1089        _descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
1090        mutation_capacity: usize,
1091        mut next_mutation: impl FnMut() -> Result<Option<AcceptedStructuralMutation>, InternalError>,
1092        operation_timestamp: Timestamp,
1093        precommit_preparation: impl FnOnce(
1094            AcceptedStructuralMutationPackingReport,
1095        ) -> Result<
1096            (T, AcceptedStructuralMutationCommitDirective),
1097            InternalError,
1098        >,
1099    ) -> Result<T, InternalError> {
1100        self.execute_accepted_structural_mutation_batch_inner(
1101            catalog,
1102            mutation_capacity,
1103            0,
1104            || {
1105                next_mutation().map(|mutation| {
1106                    mutation.map(|mutation| AcceptedStructuralMutationBatchItem {
1107                        catalog: catalog.clone(),
1108                        mutation,
1109                    })
1110                })
1111            },
1112            operation_timestamp,
1113            AcceptedStructuralMutationCommitOptions::bounded_prefix(),
1114            |rows, report| {
1115                if rows.len() != report.admitted_mutations() {
1116                    return Err(InternalError::executor_invariant());
1117                }
1118                precommit_preparation(report)
1119            },
1120        )
1121    }
1122
1123    #[expect(
1124        clippy::too_many_arguments,
1125        clippy::too_many_lines,
1126        reason = "one phased owner keeps accepted authority, mutation context, precommit preparation, output capture, and commit staging inseparable"
1127    )]
1128    fn execute_accepted_structural_mutation_batch_inner<T>(
1129        &self,
1130        anchor_catalog: &AcceptedSchemaCatalogContext,
1131        mutation_capacity: usize,
1132        identity_candidate_count: usize,
1133        mut next_mutation: impl FnMut() -> Result<
1134            Option<AcceptedStructuralMutationBatchItem>,
1135            InternalError,
1136        >,
1137        operation_timestamp: Timestamp,
1138        options: AcceptedStructuralMutationCommitOptions,
1139        precommit_preparation: impl FnOnce(
1140            Vec<AcceptedStructuralMutationRow>,
1141            AcceptedStructuralMutationPackingReport,
1142        ) -> Result<
1143            (T, AcceptedStructuralMutationCommitDirective),
1144            InternalError,
1145        >,
1146    ) -> Result<T, InternalError> {
1147        let AcceptedStructuralMutationCommitOptions {
1148            capture_output_values,
1149            packing,
1150        } = options;
1151        let anchor_identity = anchor_catalog.identity();
1152        let accepted_root_identity = anchor_catalog.runtime_root_identity();
1153        let store_path = anchor_identity.store_path();
1154        let store = self.db.recovered_store(store_path)?;
1155        let write_context = dynamic_write_context(operation_timestamp);
1156        if mutation_capacity > MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS {
1157            return Err(InternalError::mutation_batch_too_many_items(
1158                mutation_capacity,
1159                MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
1160            ));
1161        }
1162        let _ = checked_pre_key_candidate_count(identity_candidate_count)?;
1163        let mut entity_states: Vec<AcceptedStructuralMutationEntityState> = Vec::new();
1164        let mut scheduler = AcceptedMutationConstraintScheduler::new(mutation_capacity);
1165        let mut output = Vec::with_capacity(mutation_capacity);
1166        let mut staged_bytes = 0_usize;
1167        let mut stopped_before_candidate = false;
1168        let mut candidate_exceeds_batch_policy = false;
1169        let mut input_index = 0_usize;
1170
1171        while let Some(item) = next_mutation()? {
1172            if input_index >= mutation_capacity {
1173                return Err(InternalError::mutation_batch_too_many_items(
1174                    input_index.saturating_add(1),
1175                    mutation_capacity,
1176                ));
1177            }
1178            let batch_input_ordinal = u32::try_from(input_index).map_err(|_| {
1179                InternalError::mutation_batch_too_many_items(
1180                    mutation_capacity,
1181                    MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
1182                )
1183            })?;
1184            input_index = input_index.saturating_add(1);
1185            let catalog = &item.catalog;
1186            let identity = catalog.identity();
1187            if catalog.runtime_root_identity() != accepted_root_identity
1188                || identity.store_path() != store_path
1189            {
1190                return Err(InternalError::query_executor_invariant());
1191            }
1192            let descriptor =
1193                AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())?;
1194            let row_decode_contract =
1195                descriptor.row_decode_contract(catalog.value_catalog_handle().clone());
1196            let entity_path = identity.entity_path();
1197            let row_contract = StructuralRowContract::from_accepted_decode_contract(
1198                entity_path,
1199                row_decode_contract.clone(),
1200            );
1201            let entity_state_index = entity_states
1202                .iter()
1203                .position(|state| state.entity_tag == identity.entity_tag());
1204            let entity_state_index = if let Some(index) = entity_state_index {
1205                index
1206            } else {
1207                if entity_states.len() >= MAX_STRUCTURAL_MUTATION_BATCH_ENTITIES {
1208                    return Err(InternalError::mutation_batch_too_many_entities(
1209                        entity_states.len().saturating_add(1),
1210                        MAX_STRUCTURAL_MUTATION_BATCH_ENTITIES,
1211                    ));
1212                }
1213                let identity_field = accepted_identity_insert_field(&descriptor)?;
1214                let identity_incarnation = identity_field
1215                    .as_ref()
1216                    .map(|_| database_incarnation_id())
1217                    .transpose()?;
1218                entity_states.push(AcceptedStructuralMutationEntityState {
1219                    entity_tag: identity.entity_tag(),
1220                    identity_field,
1221                    identity_incarnation,
1222                    identity_cursor: None,
1223                    identity_insert_ordinal: 0,
1224                });
1225                entity_states.len().saturating_sub(1)
1226            };
1227            let identity_field = entity_states[entity_state_index].identity_field.clone();
1228            let identity_insert_ordinal = entity_states[entity_state_index].identity_insert_ordinal;
1229            let mutation = item.mutation;
1230            let AcceptedStructuralMutation::Save {
1231                mode,
1232                target,
1233                patch: authored_patch,
1234            } = mutation
1235            else {
1236                let AcceptedStructuralMutation::Delete { key } = mutation else {
1237                    return Err(InternalError::executor_invariant());
1238                };
1239                let before = validated_existing_row(store, &key, &row_contract)?
1240                    .ok_or_else(|| InternalError::store_not_found(&key))?;
1241                let raw_key = key.to_raw()?;
1242                let canonical_before = canonical_row_from_raw_row_with_accepted_decode_contract(
1243                    entity_path,
1244                    row_decode_contract.clone(),
1245                    &before,
1246                )?;
1247                let admission = admit_structural_mutation_staged_charge(
1248                    &mut staged_bytes,
1249                    [
1250                        raw_key.as_bytes().len(),
1251                        canonical_before.as_raw_row().as_bytes().len(),
1252                    ],
1253                    packing,
1254                )?;
1255                match admission {
1256                    AcceptedStructuralMutationStagedAdmission::Admitted => {}
1257                    AcceptedStructuralMutationStagedAdmission::PageFull => {
1258                        stopped_before_candidate = true;
1259                        break;
1260                    }
1261                    AcceptedStructuralMutationStagedAdmission::CandidateExceedsPolicy => {
1262                        stopped_before_candidate = true;
1263                        candidate_exceeds_batch_policy = true;
1264                        break;
1265                    }
1266                }
1267                scheduler.schedule_delete(
1268                    entity_path,
1269                    identity.entity_tag(),
1270                    catalog.fingerprint(),
1271                    CommitRowOp::new(
1272                        entity_path,
1273                        raw_key,
1274                        Some(canonical_before.as_raw_row().as_bytes().to_vec()),
1275                        None,
1276                        catalog.fingerprint(),
1277                    ),
1278                    batch_input_ordinal,
1279                )?;
1280                let reader = StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(
1281                    canonical_before.as_raw_row(),
1282                    &row_contract,
1283                )?;
1284                let values = if capture_output_values {
1285                    let mut values = Vec::with_capacity(descriptor.fields().len());
1286                    for field in descriptor.fields() {
1287                        values.push(
1288                            reader
1289                                .required_cached_value(usize::from(field.slot().get()))?
1290                                .clone(),
1291                        );
1292                    }
1293                    values
1294                } else {
1295                    Vec::new()
1296                };
1297                output.push(AcceptedStructuralMutationRow {
1298                    values,
1299                    logical_changed: true,
1300                });
1301                continue;
1302            };
1303            let mutation_context =
1304                mutation_diagnostic_context(identity.entity_tag(), mode, batch_input_ordinal);
1305            let (expected_key, preloaded_before, pre_key_insert, mut keyed_patch) = match target {
1306                AcceptedStructuralMutationTarget::ResolveFromAfterImage => {
1307                    let candidate_ordinal =
1308                        if identity_field.is_some() && matches!(mode, MutationMode::Insert) {
1309                            identity_insert_ordinal
1310                        } else {
1311                            batch_input_ordinal
1312                        };
1313                    (
1314                        None,
1315                        None,
1316                        Some(AcceptedPreKeyInsert::new(
1317                            identity.entity_tag(),
1318                            authored_patch,
1319                            candidate_ordinal,
1320                        )),
1321                        None,
1322                    )
1323                }
1324                AcceptedStructuralMutationTarget::Expected(key) => {
1325                    (Some(*key), None, None, Some(authored_patch))
1326                }
1327                AcceptedStructuralMutationTarget::ExpectedLoaded(loaded) => {
1328                    let (key, row) = loaded.into_parts();
1329                    (Some(key), Some(row), None, Some(authored_patch))
1330                }
1331            };
1332            if matches!(mode, MutationMode::Replace)
1333                && let Some(key) = expected_key.as_ref()
1334            {
1335                let patch = keyed_patch
1336                    .take()
1337                    .ok_or_else(InternalError::executor_invariant)?;
1338                keyed_patch = Some(preserve_dynamic_replacement_identity(
1339                    key,
1340                    &descriptor,
1341                    patch,
1342                )?);
1343            }
1344            let patch = pre_key_insert
1345                .as_ref()
1346                .map(AcceptedPreKeyInsert::fields)
1347                .or(keyed_patch.as_ref())
1348                .ok_or_else(InternalError::executor_invariant)?;
1349            let before = match (expected_key.as_ref(), preloaded_before) {
1350                (Some(_), Some(row)) => Some(row),
1351                (Some(key), None) => validated_existing_row(store, key, &row_contract)?,
1352                (None, None) => None,
1353                (None, Some(_)) => return Err(InternalError::executor_invariant()),
1354            };
1355            match mode {
1356                MutationMode::Insert if before.is_some() => {
1357                    return Err(mutation_key_exists_error());
1358                }
1359                MutationMode::Update if before.is_none() => {
1360                    let key = expected_key
1361                        .as_ref()
1362                        .ok_or_else(InternalError::executor_invariant)?;
1363                    return Err(InternalError::store_not_found(key));
1364                }
1365                MutationMode::Insert | MutationMode::Replace | MutationMode::Update => {}
1366            }
1367
1368            let identity_allocation = if let Some(identity_field) = identity_field.as_ref()
1369                && matches!(mode, MutationMode::Insert)
1370                && before.is_none()
1371            {
1372                let candidate = pre_key_insert.as_ref().ok_or_else(|| {
1373                    InternalError::mutation_database_owned_field_explicit(
1374                        mutation_context,
1375                        identity_field.field_id.get(),
1376                    )
1377                })?;
1378                if entity_states[entity_state_index].identity_cursor.is_none() {
1379                    let incarnation = entity_states[entity_state_index]
1380                        .identity_incarnation
1381                        .ok_or_else(InternalError::identity_state_corruption)?;
1382                    entity_states[entity_state_index].identity_cursor =
1383                        Some(store.with_schema(|schema_store| {
1384                            schema_store.identity_statement_cursor(
1385                                incarnation,
1386                                identity.entity_tag(),
1387                                identity_field.field_id,
1388                                &identity_field.accepted_kind,
1389                            )
1390                        })?);
1391                }
1392                let allocation = entity_states[entity_state_index]
1393                    .identity_cursor
1394                    .as_mut()
1395                    .ok_or_else(InternalError::identity_state_corruption)?
1396                    .allocate(identity_field.field_slot, candidate.input_ordinal())?;
1397                entity_states[entity_state_index].identity_insert_ordinal = identity_insert_ordinal
1398                    .checked_add(1)
1399                    .ok_or_else(InternalError::identity_candidate_count_exhausted)?;
1400                Some(allocation)
1401            } else if let Some(identity_field) = identity_field.as_ref()
1402                && matches!(mode, MutationMode::Replace)
1403                && before.is_none()
1404            {
1405                return Err(InternalError::mutation_database_owned_field_explicit(
1406                    mutation_context,
1407                    identity_field.field_id.get(),
1408                ));
1409            } else {
1410                None
1411            };
1412
1413            let resolved = match (mode, before.as_ref()) {
1414                (MutationMode::Insert | MutationMode::Replace, None) => {
1415                    resolve_insert_structural_patch_with_accepted_contract(
1416                        entity_path,
1417                        row_decode_contract.clone(),
1418                        catalog.fingerprint(),
1419                        catalog.accepted_row_constraints(),
1420                        patch,
1421                        write_context,
1422                        mutation_context,
1423                        identity_allocation.as_ref(),
1424                    )?
1425                }
1426                (MutationMode::Update, Some(before)) => {
1427                    resolve_update_structural_patch_with_accepted_contract(
1428                        entity_path,
1429                        row_decode_contract.clone(),
1430                        catalog.fingerprint(),
1431                        catalog.accepted_row_constraints(),
1432                        before,
1433                        patch,
1434                        write_context,
1435                        mutation_context,
1436                    )?
1437                }
1438                (MutationMode::Replace, Some(before)) => {
1439                    resolve_existing_replace_structural_patch_with_accepted_contract(
1440                        entity_path,
1441                        row_decode_contract.clone(),
1442                        catalog.fingerprint(),
1443                        catalog.accepted_row_constraints(),
1444                        before,
1445                        patch,
1446                        write_context,
1447                        mutation_context,
1448                    )?
1449                }
1450                (MutationMode::Insert, Some(_)) | (MutationMode::Update, None) => {
1451                    return Err(InternalError::executor_invariant());
1452                }
1453            };
1454            let (after, provenance) = resolved.into_parts();
1455            let reader = StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(
1456                after.as_raw_row(),
1457                &row_contract,
1458            )?;
1459            let data_key = match expected_key {
1460                Some(key) => {
1461                    reader.validate_primary_key(&key)?;
1462                    key
1463                }
1464                None => {
1465                    data_key_from_row(identity.entity_tag(), &row_contract, after.as_raw_row())?
1466                }
1467            };
1468            if let Some(allocation) = identity_allocation.as_ref() {
1469                validate_identity_materialization(
1470                    identity.entity_tag(),
1471                    identity_field
1472                        .as_ref()
1473                        .ok_or_else(InternalError::identity_corruption)?,
1474                    pre_key_insert
1475                        .as_ref()
1476                        .ok_or_else(InternalError::identity_corruption)?,
1477                    allocation,
1478                    &data_key,
1479                    &reader,
1480                )?;
1481            }
1482            if matches!(mode, MutationMode::Insert)
1483                && validated_existing_row(store, &data_key, &row_contract)?.is_some()
1484            {
1485                return Err(insert_key_exists_after_generation(
1486                    identity_allocation.is_some(),
1487                ));
1488            }
1489            let raw_key = data_key.to_raw()?;
1490            let canonical_before = before
1491                .as_ref()
1492                .map(|before| {
1493                    canonical_row_from_raw_row_with_accepted_decode_contract(
1494                        entity_path,
1495                        row_decode_contract.clone(),
1496                        before,
1497                    )
1498                })
1499                .transpose()?;
1500            let logical_changed = canonical_before.as_ref().is_none_or(|before| {
1501                before.as_raw_row().as_bytes() != after.as_raw_row().as_bytes()
1502            });
1503            let physical_changed = before
1504                .as_ref()
1505                .is_none_or(|before| before.as_bytes() != after.as_raw_row().as_bytes());
1506            let admission = admit_structural_mutation_staged_charge(
1507                &mut staged_bytes,
1508                [
1509                    raw_key.as_bytes().len(),
1510                    canonical_before
1511                        .as_ref()
1512                        .map_or(0, |before| before.as_raw_row().as_bytes().len()),
1513                    after.as_raw_row().as_bytes().len(),
1514                ],
1515                packing,
1516            )?;
1517            match admission {
1518                AcceptedStructuralMutationStagedAdmission::Admitted => {}
1519                AcceptedStructuralMutationStagedAdmission::PageFull => {
1520                    stopped_before_candidate = true;
1521                    break;
1522                }
1523                AcceptedStructuralMutationStagedAdmission::CandidateExceedsPolicy => {
1524                    stopped_before_candidate = true;
1525                    candidate_exceeds_batch_policy = true;
1526                    break;
1527                }
1528            }
1529            let row_op = physical_changed.then(|| {
1530                CommitRowOp::new(
1531                    entity_path,
1532                    raw_key.clone(),
1533                    canonical_before
1534                        .as_ref()
1535                        .map(|before| before.as_raw_row().as_bytes().to_vec()),
1536                    Some(after.as_raw_row().as_bytes().to_vec()),
1537                    catalog.fingerprint(),
1538                )
1539            });
1540            scheduler.schedule_save_after_image(
1541                AcceptedMutationConstraintContext {
1542                    entity_path,
1543                    entity_tag: identity.entity_tag(),
1544                    row_decode_contract: row_decode_contract.clone(),
1545                    schema_fingerprint: catalog.fingerprint(),
1546                    fingerprint_method: catalog.fingerprint_method_version(),
1547                    row_constraints: catalog.accepted_row_constraints(),
1548                },
1549                mode,
1550                &data_key,
1551                after.as_raw_row(),
1552                provenance.as_slice(),
1553                row_op,
1554                batch_input_ordinal,
1555            )?;
1556            let values = if capture_output_values {
1557                let mut values = Vec::with_capacity(descriptor.fields().len());
1558                for field in descriptor.fields() {
1559                    values.push(
1560                        reader
1561                            .required_cached_value(usize::from(field.slot().get()))?
1562                            .clone(),
1563                    );
1564                }
1565                values
1566            } else {
1567                Vec::new()
1568            };
1569            output.push(AcceptedStructuralMutationRow {
1570                values,
1571                logical_changed,
1572            });
1573        }
1574
1575        let report = AcceptedStructuralMutationPackingReport {
1576            admitted_mutations: output.len(),
1577            staged_bytes,
1578            stopped_before_candidate,
1579            candidate_exceeds_batch_policy,
1580        };
1581        let batch = scheduler.finish();
1582        let (prepared, commit_directive) = precommit_preparation(output, report)?;
1583        finish_current_execution_instruction_watermark()?;
1584        let mut identity_ranges = Vec::with_capacity(entity_states.len());
1585        for state in entity_states {
1586            if let Some(range) = state
1587                .identity_cursor
1588                .map(IdentityStatementCursor::into_range_advance)
1589                .transpose()?
1590                .flatten()
1591            {
1592                identity_ranges.push(range);
1593            }
1594        }
1595        if !matches!(
1596            commit_directive,
1597            AcceptedStructuralMutationCommitDirective::Skip
1598        ) && batch.is_empty()
1599            && !identity_ranges.is_empty()
1600        {
1601            return Err(InternalError::identity_corruption());
1602        }
1603        match commit_directive {
1604            AcceptedStructuralMutationCommitDirective::Skip => {}
1605            AcceptedStructuralMutationCommitDirective::Standard if batch.is_empty() => {}
1606            AcceptedStructuralMutationCommitDirective::Standard => {
1607                commit_structural_row_ops_with_window(
1608                    &self.db,
1609                    batch,
1610                    identity_ranges,
1611                    "accepted_structural_batch_apply",
1612                )?;
1613            }
1614            AcceptedStructuralMutationCommitDirective::WithMutationProgress(operation)
1615                if batch.is_empty() =>
1616            {
1617                let _ = operation;
1618                return Err(InternalError::executor_invariant());
1619            }
1620            AcceptedStructuralMutationCommitDirective::WithMutationProgress(operation) => {
1621                commit_structural_row_ops_with_mutation_progress(
1622                    &self.db,
1623                    batch,
1624                    identity_ranges,
1625                    operation,
1626                    "accepted_structural_batch_apply",
1627                )?;
1628            }
1629        }
1630        Ok(prepared)
1631    }
1632
1633    fn execute_lowered_dynamic_mutation_batch(
1634        &self,
1635        catalog: &AcceptedSchemaCatalogContext,
1636        descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
1637        mutations: Vec<AcceptedStructuralMutation>,
1638        save_kinds: Vec<Option<SaveMutationKind>>,
1639        enforce_mixed_batch_result_bound: bool,
1640    ) -> Result<DynamicMutationResult, InternalError> {
1641        let identity = catalog.identity();
1642        let entity_path = identity.entity_path_handle();
1643        let (result, metrics) = self.execute_accepted_structural_save_batch(
1644            catalog,
1645            descriptor,
1646            mutations,
1647            Timestamp::now(),
1648            |rows| {
1649                if rows.len() != save_kinds.len() {
1650                    return Err(InternalError::executor_invariant());
1651                }
1652                let metrics = rows
1653                    .iter()
1654                    .zip(save_kinds)
1655                    .filter_map(|(row, kind)| kind.map(|kind| (kind, row.logical_changed())))
1656                    .collect::<Vec<_>>();
1657                let result = prepare_dynamic_mutation_result(
1658                    catalog,
1659                    descriptor,
1660                    rows,
1661                    enforce_mixed_batch_result_bound,
1662                )?;
1663                Ok((result, metrics))
1664            },
1665        )?;
1666        for (kind, logical_changed) in metrics {
1667            record(MetricsEvent::SaveMutation {
1668                entity_path: entity_path.clone(),
1669                kind,
1670                rows_touched: u64::from(logical_changed),
1671            });
1672        }
1673        Ok(result)
1674    }
1675
1676    /// Execute one trusted entity-name-driven structural mutation.
1677    ///
1678    /// This lane resolves public values, defaults, generation, management,
1679    /// constraints, relations, and commit preparation from accepted schema.
1680    /// It never materializes a generated entity or invokes application
1681    /// validators/normalizers.
1682    pub fn execute_trusted_dynamic_mutation(
1683        &self,
1684        request: &DynamicMutation,
1685    ) -> Result<DynamicMutationResult, InternalError> {
1686        self.execute_trusted_dynamic_mutation_batch_with_result_policy(vec![request.clone()], false)
1687    }
1688
1689    /// Execute one bounded same-store structural mutation batch atomically.
1690    ///
1691    /// Every item resolves from one captured accepted root and store, shares
1692    /// one operation timestamp, and is projected to its public result before
1693    /// the commit marker can be published.
1694    pub fn execute_trusted_dynamic_mutation_batch(
1695        &self,
1696        requests: Vec<DynamicMutation>,
1697    ) -> Result<Vec<DynamicMutationResult>, InternalError> {
1698        self.execute_trusted_dynamic_mutation_batch_mixed(requests)
1699    }
1700
1701    fn execute_trusted_dynamic_mutation_batch_mixed(
1702        &self,
1703        requests: Vec<DynamicMutation>,
1704    ) -> Result<Vec<DynamicMutationResult>, InternalError> {
1705        if requests.is_empty() {
1706            return Err(InternalError::mutation_batch_empty());
1707        }
1708        if requests.len() > MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS {
1709            return Err(InternalError::mutation_batch_too_many_items(
1710                requests.len(),
1711                MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
1712            ));
1713        }
1714        let first = requests
1715            .first()
1716            .ok_or_else(InternalError::mutation_batch_empty)?;
1717        if first.entity().is_empty() {
1718            return Err(InternalError::executor_unsupported());
1719        }
1720        let anchor_catalog =
1721            self.accepted_schema_catalog_context_for_entity_name(Some(first.entity()))?;
1722        let anchor_identity = anchor_catalog.identity();
1723        let mut entity_tags = std::collections::BTreeSet::new();
1724        let mut items = Vec::with_capacity(requests.len());
1725        let mut result_catalogs = Vec::with_capacity(requests.len());
1726        let mut save_kinds = Vec::with_capacity(requests.len());
1727        let mut identity_candidate_count = 0_usize;
1728
1729        for (batch_position, request) in requests.iter().enumerate() {
1730            let batch_position = u32::try_from(batch_position).map_err(|_| {
1731                InternalError::mutation_batch_too_many_items(
1732                    requests.len(),
1733                    MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
1734                )
1735            })?;
1736            if request.entity().is_empty() {
1737                return Err(InternalError::executor_unsupported());
1738            }
1739            let item_catalog = anchor_catalog
1740                .for_entity_name(request.entity())
1741                .ok_or_else(|| InternalError::unsupported_entity_path(request.entity()))?;
1742            let item_identity = item_catalog.identity();
1743            if item_identity.store_path() != anchor_identity.store_path() {
1744                return Err(InternalError::mutation_batch_store_mismatch(
1745                    batch_position,
1746                    anchor_identity.entity_tag().value(),
1747                    item_identity.entity_tag().value(),
1748                ));
1749            }
1750            entity_tags.insert(item_identity.entity_tag());
1751            if entity_tags.len() > MAX_STRUCTURAL_MUTATION_BATCH_ENTITIES {
1752                return Err(InternalError::mutation_batch_too_many_entities(
1753                    entity_tags.len(),
1754                    MAX_STRUCTURAL_MUTATION_BATCH_ENTITIES,
1755                ));
1756            }
1757            let descriptor =
1758                AcceptedRowLayoutRuntimeContract::from_accepted_schema(item_catalog.snapshot())?;
1759            let (mutation, save_kind) = lower_dynamic_mutation_intent(
1760                item_identity.entity_tag(),
1761                &descriptor,
1762                request,
1763                batch_position,
1764            )?;
1765            if matches!(
1766                mutation,
1767                AcceptedStructuralMutation::Save {
1768                    mode: MutationMode::Insert,
1769                    target: AcceptedStructuralMutationTarget::ResolveFromAfterImage,
1770                    ..
1771                }
1772            ) {
1773                identity_candidate_count = identity_candidate_count.saturating_add(1);
1774            }
1775            result_catalogs.push(item_catalog.clone());
1776            save_kinds.push(save_kind);
1777            items.push(AcceptedStructuralMutationBatchItem {
1778                catalog: item_catalog,
1779                mutation,
1780            });
1781        }
1782
1783        self.execute_lowered_mixed_mutation_batch(
1784            &anchor_catalog,
1785            items,
1786            result_catalogs,
1787            save_kinds,
1788            identity_candidate_count,
1789        )
1790    }
1791
1792    fn execute_lowered_mixed_mutation_batch(
1793        &self,
1794        anchor_catalog: &AcceptedSchemaCatalogContext,
1795        items: Vec<AcceptedStructuralMutationBatchItem>,
1796        result_catalogs: Vec<AcceptedSchemaCatalogContext>,
1797        save_kinds: Vec<Option<SaveMutationKind>>,
1798        identity_candidate_count: usize,
1799    ) -> Result<Vec<DynamicMutationResult>, InternalError> {
1800        if items.len() != result_catalogs.len() || items.len() != save_kinds.len() {
1801            return Err(InternalError::executor_invariant());
1802        }
1803        let mutation_count = items.len();
1804        let mut items = items.into_iter();
1805        let result_entity_paths = result_catalogs
1806            .iter()
1807            .map(|catalog| catalog.identity().entity_path_handle())
1808            .collect::<Vec<_>>();
1809        let (results, metrics) = self.execute_accepted_structural_mutation_batch_inner(
1810            anchor_catalog,
1811            mutation_count,
1812            identity_candidate_count,
1813            || Ok(items.next()),
1814            Timestamp::now(),
1815            AcceptedStructuralMutationCommitOptions::standard(),
1816            |rows, _report| {
1817                if rows.len() != result_catalogs.len() {
1818                    return Err(InternalError::executor_invariant());
1819                }
1820                let mut results = Vec::with_capacity(rows.len());
1821                let mut metrics = Vec::with_capacity(rows.len());
1822                for (((row, catalog), entity_path), save_kind) in rows
1823                    .into_iter()
1824                    .zip(result_catalogs.iter())
1825                    .zip(result_entity_paths.iter())
1826                    .zip(save_kinds)
1827                {
1828                    let logical_changed = row.logical_changed();
1829                    let descriptor =
1830                        AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())?;
1831                    results.push(prepare_dynamic_mutation_result(
1832                        catalog,
1833                        &descriptor,
1834                        vec![row],
1835                        false,
1836                    )?);
1837                    if let Some(kind) = save_kind {
1838                        metrics.push((entity_path.clone(), kind, logical_changed));
1839                    }
1840                }
1841                let encoded = candid::encode_one(&results)
1842                    .map_err(|_| InternalError::executor_invariant())?;
1843                validate_structural_mutation_result_bytes(encoded.len())?;
1844                Ok((
1845                    (results, metrics),
1846                    AcceptedStructuralMutationCommitDirective::Standard,
1847                ))
1848            },
1849        )?;
1850        for (entity_path, kind, logical_changed) in metrics {
1851            record(MetricsEvent::SaveMutation {
1852                entity_path,
1853                kind,
1854                rows_touched: u64::from(logical_changed),
1855            });
1856        }
1857        Ok(results)
1858    }
1859
1860    fn execute_trusted_dynamic_mutation_batch_with_result_policy(
1861        &self,
1862        requests: Vec<DynamicMutation>,
1863        enforce_mixed_batch_result_bound: bool,
1864    ) -> Result<DynamicMutationResult, InternalError> {
1865        if requests.is_empty() {
1866            return Err(InternalError::mutation_batch_empty());
1867        }
1868        if requests.len() > MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS {
1869            return Err(InternalError::mutation_batch_too_many_items(
1870                requests.len(),
1871                MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
1872            ));
1873        }
1874        let first = requests
1875            .first()
1876            .ok_or_else(InternalError::mutation_batch_empty)?;
1877        if first.entity().is_empty() {
1878            return Err(InternalError::executor_unsupported());
1879        }
1880        let catalog = self.accepted_schema_catalog_context_for_entity_name(Some(first.entity()))?;
1881        let accepted_identity = catalog.identity();
1882        let descriptor =
1883            AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())?;
1884        let mut mutations = Vec::with_capacity(requests.len());
1885        let mut save_kinds = Vec::with_capacity(requests.len());
1886
1887        for (batch_position, request) in requests.iter().enumerate() {
1888            let batch_position = u32::try_from(batch_position).map_err(|_| {
1889                InternalError::mutation_batch_too_many_items(
1890                    requests.len(),
1891                    MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
1892                )
1893            })?;
1894            if request.entity().is_empty() {
1895                return Err(InternalError::executor_unsupported());
1896            }
1897            let item_catalog =
1898                self.accepted_schema_catalog_context_for_entity_name(Some(request.entity()))?;
1899            if item_catalog.identity() != accepted_identity {
1900                return Err(InternalError::query_executor_invariant());
1901            }
1902            let (mutation, save_kind) = lower_dynamic_mutation_intent(
1903                accepted_identity.entity_tag(),
1904                &descriptor,
1905                request,
1906                batch_position,
1907            )?;
1908            mutations.push(mutation);
1909            save_kinds.push(save_kind);
1910        }
1911
1912        self.execute_lowered_dynamic_mutation_batch(
1913            &catalog,
1914            &descriptor,
1915            mutations,
1916            save_kinds,
1917            enforce_mixed_batch_result_bound,
1918        )
1919    }
1920
1921    /// Execute one generated typed write through immutable accepted entity and
1922    /// field identities. `None` means the opaque binding is stale.
1923    #[doc(hidden)]
1924    pub fn execute_trusted_typed_mutation(
1925        &self,
1926        binding: &DynamicTypedEntityBinding,
1927        request: &DynamicTypedMutation,
1928    ) -> Result<Option<DynamicMutationResult>, InternalError> {
1929        let Some(catalog) = self.current_typed_entity_binding_catalog(binding)? else {
1930            return Ok(None);
1931        };
1932        let identity = catalog.identity();
1933        let descriptor =
1934            AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())?;
1935        let Some((mutation, save_kind)) =
1936            lower_typed_mutation_intent(identity.entity_tag(), &descriptor, binding, request, 0)?
1937        else {
1938            return Ok(None);
1939        };
1940        self.execute_lowered_dynamic_mutation_batch(
1941            &catalog,
1942            &descriptor,
1943            vec![mutation],
1944            vec![save_kind],
1945            false,
1946        )
1947        .map(Some)
1948    }
1949
1950    /// Execute one bounded same-entity generated typed-write batch through one
1951    /// exact current binding. `None` means the binding or a patch is stale or
1952    /// mismatched.
1953    #[doc(hidden)]
1954    pub fn execute_trusted_same_entity_typed_mutation_batch(
1955        &self,
1956        binding: &DynamicTypedEntityBinding,
1957        requests: Vec<DynamicTypedMutation>,
1958    ) -> Result<Option<DynamicMutationResult>, InternalError> {
1959        if requests.is_empty() {
1960            return Err(InternalError::mutation_batch_empty());
1961        }
1962        if requests.len() > MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS {
1963            return Err(InternalError::mutation_batch_too_many_items(
1964                requests.len(),
1965                MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
1966            ));
1967        }
1968        let Some(catalog) = self.current_typed_entity_binding_catalog(binding)? else {
1969            return Ok(None);
1970        };
1971        let identity = catalog.identity();
1972        let descriptor =
1973            AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())?;
1974        let mut mutations = Vec::with_capacity(requests.len());
1975        let mut save_kinds = Vec::with_capacity(requests.len());
1976        for (batch_position, request) in requests.iter().enumerate() {
1977            let batch_position = u32::try_from(batch_position).map_err(|_| {
1978                InternalError::mutation_batch_too_many_items(
1979                    requests.len(),
1980                    MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
1981                )
1982            })?;
1983            let Some((mutation, save_kind)) = lower_typed_mutation_intent(
1984                identity.entity_tag(),
1985                &descriptor,
1986                binding,
1987                request,
1988                batch_position,
1989            )?
1990            else {
1991                return Ok(None);
1992            };
1993            mutations.push(mutation);
1994            save_kinds.push(save_kind);
1995        }
1996
1997        self.execute_lowered_dynamic_mutation_batch(
1998            &catalog,
1999            &descriptor,
2000            mutations,
2001            save_kinds,
2002            true,
2003        )
2004        .map(Some)
2005    }
2006
2007    /// Execute one bounded generated typed-write batch atomically through
2008    /// exact current same-store bindings. `None` means a binding or patch is
2009    /// stale or mismatched.
2010    #[doc(hidden)]
2011    pub fn execute_trusted_typed_mutation_batch(
2012        &self,
2013        requests: Vec<(DynamicTypedEntityBinding, DynamicTypedMutation)>,
2014    ) -> Result<Option<Vec<DynamicMutationResult>>, InternalError> {
2015        if requests.is_empty() {
2016            return Err(InternalError::mutation_batch_empty());
2017        }
2018        if requests.len() > MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS {
2019            return Err(InternalError::mutation_batch_too_many_items(
2020                requests.len(),
2021                MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
2022            ));
2023        }
2024        let first_binding = requests
2025            .first()
2026            .map(|(binding, _)| binding)
2027            .ok_or_else(InternalError::mutation_batch_empty)?;
2028        let Some(catalog) = self.current_typed_entity_binding_catalog(first_binding)? else {
2029            return Ok(None);
2030        };
2031        let anchor_identity = catalog.identity();
2032        let mut entity_tags = std::collections::BTreeSet::new();
2033        let mut items = Vec::with_capacity(requests.len());
2034        let mut result_catalogs = Vec::with_capacity(requests.len());
2035        let mut save_kinds = Vec::with_capacity(requests.len());
2036        let mut identity_candidate_count = 0_usize;
2037
2038        for (batch_position, (binding, request)) in requests.iter().enumerate() {
2039            let batch_position = u32::try_from(batch_position).map_err(|_| {
2040                InternalError::mutation_batch_too_many_items(
2041                    requests.len(),
2042                    MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
2043                )
2044            })?;
2045            let Some(item_catalog) = catalog.for_entity_path(binding.entity_source.as_str()) else {
2046                return Ok(None);
2047            };
2048            if !self.typed_entity_binding_matches_catalog(binding, &item_catalog)? {
2049                return Ok(None);
2050            }
2051            let item_identity = item_catalog.identity();
2052            if item_identity.store_path() != anchor_identity.store_path() {
2053                return Err(InternalError::mutation_batch_store_mismatch(
2054                    batch_position,
2055                    anchor_identity.entity_tag().value(),
2056                    item_identity.entity_tag().value(),
2057                ));
2058            }
2059            entity_tags.insert(item_identity.entity_tag());
2060            if entity_tags.len() > MAX_STRUCTURAL_MUTATION_BATCH_ENTITIES {
2061                return Err(InternalError::mutation_batch_too_many_entities(
2062                    entity_tags.len(),
2063                    MAX_STRUCTURAL_MUTATION_BATCH_ENTITIES,
2064                ));
2065            }
2066            let descriptor =
2067                AcceptedRowLayoutRuntimeContract::from_accepted_schema(item_catalog.snapshot())?;
2068            let Some((mutation, save_kind)) = lower_typed_mutation_intent(
2069                item_identity.entity_tag(),
2070                &descriptor,
2071                binding,
2072                request,
2073                batch_position,
2074            )?
2075            else {
2076                return Ok(None);
2077            };
2078            if matches!(
2079                mutation,
2080                AcceptedStructuralMutation::Save {
2081                    mode: MutationMode::Insert,
2082                    target: AcceptedStructuralMutationTarget::ResolveFromAfterImage,
2083                    ..
2084                }
2085            ) {
2086                identity_candidate_count = identity_candidate_count.saturating_add(1);
2087            }
2088            result_catalogs.push(item_catalog.clone());
2089            save_kinds.push(save_kind);
2090            items.push(AcceptedStructuralMutationBatchItem {
2091                catalog: item_catalog,
2092                mutation,
2093            });
2094        }
2095
2096        self.execute_lowered_mixed_mutation_batch(
2097            &catalog,
2098            items,
2099            result_catalogs,
2100            save_kinds,
2101            identity_candidate_count,
2102        )
2103        .map(Some)
2104    }
2105
2106    /// Execute one trusted atomic insert batch from entity-name-driven patches.
2107    ///
2108    /// Every patch is lowered against the same accepted snapshot and shares
2109    /// one operation timestamp before the canonical structural batch owner
2110    /// stages any durable effect.
2111    pub fn execute_trusted_dynamic_insert_batch(
2112        &self,
2113        entity: &str,
2114        patches: Vec<DynamicStructuralPatch>,
2115    ) -> Result<DynamicMutationResult, InternalError> {
2116        let mutations = patches
2117            .into_iter()
2118            .map(|patch| DynamicMutation::Insert {
2119                entity: entity.to_string(),
2120                patch,
2121            })
2122            .collect();
2123        self.execute_trusted_dynamic_mutation_batch_with_result_policy(mutations, false)
2124    }
2125}
2126
2127#[cfg(test)]
2128mod typed_adapter_tests {
2129    use super::{
2130        AcceptedFieldKind, DbSession, DynamicTypedBindingError, DynamicTypedEntityBinding,
2131        DynamicTypedMutation, DynamicWriteCell, TypedEntityDescriptor, TypedFieldType,
2132        typed_adapter_field_kind_matches, typed_descriptor_field_type,
2133    };
2134    use crate::{
2135        db::{
2136            TypedFieldDescriptor,
2137            data::DataStore,
2138            index::IndexStore,
2139            registry::{StoreAllocationIdentities, StoreRegistry, StoreRuntimeStorageCapabilities},
2140            schema::{
2141                AcceptedSchemaRevision, FieldId, FieldStorageDecode, LeafCodec,
2142                PersistedFieldSnapshot, PersistedSchemaSnapshot, ScalarCodec, SchemaFieldSlot,
2143                SchemaInsertDefault, SchemaRowLayout, SchemaStore, SchemaVersion,
2144                accepted_schema_candidate_with_field_bindings_for_tests,
2145            },
2146        },
2147        traits::{CanisterKind, Path},
2148        types::EntityTag,
2149        value::InputValue,
2150    };
2151    use icydb_schema::{FieldSourceKey, ScalarType};
2152    use std::{cell::RefCell, collections::BTreeMap};
2153
2154    const STORE_PATH: &str = "session::write::typed_adapter_tests::Store";
2155    const OTHER_STORE_PATH: &str = "session::write::typed_adapter_tests::OtherStore";
2156    const ENTITY_SOURCE: &str = "session::write::typed_adapter_tests::Entity";
2157    const OTHER_ENTITY_SOURCE: &str = "session::write::typed_adapter_tests::OtherEntity";
2158    const ID_SOURCE: &str = "session::write::typed_adapter_tests::Entity::id";
2159    const VALUE_SOURCE: &str = "session::write::typed_adapter_tests::Entity::value";
2160    const REPLACEMENT_SOURCE: &str =
2161        "session::write::typed_adapter_tests::Entity::replacement_value";
2162    const OTHER_ID_SOURCE: &str = "session::write::typed_adapter_tests::OtherEntity::id";
2163    const ENTITY_DESCRIPTOR: TypedEntityDescriptor = TypedEntityDescriptor::new(
2164        ENTITY_SOURCE,
2165        &[ID_SOURCE],
2166        &[
2167            TypedFieldDescriptor::new(ID_SOURCE, TypedFieldType::Scalar(ScalarType::Nat64), false),
2168            TypedFieldDescriptor::new(
2169                VALUE_SOURCE,
2170                TypedFieldType::Scalar(ScalarType::Nat64),
2171                false,
2172            ),
2173        ],
2174    );
2175    const OTHER_ENTITY_DESCRIPTOR: TypedEntityDescriptor = TypedEntityDescriptor::new(
2176        OTHER_ENTITY_SOURCE,
2177        &[OTHER_ID_SOURCE],
2178        &[TypedFieldDescriptor::new(
2179            OTHER_ID_SOURCE,
2180            TypedFieldType::Scalar(ScalarType::Nat64),
2181            false,
2182        )],
2183    );
2184    const REPLACEMENT_DESCRIPTOR: TypedEntityDescriptor = TypedEntityDescriptor::new(
2185        ENTITY_SOURCE,
2186        &[ID_SOURCE],
2187        &[
2188            TypedFieldDescriptor::new(ID_SOURCE, TypedFieldType::Scalar(ScalarType::Nat64), false),
2189            TypedFieldDescriptor::new(
2190                REPLACEMENT_SOURCE,
2191                TypedFieldType::Scalar(ScalarType::Nat64),
2192                false,
2193            ),
2194        ],
2195    );
2196
2197    struct TestCanister;
2198
2199    impl Path for TestCanister {
2200        const PATH: &'static str = "session::write::typed_adapter_tests::Canister";
2201    }
2202
2203    impl CanisterKind for TestCanister {
2204        const COMMIT_MEMORY_ID: u8 = 41;
2205        const COMMIT_STABLE_KEY: &'static str = "icydb.typed_adapter_tests.commit.v1";
2206        const STARTUP_MEMORY_ID: u8 = 49;
2207        const STARTUP_STABLE_KEY: &'static str = "icydb.typed_adapter_tests.startup.control.v1";
2208        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 42;
2209        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
2210            "icydb.typed_adapter_tests.integrity.progress.v1";
2211    }
2212
2213    thread_local! {
2214        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
2215        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
2216        static SCHEMA_STORE: RefCell<SchemaStore> =
2217            const { RefCell::new(SchemaStore::init_heap()) };
2218        static OTHER_DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
2219        static OTHER_INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
2220        static OTHER_SCHEMA_STORE: RefCell<SchemaStore> =
2221            const { RefCell::new(SchemaStore::init_heap()) };
2222        static STORE_REGISTRY: StoreRegistry = {
2223            let mut registry = StoreRegistry::new();
2224            registry.register_store(
2225                STORE_PATH,
2226                &DATA_STORE,
2227                &INDEX_STORE,
2228                &SCHEMA_STORE,
2229                StoreAllocationIdentities::absent(),
2230                StoreRuntimeStorageCapabilities::heap(),
2231            ).expect("typed adapter test store should register");
2232            registry.register_store(
2233                OTHER_STORE_PATH,
2234                &OTHER_DATA_STORE,
2235                &OTHER_INDEX_STORE,
2236                &OTHER_SCHEMA_STORE,
2237                StoreAllocationIdentities::absent(),
2238                StoreRuntimeStorageCapabilities::heap(),
2239            ).expect("second typed adapter test store should register");
2240            registry
2241        };
2242    }
2243
2244    fn nat64_field(id: u32, name: &str, slot: u16) -> PersistedFieldSnapshot {
2245        PersistedFieldSnapshot::new_initial(
2246            FieldId::new(id),
2247            name.to_string(),
2248            SchemaFieldSlot::new(slot),
2249            AcceptedFieldKind::Nat64,
2250            Vec::new(),
2251            false,
2252            SchemaInsertDefault::None,
2253            FieldStorageDecode::ByKind,
2254            LeafCodec::Scalar(ScalarCodec::Nat64),
2255        )
2256    }
2257
2258    fn snapshot(
2259        entity_source: &str,
2260        entity_name: &str,
2261        fields: Vec<PersistedFieldSnapshot>,
2262    ) -> PersistedSchemaSnapshot {
2263        let layout = SchemaRowLayout::initial(
2264            fields
2265                .iter()
2266                .map(|field| (field.id(), field.slot()))
2267                .collect(),
2268        );
2269        PersistedSchemaSnapshot::new(
2270            SchemaVersion::initial(),
2271            entity_source.to_string(),
2272            entity_name.to_string(),
2273            FieldId::new(1),
2274            layout,
2275            fields,
2276        )
2277    }
2278
2279    fn field_source(source: &str) -> FieldSourceKey {
2280        FieldSourceKey::try_new(source).expect("typed field source should admit")
2281    }
2282
2283    fn publish(
2284        session: &DbSession<TestCanister>,
2285        expected: AcceptedSchemaRevision,
2286        revision: AcceptedSchemaRevision,
2287        snapshots: BTreeMap<EntityTag, PersistedSchemaSnapshot>,
2288        fields: BTreeMap<(EntityTag, FieldSourceKey), FieldId>,
2289    ) {
2290        publish_to_store(session, STORE_PATH, expected, revision, snapshots, fields);
2291    }
2292
2293    fn publish_to_store(
2294        session: &DbSession<TestCanister>,
2295        store_path: &'static str,
2296        expected: AcceptedSchemaRevision,
2297        revision: AcceptedSchemaRevision,
2298        snapshots: BTreeMap<EntityTag, PersistedSchemaSnapshot>,
2299        fields: BTreeMap<(EntityTag, FieldSourceKey), FieldId>,
2300    ) {
2301        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
2302            store_path, revision, snapshots, fields,
2303        );
2304        let store = session
2305            .db
2306            .store_handle(store_path)
2307            .expect("typed adapter test store should resolve");
2308        crate::db::commit::publish_accepted_schema_candidate(
2309            store_path, store, expected, &candidate,
2310        )
2311        .expect("typed binding candidate should publish");
2312    }
2313
2314    fn initialize_typed_session() -> DbSession<TestCanister> {
2315        let entity_tag = EntityTag::new(91);
2316        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
2317        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
2318        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
2319        OTHER_DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
2320        OTHER_INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
2321        OTHER_SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
2322        let session = DbSession::<TestCanister>::new(
2323            &STORE_REGISTRY,
2324            &crate::db::RequestExecutionRoot::__new_runtime_root(),
2325        );
2326        session
2327            .db
2328            .drive_startup_recovery_page()
2329            .expect("typed adapter test database should initialize");
2330        publish(
2331            &session,
2332            AcceptedSchemaRevision::NONE,
2333            AcceptedSchemaRevision::INITIAL,
2334            BTreeMap::from([(
2335                entity_tag,
2336                snapshot(
2337                    ENTITY_SOURCE,
2338                    "Entity",
2339                    vec![nat64_field(1, "id", 0), nat64_field(2, "value", 1)],
2340                ),
2341            )]),
2342            BTreeMap::from([
2343                ((entity_tag, field_source(ID_SOURCE)), FieldId::new(1)),
2344                ((entity_tag, field_source(VALUE_SOURCE)), FieldId::new(2)),
2345            ]),
2346        );
2347        session
2348    }
2349
2350    fn initialize_mixed_typed_session(other_store: bool) -> DbSession<TestCanister> {
2351        let entity_tag = EntityTag::new(91);
2352        let other_entity_tag = EntityTag::new(92);
2353        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
2354        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
2355        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
2356        OTHER_DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
2357        OTHER_INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
2358        OTHER_SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
2359        let session = DbSession::<TestCanister>::new(
2360            &STORE_REGISTRY,
2361            &crate::db::RequestExecutionRoot::__new_runtime_root(),
2362        );
2363        session
2364            .db
2365            .drive_startup_recovery_page()
2366            .expect("mixed typed adapter database should initialize");
2367
2368        let entity_snapshot = snapshot(
2369            ENTITY_SOURCE,
2370            "Entity",
2371            vec![nat64_field(1, "id", 0), nat64_field(2, "value", 1)],
2372        );
2373        let other_snapshot = snapshot(
2374            OTHER_ENTITY_SOURCE,
2375            "OtherEntity",
2376            vec![nat64_field(1, "id", 0)],
2377        );
2378        let entity_fields = BTreeMap::from([
2379            ((entity_tag, field_source(ID_SOURCE)), FieldId::new(1)),
2380            ((entity_tag, field_source(VALUE_SOURCE)), FieldId::new(2)),
2381        ]);
2382        if other_store {
2383            publish(
2384                &session,
2385                AcceptedSchemaRevision::NONE,
2386                AcceptedSchemaRevision::INITIAL,
2387                BTreeMap::from([(entity_tag, entity_snapshot)]),
2388                entity_fields,
2389            );
2390            publish_to_store(
2391                &session,
2392                OTHER_STORE_PATH,
2393                AcceptedSchemaRevision::NONE,
2394                AcceptedSchemaRevision::INITIAL,
2395                BTreeMap::from([(other_entity_tag, other_snapshot)]),
2396                BTreeMap::from([(
2397                    (other_entity_tag, field_source(OTHER_ID_SOURCE)),
2398                    FieldId::new(1),
2399                )]),
2400            );
2401        } else {
2402            let mut fields = entity_fields;
2403            fields.insert(
2404                (other_entity_tag, field_source(OTHER_ID_SOURCE)),
2405                FieldId::new(1),
2406            );
2407            publish(
2408                &session,
2409                AcceptedSchemaRevision::NONE,
2410                AcceptedSchemaRevision::INITIAL,
2411                BTreeMap::from([
2412                    (entity_tag, entity_snapshot),
2413                    (other_entity_tag, other_snapshot),
2414                ]),
2415                fields,
2416            );
2417        }
2418        session
2419    }
2420
2421    fn typed_insert(
2422        binding: &DynamicTypedEntityBinding,
2423        id: u64,
2424        value: u64,
2425    ) -> DynamicTypedMutation {
2426        let patch = binding
2427            .bind_write_ordinals(vec![
2428                (0, DynamicWriteCell::Value(InputValue::nat64(id))),
2429                (1, DynamicWriteCell::Value(InputValue::nat64(value))),
2430            ])
2431            .expect("typed insert patch should bind");
2432        DynamicTypedMutation::Insert { patch }
2433    }
2434
2435    fn typed_other_insert(binding: &DynamicTypedEntityBinding, id: u64) -> DynamicTypedMutation {
2436        let patch = binding
2437            .bind_write_ordinals(vec![(0, DynamicWriteCell::Value(InputValue::nat64(id)))])
2438            .expect("other typed insert patch should bind");
2439        DynamicTypedMutation::Insert { patch }
2440    }
2441
2442    fn typed_delete(id: u64) -> DynamicTypedMutation {
2443        DynamicTypedMutation::Delete {
2444            key: InputValue::nat64(id),
2445        }
2446    }
2447
2448    fn typed_value_patch(
2449        binding: &DynamicTypedEntityBinding,
2450        value: u64,
2451    ) -> super::DynamicTypedStructuralPatch {
2452        binding
2453            .bind_write_ordinals(vec![(1, DynamicWriteCell::Value(InputValue::nat64(value)))])
2454            .expect("typed value patch should bind")
2455    }
2456
2457    fn assert_query_diagnostic(
2458        error: crate::db::QueryError,
2459        code: icydb_diagnostic_code::DiagnosticCode,
2460        origin: icydb_diagnostic_code::ErrorOrigin,
2461        detail: icydb_diagnostic_code::DiagnosticDetail,
2462    ) {
2463        let diagnostic = error.diagnostic();
2464        assert_eq!(diagnostic.code(), code);
2465        assert_eq!(diagnostic.origin(), origin);
2466        assert_eq!(diagnostic.detail(), Some(&detail));
2467    }
2468
2469    #[test]
2470    fn typed_adapter_kind_matching_is_exact_but_accepts_relation_key_wrappers() {
2471        let relation = AcceptedFieldKind::Relation {
2472            target_path: "test::Target".to_string(),
2473            target_entity_name: "Target".to_string(),
2474            target_entity_tag: EntityTag::new(7),
2475            target_store_path: "test::Store".to_string(),
2476            key_kind: Box::new(AcceptedFieldKind::Nat64),
2477        };
2478
2479        assert!(typed_adapter_field_kind_matches(
2480            &relation,
2481            &AcceptedFieldKind::Nat64,
2482        ));
2483        assert!(typed_adapter_field_kind_matches(
2484            &AcceptedFieldKind::List(Box::new(relation)),
2485            &AcceptedFieldKind::List(Box::new(AcceptedFieldKind::Nat64)),
2486        ));
2487        assert!(!typed_adapter_field_kind_matches(
2488            &AcceptedFieldKind::Nat64,
2489            &AcceptedFieldKind::Nat32,
2490        ));
2491    }
2492
2493    #[test]
2494    fn typed_adapter_field_contract_rejects_invalid_named_source_identity() {
2495        const NAT64: TypedFieldType = TypedFieldType::Scalar(ScalarType::Nat64);
2496
2497        assert!(matches!(
2498            typed_descriptor_field_type(TypedFieldType::Named("")),
2499            Err(DynamicTypedBindingError::FieldUnavailable),
2500        ));
2501        assert!(matches!(
2502            typed_descriptor_field_type(TypedFieldType::Scalar(ScalarType::Nat16)),
2503            Ok(icydb_schema::FieldType::Scalar(ScalarType::Nat16)),
2504        ));
2505        assert!(matches!(
2506            typed_descriptor_field_type(TypedFieldType::List(&NAT64)),
2507            Ok(icydb_schema::FieldType::List(item))
2508                if *item == icydb_schema::FieldType::Scalar(ScalarType::Nat64),
2509        ));
2510    }
2511
2512    #[test]
2513    fn typed_descriptor_primary_key_must_match_accepted_source_order() {
2514        const PRIMARY_KEY_MISMATCH: TypedEntityDescriptor =
2515            TypedEntityDescriptor::new(ENTITY_SOURCE, &[VALUE_SOURCE], ENTITY_DESCRIPTOR.fields);
2516        const NULLABILITY_MISMATCH: TypedEntityDescriptor = TypedEntityDescriptor::new(
2517            ENTITY_SOURCE,
2518            &[ID_SOURCE],
2519            &[
2520                TypedFieldDescriptor::new(
2521                    ID_SOURCE,
2522                    TypedFieldType::Scalar(ScalarType::Nat64),
2523                    false,
2524                ),
2525                TypedFieldDescriptor::new(
2526                    VALUE_SOURCE,
2527                    TypedFieldType::Scalar(ScalarType::Nat64),
2528                    true,
2529                ),
2530            ],
2531        );
2532
2533        let session = initialize_typed_session();
2534        assert!(matches!(
2535            session.issue_typed_entity_binding(&PRIMARY_KEY_MISMATCH),
2536            Err(DynamicTypedBindingError::IncompatibleField),
2537        ));
2538        assert!(matches!(
2539            session.issue_typed_entity_binding(&NULLABILITY_MISMATCH),
2540            Err(DynamicTypedBindingError::IncompatibleField),
2541        ));
2542    }
2543
2544    #[test]
2545    fn typed_mutation_batch_is_bounded_and_atomic() {
2546        let session = initialize_typed_session();
2547        let binding = session
2548            .issue_typed_entity_binding(&ENTITY_DESCRIPTOR)
2549            .expect("typed batch binding should issue");
2550
2551        session
2552            .execute_trusted_typed_mutation_batch(Vec::new())
2553            .expect_err("empty typed batch should reject");
2554        let insert = typed_insert(&binding, 1, 10);
2555        let oversized = (0..=super::MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS)
2556            .map(|_| (binding.clone(), insert.clone()))
2557            .collect();
2558        session
2559            .execute_trusted_typed_mutation_batch(oversized)
2560            .expect_err("oversized typed batch should reject");
2561
2562        let duplicate = vec![
2563            (binding.clone(), insert.clone()),
2564            (binding.clone(), typed_insert(&binding, 1, 11)),
2565        ];
2566        session
2567            .execute_trusted_typed_mutation_batch(duplicate)
2568            .expect_err("late duplicate key should reject the whole typed batch");
2569        let empty = session
2570            .execute_trusted_live_page(&crate::db::DynamicQuery::new("Entity"), None)
2571            .expect("failed typed batch should leave the entity readable");
2572        assert!(empty.rows.is_empty());
2573
2574        let result = session
2575            .execute_trusted_typed_mutation_batch(vec![
2576                (binding.clone(), insert),
2577                (binding.clone(), typed_insert(&binding, 2, 20)),
2578            ])
2579            .expect("valid typed batch should execute")
2580            .expect("exact binding should remain current");
2581        assert_eq!(result.len(), 2);
2582        assert!(result.iter().all(|item| item.affected_rows == 1));
2583        assert_eq!(
2584            result
2585                .into_iter()
2586                .map(|item| item.rows.into_iter().next().expect("one row per request"))
2587                .collect::<Vec<_>>(),
2588            vec![
2589                vec![
2590                    crate::value::OutputValue::nat64(1),
2591                    crate::value::OutputValue::nat64(10),
2592                ],
2593                vec![
2594                    crate::value::OutputValue::nat64(2),
2595                    crate::value::OutputValue::nat64(20),
2596                ],
2597            ]
2598        );
2599
2600        let mut mismatched = binding.clone();
2601        mismatched.accepted_revision = mismatched.accepted_revision.saturating_add(1);
2602        let mismatch = session
2603            .execute_trusted_typed_mutation_batch(vec![
2604                (binding.clone(), typed_insert(&binding, 3, 30)),
2605                (mismatched.clone(), typed_insert(&binding, 4, 40)),
2606            ])
2607            .expect("mismatched typed batch should fail closed");
2608        assert!(mismatch.is_none());
2609        let stale = session
2610            .execute_trusted_typed_mutation_batch(vec![(mismatched, typed_insert(&binding, 5, 50))])
2611            .expect("stale typed batch should fail closed");
2612        assert!(stale.is_none());
2613    }
2614
2615    #[test]
2616    fn same_entity_typed_mutation_batch_rejects_empty_oversized_and_stale_input() {
2617        let session = initialize_typed_session();
2618        let binding = session
2619            .issue_typed_entity_binding(&ENTITY_DESCRIPTOR)
2620            .expect("typed batch binding should issue");
2621
2622        session
2623            .execute_trusted_same_entity_typed_mutation_batch(&binding, Vec::new())
2624            .expect_err("empty same-entity typed batch should reject");
2625        let insert = typed_insert(&binding, 1, 10);
2626        session
2627            .execute_trusted_same_entity_typed_mutation_batch(
2628                &binding,
2629                vec![insert.clone(); super::MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS + 1],
2630            )
2631            .expect_err("oversized same-entity typed batch should reject");
2632
2633        let mut stale = binding;
2634        stale.accepted_revision = stale.accepted_revision.saturating_add(1);
2635        let result = session
2636            .execute_trusted_same_entity_typed_mutation_batch(&stale, vec![insert])
2637            .expect("stale same-entity typed admission should remain an adapter outcome");
2638        assert!(result.is_none());
2639    }
2640
2641    #[test]
2642    fn typed_mutation_batch_accepts_mixed_same_store_bindings_and_rejects_late_stale_input() {
2643        let session = initialize_mixed_typed_session(false);
2644        let binding = session
2645            .issue_typed_entity_binding(&ENTITY_DESCRIPTOR)
2646            .expect("first typed entity should bind");
2647        let other = session
2648            .issue_typed_entity_binding(&OTHER_ENTITY_DESCRIPTOR)
2649            .expect("second typed entity should bind");
2650
2651        let mut stale_other = other.clone();
2652        stale_other.accepted_revision = stale_other.accepted_revision.saturating_add(1);
2653        let stale = session
2654            .execute_trusted_typed_mutation_batch(vec![
2655                (binding.clone(), typed_insert(&binding, 1, 10)),
2656                (stale_other, typed_other_insert(&other, 1)),
2657            ])
2658            .expect("stale typed admission should remain an adapter outcome");
2659        assert!(stale.is_none());
2660        for entity in ["Entity", "OtherEntity"] {
2661            let rows = session
2662                .execute_trusted_live_page(&crate::db::DynamicQuery::new(entity), None)
2663                .expect("failed mixed admission should leave both entities readable");
2664            assert!(rows.rows.is_empty());
2665        }
2666
2667        let results = session
2668            .execute_trusted_typed_mutation_batch(vec![
2669                (other.clone(), typed_other_insert(&other, 2)),
2670                (binding.clone(), typed_insert(&binding, 3, 30)),
2671            ])
2672            .expect("same-store typed batch should execute")
2673            .expect("both typed bindings should remain current");
2674        assert_eq!(results.len(), 2);
2675        assert_eq!(results[0].entity, "OtherEntity");
2676        assert_eq!(
2677            results[0].rows,
2678            vec![vec![crate::value::OutputValue::nat64(2)]]
2679        );
2680        assert_eq!(results[1].entity, "Entity");
2681        assert_eq!(
2682            results[1].rows,
2683            vec![vec![
2684                crate::value::OutputValue::nat64(3),
2685                crate::value::OutputValue::nat64(30),
2686            ]],
2687        );
2688    }
2689
2690    #[test]
2691    fn typed_mutation_batch_rejects_cross_store_bindings_before_writes() {
2692        let session = initialize_mixed_typed_session(true);
2693        let binding = session
2694            .issue_typed_entity_binding(&ENTITY_DESCRIPTOR)
2695            .expect("first store typed entity should bind");
2696        let other = session
2697            .issue_typed_entity_binding(&OTHER_ENTITY_DESCRIPTOR)
2698            .expect("second store typed entity should bind");
2699
2700        let error = session
2701            .execute_trusted_typed_mutation_batch(vec![
2702                (binding.clone(), typed_insert(&binding, 1, 10)),
2703                (other.clone(), typed_other_insert(&other, 1)),
2704            ])
2705            .expect_err("typed cross-store rows must reject");
2706        assert!(matches!(
2707            error.diagnostic().detail(),
2708            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2709                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchStoreMismatch,
2710            })
2711        ));
2712        for entity in ["Entity", "OtherEntity"] {
2713            let rows = session
2714                .execute_trusted_live_page(&crate::db::DynamicQuery::new(entity), None)
2715                .expect("cross-store rejection should leave both entities readable");
2716            assert!(rows.rows.is_empty());
2717        }
2718    }
2719
2720    #[test]
2721    fn same_entity_typed_mutation_batch_preserves_mixed_result_order() {
2722        let session = initialize_typed_session();
2723        let binding = session
2724            .issue_typed_entity_binding(&ENTITY_DESCRIPTOR)
2725            .expect("typed batch binding should issue");
2726        session
2727            .execute_trusted_same_entity_typed_mutation_batch(
2728                &binding,
2729                vec![
2730                    typed_insert(&binding, 1, 10),
2731                    typed_insert(&binding, 2, 20),
2732                    typed_insert(&binding, 4, 40),
2733                ],
2734            )
2735            .expect("typed fixture batch should execute")
2736            .expect("typed fixture binding should be current");
2737
2738        let result = session
2739            .execute_trusted_same_entity_typed_mutation_batch(
2740                &binding,
2741                vec![
2742                    DynamicTypedMutation::Update {
2743                        key: InputValue::nat64(1),
2744                        patch: typed_value_patch(&binding, 11),
2745                    },
2746                    DynamicTypedMutation::Replace {
2747                        key: InputValue::nat64(2),
2748                        patch: typed_value_patch(&binding, 22),
2749                    },
2750                    typed_insert(&binding, 3, 30),
2751                    typed_delete(4),
2752                ],
2753            )
2754            .expect("mixed typed batch should execute")
2755            .expect("mixed typed binding should remain current");
2756        assert_eq!(result.len(), 4);
2757        assert_eq!(result.affected_rows, 4);
2758        assert_eq!(
2759            result.rows,
2760            vec![
2761                vec![
2762                    crate::value::OutputValue::nat64(1),
2763                    crate::value::OutputValue::nat64(11),
2764                ],
2765                vec![
2766                    crate::value::OutputValue::nat64(2),
2767                    crate::value::OutputValue::nat64(22),
2768                ],
2769                vec![
2770                    crate::value::OutputValue::nat64(3),
2771                    crate::value::OutputValue::nat64(30),
2772                ],
2773                vec![
2774                    crate::value::OutputValue::nat64(4),
2775                    crate::value::OutputValue::nat64(40),
2776                ],
2777            ],
2778        );
2779    }
2780
2781    // Keep the full rename, stale-binding, and old-name-reuse lifecycle in one
2782    // regression so each issued binding is checked against the next revision.
2783    #[expect(clippy::too_many_lines)]
2784    #[test]
2785    fn typed_binding_uses_accepted_ids_and_slots_across_renames_and_name_reuse() {
2786        let entity_tag = EntityTag::new(91);
2787        let other_entity_tag = EntityTag::new(92);
2788        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
2789        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
2790        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
2791        OTHER_DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
2792        OTHER_INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
2793        OTHER_SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
2794
2795        let session = DbSession::<TestCanister>::new(
2796            &STORE_REGISTRY,
2797            &crate::db::RequestExecutionRoot::__new_runtime_root(),
2798        );
2799        session
2800            .db
2801            .drive_startup_recovery_page()
2802            .expect("typed adapter test database should initialize");
2803        publish(
2804            &session,
2805            AcceptedSchemaRevision::NONE,
2806            AcceptedSchemaRevision::INITIAL,
2807            BTreeMap::from([(
2808                entity_tag,
2809                snapshot(
2810                    ENTITY_SOURCE,
2811                    "Entity",
2812                    vec![nat64_field(1, "id", 0), nat64_field(2, "value", 1)],
2813                ),
2814            )]),
2815            BTreeMap::from([
2816                ((entity_tag, field_source(ID_SOURCE)), FieldId::new(1)),
2817                ((entity_tag, field_source(VALUE_SOURCE)), FieldId::new(2)),
2818            ]),
2819        );
2820
2821        let initial_catalog = session
2822            .find_accepted_schema_catalog_context_for_entity_source_key(ENTITY_SOURCE)
2823            .expect("initial source catalog lookup should inspect")
2824            .expect("initial source catalog should exist");
2825        assert_eq!(initial_catalog.identity().entity_tag(), entity_tag);
2826        let initial = session
2827            .issue_typed_entity_binding(&ENTITY_DESCRIPTOR)
2828            .expect("initial typed binding should issue");
2829        assert_eq!(initial.field_slot(ID_SOURCE), Some(0));
2830        assert_eq!(initial.field_slot(VALUE_SOURCE), Some(1));
2831        assert_eq!(initial.output_field_slot("value"), Some(1));
2832        let initial_patch = initial
2833            .bind_write_ordinals(vec![(1, DynamicWriteCell::Value(InputValue::nat64(7)))])
2834            .expect("source-bound patch should lower");
2835        assert_eq!(
2836            initial_patch.fields(),
2837            &[(1, DynamicWriteCell::Value(InputValue::nat64(7)))]
2838        );
2839        assert!(
2840            initial
2841                .bind_write_ordinals(vec![(2, DynamicWriteCell::Value(InputValue::nat64(8)),)])
2842                .is_none(),
2843            "out-of-range descriptor ordinals must fail closed",
2844        );
2845        assert!(
2846            initial
2847                .bind_write_ordinals(vec![
2848                    (1, DynamicWriteCell::Omitted),
2849                    (1, DynamicWriteCell::Default),
2850                ])
2851                .is_none(),
2852            "duplicate descriptor ordinals must fail closed",
2853        );
2854        assert!(
2855            initial
2856                .bind_write_ordinals(vec![
2857                    (1, DynamicWriteCell::Omitted),
2858                    (0, DynamicWriteCell::Default),
2859                ])
2860                .is_none(),
2861            "out-of-order descriptor ordinals must fail closed",
2862        );
2863
2864        publish(
2865            &session,
2866            AcceptedSchemaRevision::INITIAL,
2867            AcceptedSchemaRevision::new(2),
2868            BTreeMap::from([
2869                (
2870                    entity_tag,
2871                    snapshot(
2872                        ENTITY_SOURCE,
2873                        "RenamedEntity",
2874                        vec![
2875                            nat64_field(1, "id", 0),
2876                            nat64_field(2, "renamed_value", 1),
2877                            nat64_field(3, "value", 2),
2878                        ],
2879                    ),
2880                ),
2881                (
2882                    other_entity_tag,
2883                    snapshot(OTHER_ENTITY_SOURCE, "Entity", vec![nat64_field(1, "id", 0)]),
2884                ),
2885            ]),
2886            BTreeMap::from([
2887                ((entity_tag, field_source(ID_SOURCE)), FieldId::new(1)),
2888                ((entity_tag, field_source(VALUE_SOURCE)), FieldId::new(2)),
2889                (
2890                    (entity_tag, field_source(REPLACEMENT_SOURCE)),
2891                    FieldId::new(3),
2892                ),
2893                (
2894                    (other_entity_tag, field_source(OTHER_ID_SOURCE)),
2895                    FieldId::new(1),
2896                ),
2897            ]),
2898        );
2899
2900        let stale_authority = session
2901            .ensure_accepted_schema_authority_is_current_for_store_path(
2902                STORE_PATH,
2903                initial_catalog.value_catalog_handle().authority(),
2904            )
2905            .expect_err("the initial accepted authority must be stale after revision two");
2906        assert_eq!(
2907            stale_authority.diagnostic_facts(),
2908            vec![
2909                (
2910                    icydb_diagnostic_code::DiagnosticFactTag::ExpectedRevision,
2911                    AcceptedSchemaRevision::INITIAL.get(),
2912                ),
2913                (
2914                    icydb_diagnostic_code::DiagnosticFactTag::CurrentRevision,
2915                    AcceptedSchemaRevision::new(2).get(),
2916                ),
2917            ],
2918        );
2919
2920        assert!(
2921            !session
2922                .typed_entity_binding_is_current(&initial)
2923                .expect("renamed binding currentness should inspect")
2924        );
2925        let renamed = session
2926            .issue_typed_entity_binding(&ENTITY_DESCRIPTOR)
2927            .expect("renamed source-bound adapter should rebind");
2928        assert_eq!(renamed.entity(), "RenamedEntity");
2929        assert_eq!(renamed.field_slot(VALUE_SOURCE), Some(1));
2930        assert_eq!(renamed.output_field_slot("renamed_value"), Some(1));
2931        assert_eq!(renamed.output_field_slot("value"), None);
2932
2933        publish(
2934            &session,
2935            AcceptedSchemaRevision::new(2),
2936            AcceptedSchemaRevision::new(3),
2937            BTreeMap::from([
2938                (
2939                    entity_tag,
2940                    snapshot(
2941                        ENTITY_SOURCE,
2942                        "RenamedEntity",
2943                        vec![nat64_field(1, "id", 0), nat64_field(2, "value", 1)],
2944                    ),
2945                ),
2946                (
2947                    other_entity_tag,
2948                    snapshot(OTHER_ENTITY_SOURCE, "Entity", vec![nat64_field(1, "id", 0)]),
2949                ),
2950            ]),
2951            BTreeMap::from([
2952                ((entity_tag, field_source(ID_SOURCE)), FieldId::new(1)),
2953                (
2954                    (entity_tag, field_source(REPLACEMENT_SOURCE)),
2955                    FieldId::new(2),
2956                ),
2957                (
2958                    (other_entity_tag, field_source(OTHER_ID_SOURCE)),
2959                    FieldId::new(1),
2960                ),
2961            ]),
2962        );
2963
2964        assert!(matches!(
2965            session.issue_typed_entity_binding(&ENTITY_DESCRIPTOR),
2966            Err(DynamicTypedBindingError::FieldUnavailable),
2967        ));
2968        assert!(
2969            !session
2970                .typed_entity_binding_is_current(&renamed)
2971                .expect("removed source binding should become stale")
2972        );
2973
2974        let replacement = session
2975            .issue_typed_entity_binding(&REPLACEMENT_DESCRIPTOR)
2976            .expect("explicit replacement source should bind");
2977        assert!(
2978            session
2979                .execute_trusted_typed_mutation(
2980                    &replacement,
2981                    &DynamicTypedMutation::Insert {
2982                        patch: initial_patch
2983                    },
2984                )
2985                .expect("cross-binding patch should fail closed")
2986                .is_none()
2987        );
2988        let patch = replacement
2989            .bind_write_ordinals(vec![
2990                (0, DynamicWriteCell::Value(InputValue::nat64(1))),
2991                (1, DynamicWriteCell::Value(InputValue::nat64(9))),
2992            ])
2993            .expect("replacement source write should bind by accepted IDs and slots");
2994        let result = session
2995            .execute_trusted_typed_mutation(&replacement, &DynamicTypedMutation::Insert { patch })
2996            .expect("typed insert should use the accepted mutation pipeline")
2997            .expect("replacement binding should remain current");
2998        assert_eq!(result.entity, "RenamedEntity");
2999        assert_eq!(result.columns, vec!["id".to_string(), "value".to_string()]);
3000        assert_eq!(
3001            result.rows,
3002            vec![vec![
3003                crate::value::OutputValue::nat64(1),
3004                crate::value::OutputValue::nat64(9)
3005            ]]
3006        );
3007        assert_eq!(result.affected_rows, 1);
3008
3009        let second_patch = replacement
3010            .bind_write_ordinals(vec![
3011                (0, DynamicWriteCell::Value(InputValue::nat64(2))),
3012                (1, DynamicWriteCell::Value(InputValue::nat64(10))),
3013            ])
3014            .expect("second source-bound patch should lower");
3015        session
3016            .execute_trusted_typed_mutation(
3017                &replacement,
3018                &DynamicTypedMutation::Insert {
3019                    patch: second_patch,
3020                },
3021            )
3022            .expect("second typed insert should use the accepted mutation pipeline")
3023            .expect("replacement binding should remain current");
3024
3025        {
3026            let query = crate::db::DynamicQuery::new("RenamedEntity")
3027                .select(["id", "value"])
3028                .order_by(crate::db::asc("id"))
3029                .limit(1);
3030            let result = session
3031                .execute_trusted_live_page(&query, None)
3032                .expect("SQL-free dynamic execution should use accepted authority");
3033            assert_eq!(result.entity, "RenamedEntity");
3034            assert_eq!(result.columns, vec!["id".to_string(), "value".to_string()]);
3035            assert_eq!(
3036                result.rows,
3037                vec![vec![
3038                    crate::value::OutputValue::nat64(1),
3039                    crate::value::OutputValue::nat64(9)
3040                ]]
3041            );
3042            assert_eq!(result.row_count, 1);
3043            assert_query_diagnostic(
3044                session
3045                    .execute_trusted_live_page(&query.cursor("00"), None)
3046                    .expect_err("scalar execution must reject grouped cursor state"),
3047                icydb_diagnostic_code::DiagnosticCode::QueryIntent,
3048                icydb_diagnostic_code::ErrorOrigin::Query,
3049                icydb_diagnostic_code::DiagnosticDetail::QueryKind {
3050                    kind: icydb_diagnostic_code::QueryErrorKind::Intent,
3051                },
3052            );
3053            assert_query_diagnostic(
3054                session
3055                    .execute_public_dynamic_grouped_query(
3056                        &crate::db::DynamicQuery::new("RenamedEntity").grouped_limits(1, 1024),
3057                    )
3058                    .expect_err("grouped execution must reject scalar query state"),
3059                icydb_diagnostic_code::DiagnosticCode::QueryIntent,
3060                icydb_diagnostic_code::ErrorOrigin::Query,
3061                icydb_diagnostic_code::DiagnosticDetail::QueryKind {
3062                    kind: icydb_diagnostic_code::QueryErrorKind::Intent,
3063                },
3064            );
3065
3066            let grouped_query = crate::db::DynamicQuery::new("RenamedEntity")
3067                .filter(crate::db::FieldRef::new("id").eq(1_u64))
3068                .group_by("value")
3069                .aggregate(crate::db::count())
3070                .grouped_limits(1, 16 * 1024)
3071                .limit(1);
3072            let grouped = session
3073                .execute_public_dynamic_grouped_query(&grouped_query)
3074                .expect("SQL-free grouped execution should use accepted authority");
3075            let typed_grouped = session
3076                .execute_public_dynamic_grouped_query_for_typed_binding(
3077                    &replacement,
3078                    &grouped_query,
3079                )
3080                .expect("typed grouped execution should inspect accepted authority")
3081                .expect("replacement binding should remain current");
3082            assert_eq!(typed_grouped, grouped);
3083            assert!(
3084                session
3085                    .execute_public_dynamic_grouped_query_for_typed_binding(
3086                        &renamed,
3087                        &grouped_query,
3088                    )
3089                    .expect("stale grouped binding should inspect accepted authority")
3090                    .is_none(),
3091                "stale typed grouped bindings must fail closed before execution"
3092            );
3093            assert_eq!(grouped.entity, "RenamedEntity");
3094            assert_eq!(grouped.row_count, 1);
3095            assert_eq!(grouped.rows.len(), 1);
3096            assert_eq!(
3097                grouped.rows[0].group_key(),
3098                &[crate::value::OutputValue::nat64(9)]
3099            );
3100            assert_eq!(
3101                grouped.rows[0].aggregate_values(),
3102                &[crate::value::OutputValue::nat64(1)]
3103            );
3104            assert_eq!(grouped.next_cursor, None);
3105
3106            let grouped_state_error = session
3107                .execute_trusted_dynamic_grouped_query(&grouped_query.clone().grouped_limits(1, 1))
3108                .expect_err("grouped retained state must respect its explicit byte ceiling");
3109            assert!(matches!(
3110                grouped_state_error.diagnostic().detail(),
3111                Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
3112                    boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
3113                })
3114            ));
3115            assert_eq!(
3116                grouped_state_error.diagnostic_facts()[0],
3117                (
3118                    icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
3119                    icydb_diagnostic_code::DiagnosticExecutionBudgetResource::GroupDistinctStateBytes.raw(),
3120                ),
3121            );
3122
3123            assert_query_diagnostic(
3124                session
3125                    .execute_public_dynamic_grouped_query(&grouped_query.clone().select(["value"]))
3126                    .expect_err("grouped output must reject scalar selection"),
3127                icydb_diagnostic_code::DiagnosticCode::QueryIntent,
3128                icydb_diagnostic_code::ErrorOrigin::Query,
3129                icydb_diagnostic_code::DiagnosticDetail::QueryKind {
3130                    kind: icydb_diagnostic_code::QueryErrorKind::Intent,
3131                },
3132            );
3133            assert_query_diagnostic(
3134                session
3135                    .execute_public_dynamic_grouped_query(
3136                        &crate::db::DynamicQuery::new("RenamedEntity")
3137                            .group_by("value")
3138                            .aggregate(crate::db::count()),
3139                    )
3140                    .expect_err("public grouped execution must require explicit limits"),
3141                icydb_diagnostic_code::DiagnosticCode::QueryReadAdmission,
3142                icydb_diagnostic_code::ErrorOrigin::Query,
3143                icydb_diagnostic_code::DiagnosticDetail::QueryReadAdmission {
3144                    reason:
3145                        icydb_diagnostic_code::QueryReadAdmissionCode::GroupedQueryRequiresLimits,
3146                },
3147            );
3148            assert_query_diagnostic(
3149                session
3150                    .execute_trusted_dynamic_grouped_query(
3151                        &crate::db::DynamicQuery::new("RenamedEntity")
3152                            .group_by("value")
3153                            .aggregate(crate::db::count())
3154                            .grouped_limits(0, 1024),
3155                    )
3156                    .expect_err("trusted grouped execution must reject zero limits"),
3157                icydb_diagnostic_code::DiagnosticCode::QueryReadAdmission,
3158                icydb_diagnostic_code::ErrorOrigin::Query,
3159                icydb_diagnostic_code::DiagnosticDetail::QueryReadAdmission {
3160                    reason:
3161                        icydb_diagnostic_code::QueryReadAdmissionCode::GroupedQueryRequiresLimits,
3162                },
3163            );
3164            assert_query_diagnostic(
3165                session
3166                    .execute_public_dynamic_grouped_query(&grouped_query.grouped_limits(101, 1024))
3167                    .expect_err("public grouped execution must enforce its group budget"),
3168                icydb_diagnostic_code::DiagnosticCode::QueryReadAdmission,
3169                icydb_diagnostic_code::ErrorOrigin::Query,
3170                icydb_diagnostic_code::DiagnosticDetail::QueryReadAdmission {
3171                    reason:
3172                        icydb_diagnostic_code::QueryReadAdmissionCode::GroupedQueryExceedsBudget,
3173                },
3174            );
3175
3176            let paged_query = crate::db::DynamicQuery::new("RenamedEntity")
3177                .group_by("value")
3178                .aggregate(crate::db::count())
3179                .grouped_limits(2, 16 * 1024)
3180                .limit(1);
3181            assert_query_diagnostic(
3182                session
3183                    .execute_public_dynamic_grouped_query(&paged_query)
3184                    .expect_err("public grouped execution must reject an unbounded full scan"),
3185                icydb_diagnostic_code::DiagnosticCode::QueryReadAdmission,
3186                icydb_diagnostic_code::ErrorOrigin::Query,
3187                icydb_diagnostic_code::DiagnosticDetail::QueryReadAdmission {
3188                    reason:
3189                        icydb_diagnostic_code::QueryReadAdmissionCode::UnboundedFullScanRejected,
3190                },
3191            );
3192            let first_page = session
3193                .execute_trusted_dynamic_grouped_query(&paged_query)
3194                .expect("SQL-free grouped first page should execute");
3195            assert_eq!(first_page.row_count, 1);
3196            assert_eq!(
3197                first_page.rows[0].group_key(),
3198                &[crate::value::OutputValue::nat64(9)]
3199            );
3200            let cursor = first_page
3201                .next_cursor
3202                .expect("first grouped page should return a continuation cursor");
3203            assert_query_diagnostic(
3204                session
3205                    .execute_trusted_dynamic_grouped_query(
3206                        &paged_query.clone().cursor(format!("{cursor}0")),
3207                    )
3208                    .expect_err("tampered grouped cursor must fail closed"),
3209                icydb_diagnostic_code::DiagnosticCode::QueryInvalidContinuationCursor,
3210                icydb_diagnostic_code::ErrorOrigin::Cursor,
3211                icydb_diagnostic_code::DiagnosticDetail::QueryKind {
3212                    kind: icydb_diagnostic_code::QueryErrorKind::InvalidContinuationCursor,
3213                },
3214            );
3215            let second_page = session
3216                .execute_trusted_dynamic_grouped_query(&paged_query.cursor(cursor))
3217                .expect("SQL-free grouped continuation should execute");
3218            assert_eq!(second_page.row_count, 1);
3219            assert_eq!(
3220                second_page.rows[0].group_key(),
3221                &[crate::value::OutputValue::nat64(10)]
3222            );
3223            assert_eq!(second_page.next_cursor, None);
3224        }
3225    }
3226}
3227
3228#[cfg(test)]
3229mod mixed_relation_batch_tests {
3230    use super::{
3231        DbSession, DynamicMutation, DynamicStructuralPatch, DynamicWriteCell,
3232        TypedEntityDescriptor, TypedFieldType,
3233    };
3234    use crate::{
3235        db::{
3236            DynamicQuery, TypedFieldDescriptor, asc,
3237            data::DataStore,
3238            desc,
3239            index::IndexStore,
3240            query::expr::FilterExpr,
3241            registry::{StoreAllocationIdentities, StoreRegistry, StoreRuntimeStorageCapabilities},
3242            schema::{
3243                AcceptedConstraintCatalog, AcceptedFieldKind, AcceptedSchemaRevision, FieldId,
3244                FieldStorageDecode, FieldWriteManagement, LeafCodec, PersistedFieldSnapshot,
3245                PersistedIndexFieldPathSnapshot, PersistedIndexKeySnapshot, PersistedIndexSnapshot,
3246                PersistedRelationEdgeSnapshot, PersistedSchemaSnapshot, RelationId, ScalarCodec,
3247                SchemaFieldSlot, SchemaFieldWritePolicy, SchemaIndexId, SchemaInsertDefault,
3248                SchemaRowLayout, SchemaStore, SchemaVersion,
3249                accepted_schema_candidate_with_field_bindings_for_tests,
3250            },
3251        },
3252        error::{ErrorClass, ErrorOrigin},
3253        traits::{CanisterKind, Path},
3254        types::EntityTag,
3255        value::{InputValue, OutputValue},
3256    };
3257    use icydb_schema::{FieldSourceKey, ScalarType};
3258    use std::{cell::RefCell, collections::BTreeMap};
3259
3260    const STORE_PATH: &str = "session::write::mixed_relation_batch_tests::Store";
3261    const ENTITY_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node";
3262    const ID_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node::id";
3263    const PARENT_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node::parent_id";
3264    const CODE_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node::code";
3265    const ENTITY_NAME: &str = "MixedRelationNode";
3266    const ENTITY_TAG: EntityTag = EntityTag::new(94);
3267    const OTHER_ENTITY_SOURCE: &str = "session::write::mixed_relation_batch_tests::Other";
3268    const OTHER_ID_SOURCE: &str = "session::write::mixed_relation_batch_tests::Other::id";
3269    const OTHER_VALUE_SOURCE: &str = "session::write::mixed_relation_batch_tests::Other::value";
3270    const OTHER_NODE_SOURCE: &str = "session::write::mixed_relation_batch_tests::Other::node_id";
3271    const OTHER_ENTITY_NAME: &str = "MixedRelationOther";
3272    const OTHER_ENTITY_TAG: EntityTag = EntityTag::new(95);
3273    const CROSS_STORE_PATH: &str = "session::write::mixed_relation_batch_tests::OtherStore";
3274    const CROSS_ENTITY_SOURCE: &str = "session::write::mixed_relation_batch_tests::CrossStore";
3275    const CROSS_ID_SOURCE: &str = "session::write::mixed_relation_batch_tests::CrossStore::id";
3276    const CROSS_ENTITY_NAME: &str = "MixedCrossStore";
3277    const CROSS_ENTITY_TAG: EntityTag = EntityTag::new(2_000);
3278    const TYPED_DESCRIPTOR: TypedEntityDescriptor = TypedEntityDescriptor::new(
3279        ENTITY_SOURCE,
3280        &[ID_SOURCE],
3281        &[TypedFieldDescriptor::new(
3282            ID_SOURCE,
3283            TypedFieldType::Scalar(ScalarType::Nat64),
3284            false,
3285        )],
3286    );
3287
3288    fn batch_rows(results: &[crate::db::DynamicMutationResult]) -> Vec<Vec<OutputValue>> {
3289        results
3290            .iter()
3291            .flat_map(|result| result.rows.iter().cloned())
3292            .collect()
3293    }
3294
3295    struct TestCanister;
3296
3297    impl Path for TestCanister {
3298        const PATH: &'static str = "session::write::mixed_relation_batch_tests::Canister";
3299    }
3300
3301    impl CanisterKind for TestCanister {
3302        const COMMIT_MEMORY_ID: u8 = 47;
3303        const COMMIT_STABLE_KEY: &'static str = "icydb.mixed_relation_batch_tests.commit.v1";
3304        const STARTUP_MEMORY_ID: u8 = 50;
3305        const STARTUP_STABLE_KEY: &'static str =
3306            "icydb.mixed_relation_batch_tests.startup.control.v1";
3307        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 48;
3308        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
3309            "icydb.mixed_relation_batch_tests.integrity.progress.v1";
3310    }
3311
3312    thread_local! {
3313        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
3314        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
3315        static SCHEMA_STORE: RefCell<SchemaStore> =
3316            const { RefCell::new(SchemaStore::init_heap()) };
3317        static CROSS_DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
3318        static CROSS_INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
3319        static CROSS_SCHEMA_STORE: RefCell<SchemaStore> =
3320            const { RefCell::new(SchemaStore::init_heap()) };
3321        static STORE_REGISTRY: StoreRegistry = {
3322            let mut registry = StoreRegistry::new();
3323            registry.register_store(
3324                STORE_PATH,
3325                &DATA_STORE,
3326                &INDEX_STORE,
3327                &SCHEMA_STORE,
3328                StoreAllocationIdentities::absent(),
3329                StoreRuntimeStorageCapabilities::heap(),
3330            ).expect("mixed relation test store should register");
3331            registry.register_store(
3332                CROSS_STORE_PATH,
3333                &CROSS_DATA_STORE,
3334                &CROSS_INDEX_STORE,
3335                &CROSS_SCHEMA_STORE,
3336                StoreAllocationIdentities::absent(),
3337                StoreRuntimeStorageCapabilities::heap(),
3338            ).expect("cross-store test store should register");
3339            registry
3340        };
3341    }
3342
3343    fn source_key(source: &str) -> FieldSourceKey {
3344        FieldSourceKey::try_new(source).expect("mixed relation field source should admit")
3345    }
3346
3347    fn relation_snapshot() -> PersistedSchemaSnapshot {
3348        let fields = vec![
3349            PersistedFieldSnapshot::new_initial(
3350                FieldId::new(1),
3351                "id".to_string(),
3352                SchemaFieldSlot::new(0),
3353                AcceptedFieldKind::Nat64,
3354                Vec::new(),
3355                false,
3356                SchemaInsertDefault::None,
3357                FieldStorageDecode::ByKind,
3358                LeafCodec::Scalar(ScalarCodec::Nat64),
3359            ),
3360            PersistedFieldSnapshot::new_initial(
3361                FieldId::new(2),
3362                "parent_id".to_string(),
3363                SchemaFieldSlot::new(1),
3364                AcceptedFieldKind::Nat64,
3365                Vec::new(),
3366                true,
3367                SchemaInsertDefault::None,
3368                FieldStorageDecode::ByKind,
3369                LeafCodec::Scalar(ScalarCodec::Nat64),
3370            ),
3371            PersistedFieldSnapshot::new_initial(
3372                FieldId::new(3),
3373                "code".to_string(),
3374                SchemaFieldSlot::new(2),
3375                AcceptedFieldKind::Nat64,
3376                Vec::new(),
3377                false,
3378                SchemaInsertDefault::None,
3379                FieldStorageDecode::ByKind,
3380                LeafCodec::Scalar(ScalarCodec::Nat64),
3381            ),
3382        ];
3383        let relation = PersistedRelationEdgeSnapshot::new(
3384            RelationId::new(1).expect("mixed relation identity should be non-zero"),
3385            "parent".to_string(),
3386            ENTITY_SOURCE.to_string(),
3387            vec![FieldId::new(2)],
3388        );
3389        let snapshot = PersistedSchemaSnapshot::new_with_indexes(
3390            SchemaVersion::initial(),
3391            ENTITY_SOURCE.to_string(),
3392            ENTITY_NAME.to_string(),
3393            FieldId::new(1),
3394            SchemaRowLayout::initial(
3395                fields
3396                    .iter()
3397                    .map(|field| (field.id(), field.slot()))
3398                    .collect(),
3399            ),
3400            fields,
3401            vec![PersistedIndexSnapshot::new(
3402                SchemaIndexId::new(1).expect("mixed unique index identity should be non-zero"),
3403                1,
3404                "by_code".to_string(),
3405                STORE_PATH.to_string(),
3406                true,
3407                PersistedIndexKeySnapshot::FieldPath(vec![PersistedIndexFieldPathSnapshot::new(
3408                    FieldId::new(3),
3409                    SchemaFieldSlot::new(2),
3410                    vec!["code".to_string()],
3411                    AcceptedFieldKind::Nat64,
3412                    false,
3413                )]),
3414                None,
3415            )],
3416        )
3417        .with_relations(vec![relation]);
3418        let constraints = AcceptedConstraintCatalog::initial(
3419            snapshot.fields(),
3420            snapshot.indexes(),
3421            snapshot.relations(),
3422        )
3423        .expect("mixed relation constraints should close");
3424        snapshot.with_constraint_catalog(constraints)
3425    }
3426
3427    fn other_snapshot() -> PersistedSchemaSnapshot {
3428        let fields = vec![
3429            PersistedFieldSnapshot::new_initial(
3430                FieldId::new(1),
3431                "id".to_string(),
3432                SchemaFieldSlot::new(0),
3433                AcceptedFieldKind::Nat64,
3434                Vec::new(),
3435                false,
3436                SchemaInsertDefault::None,
3437                FieldStorageDecode::ByKind,
3438                LeafCodec::Scalar(ScalarCodec::Nat64),
3439            ),
3440            PersistedFieldSnapshot::new_initial(
3441                FieldId::new(2),
3442                "value".to_string(),
3443                SchemaFieldSlot::new(1),
3444                AcceptedFieldKind::Nat64,
3445                Vec::new(),
3446                false,
3447                SchemaInsertDefault::None,
3448                FieldStorageDecode::ByKind,
3449                LeafCodec::Scalar(ScalarCodec::Nat64),
3450            ),
3451            PersistedFieldSnapshot::new_initial(
3452                FieldId::new(3),
3453                "node_id".to_string(),
3454                SchemaFieldSlot::new(2),
3455                AcceptedFieldKind::Nat64,
3456                Vec::new(),
3457                true,
3458                SchemaInsertDefault::None,
3459                FieldStorageDecode::ByKind,
3460                LeafCodec::Scalar(ScalarCodec::Nat64),
3461            ),
3462        ];
3463        let relation = PersistedRelationEdgeSnapshot::new(
3464            RelationId::new(1).expect("cross-entity relation identity should be non-zero"),
3465            "node".to_string(),
3466            ENTITY_SOURCE.to_string(),
3467            vec![FieldId::new(3)],
3468        );
3469        let snapshot = PersistedSchemaSnapshot::new(
3470            SchemaVersion::initial(),
3471            OTHER_ENTITY_SOURCE.to_string(),
3472            OTHER_ENTITY_NAME.to_string(),
3473            FieldId::new(1),
3474            SchemaRowLayout::initial(
3475                fields
3476                    .iter()
3477                    .map(|field| (field.id(), field.slot()))
3478                    .collect(),
3479            ),
3480            fields,
3481        )
3482        .with_relations(vec![relation]);
3483        let constraints = AcceptedConstraintCatalog::initial(
3484            snapshot.fields(),
3485            snapshot.indexes(),
3486            snapshot.relations(),
3487        )
3488        .expect("cross-entity relation constraints should close");
3489        snapshot.with_constraint_catalog(constraints)
3490    }
3491
3492    fn bounded_entity_snapshot(index: usize) -> PersistedSchemaSnapshot {
3493        let fields = vec![
3494            PersistedFieldSnapshot::new_initial(
3495                FieldId::new(1),
3496                "id".to_string(),
3497                SchemaFieldSlot::new(0),
3498                AcceptedFieldKind::Nat64,
3499                Vec::new(),
3500                false,
3501                SchemaInsertDefault::None,
3502                FieldStorageDecode::ByKind,
3503                LeafCodec::Scalar(ScalarCodec::Nat64),
3504            ),
3505            PersistedFieldSnapshot::new_initial_with_write_policy(
3506                FieldId::new(2),
3507                "updated_at".to_string(),
3508                SchemaFieldSlot::new(1),
3509                AcceptedFieldKind::Timestamp,
3510                Vec::new(),
3511                false,
3512                SchemaInsertDefault::None,
3513                SchemaFieldWritePolicy::from_model_policies(
3514                    None,
3515                    Some(FieldWriteManagement::UpdatedAt),
3516                ),
3517                FieldStorageDecode::ByKind,
3518                LeafCodec::Scalar(ScalarCodec::Timestamp),
3519            ),
3520        ];
3521        PersistedSchemaSnapshot::new(
3522            SchemaVersion::initial(),
3523            format!("session::write::mixed_relation_batch_tests::Bounded{index}"),
3524            format!("MixedBounded{index}"),
3525            FieldId::new(1),
3526            SchemaRowLayout::initial(
3527                fields
3528                    .iter()
3529                    .map(|field| (field.id(), field.slot()))
3530                    .collect(),
3531            ),
3532            fields,
3533        )
3534    }
3535
3536    fn cross_store_snapshot() -> PersistedSchemaSnapshot {
3537        let field = PersistedFieldSnapshot::new_initial(
3538            FieldId::new(1),
3539            "id".to_string(),
3540            SchemaFieldSlot::new(0),
3541            AcceptedFieldKind::Nat64,
3542            Vec::new(),
3543            false,
3544            SchemaInsertDefault::None,
3545            FieldStorageDecode::ByKind,
3546            LeafCodec::Scalar(ScalarCodec::Nat64),
3547        );
3548        PersistedSchemaSnapshot::new(
3549            SchemaVersion::initial(),
3550            CROSS_ENTITY_SOURCE.to_string(),
3551            CROSS_ENTITY_NAME.to_string(),
3552            FieldId::new(1),
3553            SchemaRowLayout::initial(vec![(field.id(), field.slot())]),
3554            vec![field],
3555        )
3556    }
3557
3558    fn initialize() -> DbSession<TestCanister> {
3559        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
3560        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
3561        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
3562        CROSS_DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
3563        CROSS_INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
3564        CROSS_SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
3565        let session = DbSession::<TestCanister>::new(
3566            &STORE_REGISTRY,
3567            &crate::db::RequestExecutionRoot::__new_runtime_root(),
3568        );
3569        session
3570            .db
3571            .drive_startup_recovery_page()
3572            .expect("mixed relation database should initialize");
3573        let mut snapshots = BTreeMap::from([
3574            (ENTITY_TAG, relation_snapshot()),
3575            (OTHER_ENTITY_TAG, other_snapshot()),
3576        ]);
3577        let mut field_bindings = BTreeMap::from([
3578            ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
3579            ((ENTITY_TAG, source_key(PARENT_SOURCE)), FieldId::new(2)),
3580            ((ENTITY_TAG, source_key(CODE_SOURCE)), FieldId::new(3)),
3581            (
3582                (OTHER_ENTITY_TAG, source_key(OTHER_ID_SOURCE)),
3583                FieldId::new(1),
3584            ),
3585            (
3586                (OTHER_ENTITY_TAG, source_key(OTHER_VALUE_SOURCE)),
3587                FieldId::new(2),
3588            ),
3589            (
3590                (OTHER_ENTITY_TAG, source_key(OTHER_NODE_SOURCE)),
3591                FieldId::new(3),
3592            ),
3593        ]);
3594        for index in 0..65 {
3595            let tag = EntityTag::new(1_000 + index as u64);
3596            snapshots.insert(tag, bounded_entity_snapshot(index));
3597            field_bindings.insert(
3598                (
3599                    tag,
3600                    source_key(
3601                        format!("session::write::mixed_relation_batch_tests::Bounded{index}::id")
3602                            .as_str(),
3603                    ),
3604                ),
3605                FieldId::new(1),
3606            );
3607            field_bindings.insert(
3608                (
3609                    tag,
3610                    source_key(
3611                        format!(
3612                            "session::write::mixed_relation_batch_tests::Bounded{index}::updated_at"
3613                        )
3614                        .as_str(),
3615                    ),
3616                ),
3617                FieldId::new(2),
3618            );
3619        }
3620        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
3621            STORE_PATH,
3622            AcceptedSchemaRevision::INITIAL,
3623            snapshots,
3624            field_bindings,
3625        );
3626        let store = session
3627            .db
3628            .store_handle(STORE_PATH)
3629            .expect("mixed relation store should resolve");
3630        crate::db::commit::publish_accepted_schema_candidate(
3631            STORE_PATH,
3632            store,
3633            AcceptedSchemaRevision::NONE,
3634            &candidate,
3635        )
3636        .expect("mixed relation candidate should publish");
3637        let cross_candidate = accepted_schema_candidate_with_field_bindings_for_tests(
3638            CROSS_STORE_PATH,
3639            AcceptedSchemaRevision::INITIAL,
3640            BTreeMap::from([(CROSS_ENTITY_TAG, cross_store_snapshot())]),
3641            BTreeMap::from([(
3642                (CROSS_ENTITY_TAG, source_key(CROSS_ID_SOURCE)),
3643                FieldId::new(1),
3644            )]),
3645        );
3646        let cross_store = session
3647            .db
3648            .store_handle(CROSS_STORE_PATH)
3649            .expect("cross-store fixture should resolve");
3650        crate::db::commit::publish_accepted_schema_candidate(
3651            CROSS_STORE_PATH,
3652            cross_store,
3653            AcceptedSchemaRevision::NONE,
3654            &cross_candidate,
3655        )
3656        .expect("cross-store candidate should publish");
3657        session
3658    }
3659
3660    fn patch(id: Option<u64>, parent: Option<u64>, code: Option<u64>) -> DynamicStructuralPatch {
3661        let mut fields = Vec::new();
3662        if let Some(id) = id {
3663            fields.push((
3664                "id".to_string(),
3665                DynamicWriteCell::Value(InputValue::nat64(id)),
3666            ));
3667        }
3668        fields.push((
3669            "parent_id".to_string(),
3670            parent.map_or(DynamicWriteCell::Null, |parent| {
3671                DynamicWriteCell::Value(InputValue::nat64(parent))
3672            }),
3673        ));
3674        if let Some(code) = code {
3675            fields.push((
3676                "code".to_string(),
3677                DynamicWriteCell::Value(InputValue::nat64(code)),
3678            ));
3679        }
3680        DynamicStructuralPatch::new(fields)
3681    }
3682
3683    fn insert(id: u64, parent: Option<u64>) -> DynamicMutation {
3684        insert_with_code(id, parent, id)
3685    }
3686
3687    fn insert_with_code(id: u64, parent: Option<u64>, code: u64) -> DynamicMutation {
3688        DynamicMutation::Insert {
3689            entity: ENTITY_NAME.to_string(),
3690            patch: patch(Some(id), parent, Some(code)),
3691        }
3692    }
3693
3694    fn update_parent(id: u64, parent: Option<u64>) -> DynamicMutation {
3695        DynamicMutation::Update {
3696            entity: ENTITY_NAME.to_string(),
3697            key: InputValue::nat64(id),
3698            patch: patch(None, parent, None),
3699        }
3700    }
3701
3702    fn update_code(id: u64, code: u64) -> DynamicMutation {
3703        DynamicMutation::Update {
3704            entity: ENTITY_NAME.to_string(),
3705            key: InputValue::nat64(id),
3706            patch: DynamicStructuralPatch::new(vec![(
3707                "code".to_string(),
3708                DynamicWriteCell::Value(InputValue::nat64(code)),
3709            )]),
3710        }
3711    }
3712
3713    fn delete(id: u64) -> DynamicMutation {
3714        DynamicMutation::Delete {
3715            entity: ENTITY_NAME.to_string(),
3716            key: InputValue::nat64(id),
3717        }
3718    }
3719
3720    fn expected_row(id: u64, parent: Option<u64>) -> Vec<OutputValue> {
3721        expected_row_with_code(id, parent, id)
3722    }
3723
3724    fn expected_row_with_code(id: u64, parent: Option<u64>, code: u64) -> Vec<OutputValue> {
3725        vec![
3726            OutputValue::nat64(id),
3727            parent.map_or_else(OutputValue::null, OutputValue::nat64),
3728            OutputValue::nat64(code),
3729        ]
3730    }
3731
3732    fn other_patch(id: Option<u64>, value: u64) -> DynamicStructuralPatch {
3733        other_patch_with_node(id, value, None)
3734    }
3735
3736    fn other_patch_with_node(
3737        id: Option<u64>,
3738        value: u64,
3739        node_id: Option<u64>,
3740    ) -> DynamicStructuralPatch {
3741        let mut fields = Vec::new();
3742        if let Some(id) = id {
3743            fields.push((
3744                "id".to_string(),
3745                DynamicWriteCell::Value(InputValue::nat64(id)),
3746            ));
3747        }
3748        fields.push((
3749            "value".to_string(),
3750            DynamicWriteCell::Value(InputValue::nat64(value)),
3751        ));
3752        fields.push((
3753            "node_id".to_string(),
3754            node_id.map_or(DynamicWriteCell::Null, |node_id| {
3755                DynamicWriteCell::Value(InputValue::nat64(node_id))
3756            }),
3757        ));
3758        DynamicStructuralPatch::new(fields)
3759    }
3760
3761    fn assert_relation_violation(error: &crate::error::InternalError) {
3762        assert!(error.diagnostic_facts().contains(&(
3763            icydb_diagnostic_code::DiagnosticFactTag::ConstraintKind,
3764            icydb_diagnostic_code::DiagnosticConstraintKind::Relation.raw(),
3765        )));
3766    }
3767
3768    #[test]
3769    fn live_pages_resume_mixed_projection_from_authenticated_hidden_order_values() {
3770        let session = initialize();
3771        session
3772            .execute_trusted_dynamic_mutation_batch(vec![
3773                insert_with_code(1, None, 10),
3774                insert_with_code(2, Some(1), 20),
3775                insert_with_code(3, None, 30),
3776            ])
3777            .expect("live-page rows should insert");
3778        let query = DynamicQuery::new(ENTITY_NAME)
3779            .select(["id"])
3780            .order_by(desc("code"));
3781
3782        let first = session
3783            .execute_public_live_page(&query, None)
3784            .expect("initial live page should execute");
3785        assert_eq!(
3786            first.rows,
3787            vec![vec![OutputValue::nat64(3)], vec![OutputValue::nat64(2)]]
3788        );
3789        let cursor = first
3790            .continuation
3791            .as_deref()
3792            .expect("unreturned matching row should produce continuation");
3793        let second = session
3794            .execute_public_live_page(&query, Some(cursor))
3795            .expect("authenticated live continuation should resume");
3796        assert_eq!(second.rows, vec![vec![OutputValue::nat64(1)]]);
3797        assert_eq!(second.continuation, None);
3798
3799        let total_limit = session
3800            .execute_public_live_page(&query.clone().limit(2), None)
3801            .expect("total live-page limit should execute");
3802        assert_eq!(
3803            total_limit.rows,
3804            vec![vec![OutputValue::nat64(3)], vec![OutputValue::nat64(2)]],
3805        );
3806        assert_eq!(
3807            total_limit.continuation, None,
3808            "query LIMIT is a total traversal window rather than a page size",
3809        );
3810
3811        let three_row_window = query.clone().limit(3);
3812        let limited_first = session
3813            .execute_public_live_page(&three_row_window, None)
3814            .expect("first total-window page should execute");
3815        let limited_cursor = limited_first
3816            .continuation
3817            .as_deref()
3818            .expect("a partially consumed total window should continue");
3819        let limited_second = session
3820            .execute_public_live_page(&three_row_window, Some(limited_cursor))
3821            .expect("remaining total window should preserve the plan signature");
3822        assert_eq!(limited_second.rows, vec![vec![OutputValue::nat64(1)]]);
3823        assert_eq!(limited_second.continuation, None);
3824
3825        let mixed_order = DynamicQuery::new(ENTITY_NAME)
3826            .select(["id"])
3827            .order_by(desc("parent_id"))
3828            .order_by(asc("id"));
3829        let mixed_first = session
3830            .execute_trusted_live_page(&mixed_order, None)
3831            .expect("mixed-direction nullable order should execute");
3832        assert_eq!(
3833            mixed_first.rows,
3834            vec![vec![OutputValue::nat64(2)], vec![OutputValue::nat64(1)]],
3835        );
3836        let mixed_cursor = mixed_first
3837            .continuation
3838            .as_deref()
3839            .expect("duplicate null order values should retain continuation");
3840        let mixed_second = session
3841            .execute_trusted_live_page(&mixed_order, Some(mixed_cursor))
3842            .expect("mixed-direction nullable order should resume");
3843        assert_eq!(mixed_second.rows, vec![vec![OutputValue::nat64(3)]]);
3844        assert_eq!(mixed_second.continuation, None);
3845
3846        let mismatched_window = session
3847            .execute_public_live_page(&query.clone().limit(3), Some(cursor))
3848            .expect_err("a changed total limit must invalidate the continuation");
3849        assert_eq!(
3850            mismatched_window.diagnostic_code(),
3851            icydb_diagnostic_code::DiagnosticCode::QueryInvalidContinuationCursor,
3852        );
3853
3854        let mut tampered = cursor.as_bytes().to_vec();
3855        let last = tampered.len().saturating_sub(1);
3856        tampered[last] = if tampered[last] == b'0' { b'1' } else { b'0' };
3857        let tampered = String::from_utf8(tampered).expect("hex cursor should remain UTF-8");
3858        let error = session
3859            .execute_public_live_page(&query, Some(tampered.as_str()))
3860            .expect_err("tampered cursor must fail closed");
3861        assert_eq!(
3862            error.diagnostic_code(),
3863            icydb_diagnostic_code::DiagnosticCode::QueryInvalidContinuationCursor,
3864        );
3865    }
3866
3867    #[test]
3868    fn attributed_live_page_preserves_result_database_and_retained_metrics() {
3869        let session = initialize();
3870        session
3871            .execute_trusted_dynamic_mutation_batch(vec![
3872                insert_with_code(1, None, 10),
3873                insert_with_code(2, Some(1), 20),
3874                insert_with_code(3, None, 30),
3875            ])
3876            .expect("attributed live-page rows should insert");
3877        let query = DynamicQuery::new(ENTITY_NAME)
3878            .select(["id"])
3879            .order_by(desc("code"));
3880        let ordinary = session
3881            .execute_public_live_page(&query, None)
3882            .expect("ordinary live page should execute");
3883        let binding = session
3884            .issue_typed_entity_binding(&TYPED_DESCRIPTOR)
3885            .expect("attributed typed binding should issue");
3886        let proof_before = session
3887            .capture_read_set_revision_proof(&[ENTITY_NAME])
3888            .expect("read-set proof should capture before attributed execution");
3889        crate::metrics::metrics_reset_all();
3890        let metrics_before = crate::metrics::compact_metrics_report(None);
3891
3892        let attributed = session
3893            .execute_public_live_page_with_attribution(&query, None)
3894            .expect("attributed live page should execute");
3895        let typed_attributed = session
3896            .execute_public_live_page_with_attribution_for_typed_binding(&binding, &query, None)
3897            .expect("attributed typed live page should execute")
3898            .expect("attributed typed binding should remain current");
3899
3900        let metrics_after = crate::metrics::compact_metrics_report(None);
3901        let proof_after = session
3902            .capture_read_set_revision_proof(&[ENTITY_NAME])
3903            .expect("read-set proof should capture after attributed execution");
3904        assert_eq!(attributed.result, ordinary);
3905        assert_eq!(typed_attributed.result, ordinary);
3906        assert_eq!(
3907            attributed.attribution.rows_scanned,
3908            ordinary.work.entries_visited
3909        );
3910        assert_eq!(
3911            attributed.attribution.rows_emitted,
3912            u64::from(ordinary.row_count),
3913        );
3914        assert_eq!(
3915            attributed.attribution.plan_cache,
3916            crate::db::ReadPlanCacheOutcome::Hit,
3917        );
3918        assert_eq!(attributed.attribution.total_local_instructions, 0);
3919        assert_eq!(attributed.attribution.engine_local_instructions, 0);
3920        assert_eq!(attributed.attribution.response_decode_local_instructions, 0,);
3921        assert_eq!(proof_after, proof_before);
3922        assert_eq!(
3923            metrics_after.requested_window_start_ms(),
3924            metrics_before.requested_window_start_ms(),
3925        );
3926        assert_eq!(
3927            metrics_after.active_window_start_ms(),
3928            metrics_before.active_window_start_ms(),
3929        );
3930        assert_eq!(
3931            metrics_after.entity_counters(),
3932            metrics_before.entity_counters(),
3933        );
3934        let counters_before = metrics_before
3935            .counters()
3936            .expect("reset compact metrics should report the active window");
3937        let counters_after = metrics_after
3938            .counters()
3939            .expect("attributed reads should preserve the active metrics window");
3940        assert_eq!(counters_after.metrics(), counters_before.metrics());
3941        assert_eq!(
3942            counters_after.window_start_ms(),
3943            counters_before.window_start_ms(),
3944        );
3945    }
3946
3947    #[test]
3948    fn live_pages_resume_across_changed_output_work_envelopes() {
3949        let session = initialize();
3950        session
3951            .execute_trusted_dynamic_mutation_batch(vec![
3952                insert(1, None),
3953                insert(2, None),
3954                insert(3, None),
3955            ])
3956            .expect("output-envelope rows should insert");
3957        let query = DynamicQuery::new(ENTITY_NAME)
3958            .select(["id"])
3959            .order_by(desc("code"));
3960        let first = session
3961            .execute_trusted_live_page_with_result_bytes_limit_for_tests(&query, None, 32)
3962            .expect("small output envelope should publish the first bounded page");
3963        assert_eq!(first.rows, vec![vec![OutputValue::nat64(3)]]);
3964        let continuation = first
3965            .continuation
3966            .expect("small output envelope should leave authenticated progress");
3967
3968        let second = session
3969            .execute_trusted_live_page_with_result_bytes_limit_for_tests(
3970                &query,
3971                Some(continuation.as_str()),
3972                64,
3973            )
3974            .unwrap_or_else(|error| {
3975                panic!(
3976                    "larger output envelope should resume the same query: {error:?}, facts={:?}",
3977                    error.diagnostic_facts(),
3978                )
3979            });
3980        assert_eq!(
3981            second.rows,
3982            vec![vec![OutputValue::nat64(2)], vec![OutputValue::nat64(1)]]
3983        );
3984        let second_continuation = second
3985            .continuation
3986            .as_deref()
3987            .expect("an exact-full page still needs to prove physical exhaustion");
3988        assert_ne!(first.work.envelope_identity, second.work.envelope_identity);
3989
3990        let terminal = session
3991            .execute_trusted_live_page_with_result_bytes_limit_for_tests(
3992                &query,
3993                Some(second_continuation),
3994                48,
3995            )
3996            .expect("a third finite envelope should prove exhaustion without replaying rows");
3997        assert!(terminal.rows.is_empty());
3998        assert_eq!(terminal.continuation, None);
3999        assert_ne!(
4000            second.work.envelope_identity,
4001            terminal.work.envelope_identity
4002        );
4003
4004        assert_eq!(
4005            [first.rows, second.rows, terminal.rows].concat(),
4006            vec![
4007                vec![OutputValue::nat64(3)],
4008                vec![OutputValue::nat64(2)],
4009                vec![OutputValue::nat64(1)],
4010            ]
4011        );
4012    }
4013
4014    #[test]
4015    fn distinct_live_pages_resume_adjacent_groups_and_global_replay_end_to_end() {
4016        let session = initialize();
4017        session
4018            .execute_trusted_dynamic_mutation_batch(vec![
4019                insert(1, None),
4020                insert(2, None),
4021                insert(3, Some(1)),
4022                insert(4, Some(2)),
4023                insert(5, Some(1)),
4024                insert(6, Some(3)),
4025                insert(7, Some(2)),
4026            ])
4027            .expect("DISTINCT continuation rows should insert atomically");
4028
4029        let adjacent = DynamicQuery::new(ENTITY_NAME)
4030            .select(["parent_id"])
4031            .order_by(asc("parent_id"))
4032            .order_by(asc("id"))
4033            .distinct_for_internal_execution();
4034        let global = DynamicQuery::new(ENTITY_NAME)
4035            .select(["parent_id"])
4036            .order_by(asc("id"))
4037            .distinct_for_internal_execution();
4038
4039        let traverse = |query: &DynamicQuery, strategy: &str| {
4040            let mut continuation = None;
4041            let mut rows = Vec::new();
4042            let mut cursors = std::collections::BTreeSet::new();
4043            let mut pages = 0_u32;
4044            let mut entries_visited = 0_u64;
4045            loop {
4046                let page = session
4047                    .execute_trusted_live_page(query, continuation.as_deref())
4048                    .unwrap_or_else(|error| {
4049                        panic!("{strategy} DISTINCT page should execute: {error:?}")
4050                    });
4051                pages = pages.saturating_add(1);
4052                entries_visited = entries_visited.saturating_add(page.work.entries_visited);
4053                assert_eq!(page.row_count as usize, page.rows.len());
4054                assert_eq!(page.work.result_rows, page.row_count);
4055                rows.extend(page.rows);
4056                let Some(cursor) = page.continuation else {
4057                    break;
4058                };
4059                assert!(
4060                    cursors.insert(cursor.clone()),
4061                    "{strategy} DISTINCT continuation must advance monotonically",
4062                );
4063                continuation = Some(cursor);
4064                assert!(pages < 8, "{strategy} DISTINCT traversal must terminate");
4065            }
4066
4067            (rows, pages, entries_visited)
4068        };
4069
4070        let expected = vec![
4071            vec![OutputValue::null()],
4072            vec![OutputValue::nat64(1)],
4073            vec![OutputValue::nat64(2)],
4074            vec![OutputValue::nat64(3)],
4075        ];
4076        let (adjacent_rows, adjacent_pages, adjacent_entries) = traverse(&adjacent, "adjacent");
4077        let (global_rows, global_pages, global_entries) = traverse(&global, "global");
4078
4079        assert_eq!(adjacent_rows, expected);
4080        assert_eq!(global_rows, expected);
4081        assert_eq!(adjacent_pages, 2);
4082        assert_eq!(global_pages, 2);
4083        assert!(adjacent_entries > 0);
4084        assert!(global_entries > 0);
4085    }
4086
4087    #[test]
4088    fn selective_live_pages_publish_monotonic_empty_physical_progress() {
4089        let session = initialize();
4090        session
4091            .execute_trusted_dynamic_mutation_batch(
4092                (1..=9)
4093                    .map(|id| {
4094                        let parent = match id {
4095                            1 => Some(2),
4096                            9 => Some(1),
4097                            _ => None,
4098                        };
4099                        insert(id, parent)
4100                    })
4101                    .collect(),
4102            )
4103            .expect("selective live-page rows should insert");
4104        let query = DynamicQuery::new(ENTITY_NAME)
4105            .select(["id"])
4106            .filter(FilterExpr::eq("parent_id", 1_u64))
4107            .order_by(asc("id"))
4108            .limit(1);
4109
4110        let first = session
4111            .execute_trusted_live_page(&query, None)
4112            .expect("first selective page should stop with physical progress");
4113        assert!(first.rows.is_empty());
4114        assert_eq!(first.work.entries_visited, 4);
4115        let first_cursor = first
4116            .continuation
4117            .expect("filtered physical progress must return a continuation");
4118
4119        let second = session
4120            .execute_trusted_live_page(&query, Some(first_cursor.as_str()))
4121            .expect("second selective page should resume after the first physical frontier");
4122        assert!(second.rows.is_empty());
4123        assert_eq!(second.work.entries_visited, 4);
4124        let second_cursor = second
4125            .continuation
4126            .expect("second filtered frontier must remain resumable");
4127        assert_ne!(second_cursor, first_cursor);
4128
4129        let third = session
4130            .execute_trusted_live_page(&query, Some(second_cursor.as_str()))
4131            .expect("final selective page should return the late match");
4132        assert_eq!(third.rows, vec![vec![OutputValue::nat64(9)]]);
4133        assert_eq!(third.work.entries_visited, 1);
4134        assert_eq!(third.continuation, None);
4135
4136        let descending = DynamicQuery::new(ENTITY_NAME)
4137            .select(["id"])
4138            .filter(FilterExpr::eq("parent_id", 2_u64))
4139            .order_by(desc("id"))
4140            .limit(1);
4141        let descending_first = session
4142            .execute_trusted_live_page(&descending, None)
4143            .expect("descending selective page should stop with physical progress");
4144        assert!(descending_first.rows.is_empty());
4145        let descending_first_cursor = descending_first
4146            .continuation
4147            .expect("descending filtered progress must return a continuation");
4148        let descending_second = session
4149            .execute_trusted_live_page(&descending, Some(descending_first_cursor.as_str()))
4150            .expect("descending progress should resume after its physical frontier");
4151        assert!(descending_second.rows.is_empty());
4152        let descending_second_cursor = descending_second
4153            .continuation
4154            .expect("descending second frontier must remain resumable");
4155        assert_ne!(descending_second_cursor, descending_first_cursor);
4156        let descending_third = session
4157            .execute_trusted_live_page(&descending, Some(descending_second_cursor.as_str()))
4158            .expect("descending final page should return the late match");
4159        assert_eq!(descending_third.rows, vec![vec![OutputValue::nat64(1)]]);
4160        assert_eq!(descending_third.continuation, None);
4161    }
4162
4163    #[test]
4164    fn accepted_relation_edges_drive_catalog_and_describe_introspection() {
4165        let session = initialize();
4166        let entities = session
4167            .show_entities()
4168            .expect("accepted entity catalog should resolve");
4169        let source = entities
4170            .iter()
4171            .find(|entity| entity.entity_name() == ENTITY_NAME)
4172            .expect("relation source should be listed");
4173        assert_eq!(source.relations(), 1);
4174
4175        let description = session
4176            .try_describe_entity_by_name(ENTITY_NAME)
4177            .expect("accepted relation source should describe");
4178        let [relation] = description.relations() else {
4179            panic!("accepted relation edge should produce one relation row");
4180        };
4181        assert_eq!(relation.field(), "parent_id");
4182        assert_eq!(relation.target_path(), ENTITY_SOURCE);
4183        assert_eq!(relation.target_entity_name(), ENTITY_NAME);
4184        assert_eq!(relation.target_store_path(), STORE_PATH);
4185        assert_eq!(
4186            relation.cardinality(),
4187            crate::db::EntityRelationCardinality::Single,
4188        );
4189    }
4190
4191    #[test]
4192    fn mixed_relation_validation_uses_the_complete_final_row_overlay() {
4193        let session = initialize();
4194        session
4195            .execute_trusted_dynamic_mutation_batch(vec![insert(1, None), insert(2, Some(1))])
4196            .expect("the initial relation should commit");
4197
4198        let blocked = session
4199            .execute_trusted_dynamic_mutation(&delete(1))
4200            .expect_err("an unaffected committed source must block target deletion");
4201        assert_relation_violation(&blocked);
4202
4203        let deleted = session
4204            .execute_trusted_dynamic_mutation_batch(vec![delete(2), delete(1)])
4205            .expect("a source and its target should delete atomically");
4206        assert_eq!(
4207            batch_rows(&deleted),
4208            vec![expected_row(2, Some(1)), expected_row(1, None)],
4209        );
4210
4211        session
4212            .execute_trusted_dynamic_mutation_batch(vec![insert(3, None), insert(4, Some(3))])
4213            .expect("the update-away fixture should commit");
4214        let updated_away = session
4215            .execute_trusted_dynamic_mutation_batch(vec![update_parent(4, None), delete(3)])
4216            .expect("an updated final source may release a deleted target");
4217        assert_eq!(
4218            batch_rows(&updated_away),
4219            vec![expected_row(4, None), expected_row(3, None)],
4220        );
4221
4222        session
4223            .execute_trusted_dynamic_mutation_batch(vec![insert(5, None), insert(6, Some(5))])
4224            .expect("the retained-reference fixture should commit");
4225        let retained = session
4226            .execute_trusted_dynamic_mutation_batch(vec![update_parent(6, Some(5)), delete(5)])
4227            .expect_err("a final updated source must still block target deletion");
4228        assert_relation_violation(&retained);
4229
4230        session
4231            .execute_trusted_dynamic_mutation(&insert(7, None))
4232            .expect("the inserted-reference fixture target should commit");
4233        let inserted_reference = session
4234            .execute_trusted_dynamic_mutation_batch(vec![insert(8, Some(7)), delete(7)])
4235            .expect_err("a final inserted source must not reference a deleted target");
4236        assert_relation_violation(&inserted_reference);
4237
4238        let inserted_target = session
4239            .execute_trusted_dynamic_mutation_batch(vec![insert(10, Some(9)), insert(9, None)])
4240            .expect("an inserted relation should see its batch-final target");
4241        assert_eq!(
4242            batch_rows(&inserted_target),
4243            vec![expected_row(10, Some(9)), expected_row(9, None)],
4244        );
4245
4246        session
4247            .execute_trusted_dynamic_mutation(&insert(11, None))
4248            .expect("the updated-reference fixture source should commit");
4249        let updated_target = session
4250            .execute_trusted_dynamic_mutation_batch(vec![
4251                update_parent(11, Some(12)),
4252                insert(12, None),
4253            ])
4254            .expect("an updated relation should see its batch-final target");
4255        assert_eq!(
4256            batch_rows(&updated_target),
4257            vec![expected_row(11, Some(12)), expected_row(12, None)],
4258        );
4259    }
4260
4261    #[test]
4262    fn mixed_batch_commits_cross_entity_then_rejects_late_failures_atomically() {
4263        let session = initialize();
4264        session
4265            .execute_trusted_dynamic_mutation(&insert(1, None))
4266            .expect("the primary mixed fixture row should commit");
4267        session
4268            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
4269                entity: OTHER_ENTITY_NAME.to_string(),
4270                patch: other_patch(Some(1), 10),
4271            })
4272            .expect("the secondary mixed fixture row should commit");
4273
4274        let mixed_entity = session
4275            .execute_trusted_dynamic_mutation_batch(vec![
4276                update_code(1, 11),
4277                DynamicMutation::Update {
4278                    entity: OTHER_ENTITY_NAME.to_string(),
4279                    key: InputValue::nat64(1),
4280                    patch: other_patch(None, 11),
4281                },
4282            ])
4283            .expect("one atomic batch may span accepted entities in the same store");
4284        assert_eq!(
4285            batch_rows(&mixed_entity),
4286            vec![
4287                expected_row_with_code(1, None, 11),
4288                vec![
4289                    OutputValue::nat64(1),
4290                    OutputValue::nat64(11),
4291                    OutputValue::null(),
4292                ],
4293            ],
4294        );
4295
4296        let missing = session
4297            .execute_trusted_dynamic_mutation_batch(vec![update_code(1, 12), delete(99)])
4298            .expect_err("a late missing delete must reject the earlier staged update");
4299        assert_eq!(missing.class(), ErrorClass::NotFound);
4300
4301        session
4302            .execute_trusted_dynamic_mutation(&insert(2, None))
4303            .expect("the collision fixture should commit");
4304        let collision = session
4305            .execute_trusted_dynamic_mutation_batch(vec![update_code(1, 13), insert(2, None)])
4306            .expect_err("an insert collision must reject the earlier staged update");
4307        assert_eq!(collision.class(), ErrorClass::Conflict);
4308        let failures_unchanged = session
4309            .execute_trusted_dynamic_mutation(&update_code(1, 11))
4310            .expect("failed batches must preserve the original unique value");
4311        assert_eq!(failures_unchanged.affected_rows, 0);
4312
4313        let replaced = session
4314            .execute_trusted_dynamic_mutation_batch(vec![
4315                update_code(1, 14),
4316                DynamicMutation::Replace {
4317                    entity: ENTITY_NAME.to_string(),
4318                    key: InputValue::nat64(99),
4319                    patch: patch(None, None, Some(99)),
4320                },
4321            ])
4322            .expect("ordinary caller-key replace should insert its absent final row");
4323        assert_eq!(
4324            batch_rows(&replaced),
4325            vec![
4326                expected_row_with_code(1, None, 14),
4327                expected_row_with_code(99, None, 99),
4328            ],
4329        );
4330
4331        let unchanged = session
4332            .execute_trusted_dynamic_mutation(&update_code(1, 14))
4333            .expect("the successful mixed replace must publish its preceding update");
4334        assert_eq!(unchanged.affected_rows, 0);
4335        let other_unchanged = session
4336            .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
4337                entity: OTHER_ENTITY_NAME.to_string(),
4338                key: InputValue::nat64(1),
4339                patch: other_patch(None, 11),
4340            })
4341            .expect("the cross-entity commit must publish the secondary row");
4342        assert_eq!(other_unchanged.affected_rows, 0);
4343    }
4344
4345    #[test]
4346    fn structural_unknown_root_and_dotted_subpath_reject_before_commit() {
4347        let session = initialize();
4348        session
4349            .execute_trusted_dynamic_mutation_batch(vec![insert(1, None), insert(2, None)])
4350            .expect("structural rejection fixtures should commit");
4351
4352        let unknown_root = session
4353            .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
4354                entity: ENTITY_NAME.to_string(),
4355                key: InputValue::nat64(1),
4356                patch: DynamicStructuralPatch::new(vec![(
4357                    "missing".to_string(),
4358                    DynamicWriteCell::Value(InputValue::nat64(10)),
4359                )]),
4360            })
4361            .expect_err("an unknown structural root field must reject");
4362        assert_eq!(unknown_root.class(), ErrorClass::Unsupported);
4363        assert_eq!(unknown_root.origin(), ErrorOrigin::Executor);
4364        assert_eq!(
4365            unknown_root.diagnostic_code(),
4366            icydb_diagnostic_code::DiagnosticCode::RuntimeUnsupported,
4367        );
4368        assert!(unknown_root.diagnostic_facts().is_empty());
4369
4370        let dotted_subpath = session
4371            .execute_trusted_dynamic_mutation_batch(vec![
4372                update_code(1, 11),
4373                DynamicMutation::Update {
4374                    entity: ENTITY_NAME.to_string(),
4375                    key: InputValue::nat64(2),
4376                    patch: DynamicStructuralPatch::new(vec![(
4377                        "code.value".to_string(),
4378                        DynamicWriteCell::Value(InputValue::nat64(12)),
4379                    )]),
4380                },
4381            ])
4382            .expect_err("a dotted structural subpath must reject the complete batch");
4383        assert_eq!(dotted_subpath.class(), ErrorClass::Unsupported);
4384        assert_eq!(dotted_subpath.origin(), ErrorOrigin::Executor);
4385        assert_eq!(
4386            dotted_subpath.diagnostic_code(),
4387            icydb_diagnostic_code::DiagnosticCode::RuntimeUnsupported,
4388        );
4389        assert!(dotted_subpath.diagnostic_facts().is_empty());
4390
4391        let unchanged = session
4392            .execute_trusted_dynamic_mutation(&update_code(1, 1))
4393            .expect("the rejected batch must preserve the earlier row");
4394        assert_eq!(unchanged.affected_rows, 0);
4395
4396        let whole_field = session
4397            .execute_trusted_dynamic_mutation(&update_code(2, 12))
4398            .expect("a complete root-field update must remain supported");
4399        assert_eq!(whole_field.affected_rows, 1);
4400        assert_eq!(whole_field.rows, vec![expected_row_with_code(2, None, 12)]);
4401    }
4402
4403    #[test]
4404    fn cross_entity_relations_observe_one_complete_final_overlay() {
4405        let session = initialize();
4406        let inserted = session
4407            .execute_trusted_dynamic_mutation_batch(vec![
4408                DynamicMutation::Insert {
4409                    entity: OTHER_ENTITY_NAME.to_string(),
4410                    patch: other_patch_with_node(Some(20), 200, Some(42)),
4411                },
4412                insert(42, None),
4413            ])
4414            .expect("a source may precede its same-batch target in another entity");
4415        assert_eq!(inserted.len(), 2);
4416
4417        session
4418            .execute_trusted_dynamic_mutation_batch(vec![
4419                delete(42),
4420                DynamicMutation::Delete {
4421                    entity: OTHER_ENTITY_NAME.to_string(),
4422                    key: InputValue::nat64(20),
4423                },
4424            ])
4425            .expect("a target and cross-entity source may delete in either request order");
4426
4427        session
4428            .execute_trusted_dynamic_mutation_batch(vec![
4429                insert(43, None),
4430                DynamicMutation::Insert {
4431                    entity: OTHER_ENTITY_NAME.to_string(),
4432                    patch: other_patch_with_node(Some(21), 210, Some(43)),
4433                },
4434            ])
4435            .expect("the retained cross-entity relation fixture should commit");
4436        let blocked = session
4437            .execute_trusted_dynamic_mutation_batch(vec![delete(43)])
4438            .expect_err("a retained source in another entity must protect its target");
4439        assert_relation_violation(&blocked);
4440    }
4441
4442    #[test]
4443    fn mixed_batch_admits_64_entities_with_one_timestamp_and_rejects_the_65th() {
4444        let session = initialize();
4445        let requests = (0..64)
4446            .map(|index| DynamicMutation::Insert {
4447                entity: format!("MixedBounded{index}"),
4448                patch: DynamicStructuralPatch::new(vec![(
4449                    "id".to_string(),
4450                    DynamicWriteCell::Value(InputValue::nat64(1)),
4451                )]),
4452            })
4453            .collect();
4454        let admitted = session
4455            .execute_trusted_dynamic_mutation_batch(requests)
4456            .expect("exactly 64 same-store entities should admit");
4457        assert_eq!(admitted.len(), 64);
4458        let timestamps = admitted
4459            .iter()
4460            .map(|result| {
4461                result
4462                    .rows
4463                    .first()
4464                    .and_then(|row| row.get(1))
4465                    .expect("every bounded entity should return its managed timestamp")
4466            })
4467            .collect::<Vec<_>>();
4468        assert!(timestamps.windows(2).all(|pair| pair[0] == pair[1]));
4469
4470        let over_limit = (0..65)
4471            .map(|index| DynamicMutation::Insert {
4472                entity: format!("MixedBounded{index}"),
4473                patch: DynamicStructuralPatch::new(vec![(
4474                    "id".to_string(),
4475                    DynamicWriteCell::Value(InputValue::nat64(2)),
4476                )]),
4477            })
4478            .collect();
4479        let error = session
4480            .execute_trusted_dynamic_mutation_batch(over_limit)
4481            .expect_err("the 65th distinct entity must reject before staging");
4482        assert_eq!(error.class(), ErrorClass::Unsupported);
4483        assert_eq!(
4484            error.diagnostic_facts(),
4485            vec![
4486                (icydb_diagnostic_code::DiagnosticFactTag::ActualCount, 65),
4487                (icydb_diagnostic_code::DiagnosticFactTag::Limit, 64),
4488            ],
4489        );
4490    }
4491
4492    #[test]
4493    fn mixed_batch_rejects_a_cross_store_item_with_bounded_tags() {
4494        let session = initialize();
4495        let error = session
4496            .execute_trusted_dynamic_mutation_batch(vec![
4497                insert(70, None),
4498                DynamicMutation::Insert {
4499                    entity: CROSS_ENTITY_NAME.to_string(),
4500                    patch: DynamicStructuralPatch::new(vec![(
4501                        "id".to_string(),
4502                        DynamicWriteCell::Value(InputValue::nat64(70)),
4503                    )]),
4504                },
4505            ])
4506            .expect_err("a structural batch must remain inside one accepted store");
4507        assert_eq!(error.class(), ErrorClass::Conflict);
4508        assert_eq!(
4509            error.diagnostic_facts(),
4510            vec![
4511                (icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 1),
4512                (
4513                    icydb_diagnostic_code::DiagnosticFactTag::ExpectedEntityTag,
4514                    ENTITY_TAG.value(),
4515                ),
4516                (
4517                    icydb_diagnostic_code::DiagnosticFactTag::ActualEntityTag,
4518                    CROSS_ENTITY_TAG.value(),
4519                ),
4520            ],
4521        );
4522        session
4523            .execute_trusted_dynamic_mutation(&insert(70, None))
4524            .expect("cross-store rejection must publish no first-item effect");
4525    }
4526
4527    #[test]
4528    fn mixed_batch_unique_swap_and_delete_release_use_the_final_overlay() {
4529        let session = initialize();
4530        session
4531            .execute_trusted_dynamic_mutation_batch(vec![
4532                insert_with_code(1, None, 10),
4533                insert_with_code(2, None, 20),
4534            ])
4535            .expect("the unique-overlay fixture should commit");
4536
4537        let swapped = session
4538            .execute_trusted_dynamic_mutation_batch(vec![update_code(1, 20), update_code(2, 10)])
4539            .expect("two final rows should atomically swap unique memberships");
4540        assert_eq!(
4541            batch_rows(&swapped),
4542            vec![
4543                expected_row_with_code(1, None, 20),
4544                expected_row_with_code(2, None, 10),
4545            ],
4546        );
4547
4548        let released = session
4549            .execute_trusted_dynamic_mutation_batch(vec![delete(1), insert_with_code(3, None, 20)])
4550            .expect("a delete should release unique membership to a final inserted row");
4551        assert_eq!(
4552            batch_rows(&released),
4553            vec![
4554                expected_row_with_code(1, None, 20),
4555                expected_row_with_code(3, None, 20),
4556            ],
4557        );
4558    }
4559}
4560
4561#[cfg(test)]
4562mod identity_pre_key_tests {
4563    use super::DynamicTypedEntityBinding;
4564    use super::{
4565        AcceptedMutationIntentPatch, AcceptedRowLayoutRuntimeContract, AcceptedStructuralMutation,
4566        AcceptedStructuralMutationPacking, AcceptedStructuralMutationStagedAdmission,
4567        AcceptedStructuralMutationTarget, DbSession, DynamicMutation, DynamicStructuralPatch,
4568        DynamicTypedMutation, DynamicWriteCell, FieldSlot,
4569        MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS, MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES,
4570        MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES, MutationProgressRecordOp,
4571        TypedEntityDescriptor, TypedFieldType, add_structural_mutation_staged_bytes,
4572        admit_structural_mutation_staged_charge, checked_pre_key_candidate_count,
4573        insert_key_exists_after_generation, structural_mutation_staged_charge,
4574        validate_structural_mutation_result_bytes,
4575    };
4576    #[cfg(feature = "sql")]
4577    use crate::db::data::DecodedDataStoreKey;
4578    #[cfg(all(feature = "sql", feature = "diagnostics"))]
4579    use crate::db::executor::budget::{
4580        HardExecutionBudget, HardExecutionContext, HardExecutionFailureHeadroom,
4581        with_execution_budget_for_tests, with_query_execution_budget_for_tests,
4582    };
4583    use crate::db::mutation_job::{MutationJobRecord, MutationJobTransition};
4584    #[cfg(all(feature = "sql", feature = "diagnostics"))]
4585    use crate::db::{
4586        CompareProofAndAdvanceError, ExhaustiveReadError, MutationJobError,
4587        MutationJobRestartReason, PrimaryKeyComponent, PrimaryKeyValue, RawDataStoreKey,
4588        ReadSetRevisionError, ResumableJobAdvance, ResumableJobAdvanceRequest,
4589        ResumableJobAdvanceStatus, ResumableJobError, ResumableJobId, ResumableJobIdempotencyKey,
4590        ResumableJobStatus, asc,
4591    };
4592    use crate::db::{DynamicQuery, QueryExecutionError};
4593    use crate::{
4594        db::{
4595            GeneratedStartupDriverStep, MutationJobAdvanceRequest, MutationJobId,
4596            MutationJobIdempotencyKey, MutationJobPhase, MutationJobStatus, TypedFieldDescriptor,
4597            commit::{
4598                database_incarnation_id, forget_recovered_domain_for_tests,
4599                install_startup_recovery_wakeup,
4600            },
4601            data::DataStore,
4602            drive_generated_startup_recovery_page,
4603            executor::{MutationCommitInterruption, interrupt_next_mutation_commit_for_tests},
4604            index::{IndexId, IndexKey, IndexKeyKind, IndexStore, IndexStoreVisit},
4605            integrity::{
4606                InsertMutationJobResult, PhysicalUnitCheckpoint, QuickIntegrityStatus,
4607                RowInspectionLimits, execute_quick_integrity, execute_row_integrity_page,
4608                with_mutation_progress_store,
4609            },
4610            journal::{
4611                JournalBatch, JournalRecord, JournalSequence, JournalTailControl, JournalTailStore,
4612                encode_journal_batch,
4613            },
4614            registry::{
4615                StoreAllocationIdentities, StoreAllocationIdentity, StoreHandle, StoreRegistry,
4616                StoreRuntimeStorageCapabilities,
4617            },
4618            schema::{
4619                AcceptedConstraintCatalog, AcceptedFieldKind, AcceptedSchemaRevision, FieldId,
4620                FieldInsertGeneration, FieldStorageDecode, LeafCodec, PersistedFieldSnapshot,
4621                PersistedIndexFieldPathSnapshot, PersistedIndexKeySnapshot, PersistedIndexSnapshot,
4622                PersistedRelationEdgeSnapshot, PersistedSchemaSnapshot, RelationId, ScalarCodec,
4623                SchemaFieldSlot, SchemaFieldWritePolicy, SchemaIndexId, SchemaInsertDefault,
4624                SchemaRowLayout, SchemaStore, SchemaVersion,
4625                accepted_schema_candidate_with_field_bindings_for_tests,
4626                cardinality_build::{
4627                    CardinalityBuildAuthority, CardinalityGenerationPageOutcome,
4628                    drive_cardinality_generation_page,
4629                },
4630                cardinality_generation::{CardinalityGenerationHeader, CardinalityGenerationState},
4631            },
4632            write_context::MutationMode,
4633        },
4634        error::{ErrorClass, ErrorOrigin, InternalError},
4635        testing::test_memory,
4636        traits::{CanisterKind, Path},
4637        types::{EntityTag, Timestamp},
4638        value::{InputValue, OutputValue, Value},
4639    };
4640    use icydb_schema::{FieldSourceKey, ScalarType};
4641    use std::{
4642        cell::{Cell, RefCell},
4643        collections::BTreeMap,
4644        time::Instant,
4645    };
4646
4647    const STORE_PATH: &str = "session::write::identity_pre_key_tests::Store";
4648    const ENTITY_SOURCE: &str = "session::write::identity_pre_key_tests::Entity";
4649    const ID_SOURCE: &str = "session::write::identity_pre_key_tests::Entity::id";
4650    const PAYLOAD_SOURCE: &str = "session::write::identity_pre_key_tests::Entity::payload";
4651    const ENTITY_NAME: &str = "IdentityRow";
4652    const ENTITY_TAG: EntityTag = EntityTag::new(93);
4653    const TYPED_DESCRIPTOR: TypedEntityDescriptor = TypedEntityDescriptor::new(
4654        ENTITY_SOURCE,
4655        &[ID_SOURCE],
4656        &[
4657            TypedFieldDescriptor::new(ID_SOURCE, TypedFieldType::Scalar(ScalarType::Nat64), false),
4658            TypedFieldDescriptor::new(
4659                PAYLOAD_SOURCE,
4660                TypedFieldType::Scalar(ScalarType::Nat64),
4661                false,
4662            ),
4663        ],
4664    );
4665    const SECOND_ENTITY_SOURCE: &str = "session::write::identity_pre_key_tests::SecondEntity";
4666    const SECOND_ID_SOURCE: &str = "session::write::identity_pre_key_tests::SecondEntity::id";
4667    const SECOND_PAYLOAD_SOURCE: &str =
4668        "session::write::identity_pre_key_tests::SecondEntity::payload";
4669    const SECOND_TARGET_SOURCE: &str =
4670        "session::write::identity_pre_key_tests::SecondEntity::target_id";
4671    const SECOND_ENTITY_NAME: &str = "SecondIdentityRow";
4672    const SECOND_ENTITY_TAG: EntityTag = EntityTag::new(96);
4673    const THIRD_ENTITY_SOURCE: &str = "session::write::identity_pre_key_tests::ThirdEntity";
4674    const THIRD_ID_SOURCE: &str = "session::write::identity_pre_key_tests::ThirdEntity::id";
4675    const THIRD_PAYLOAD_SOURCE: &str =
4676        "session::write::identity_pre_key_tests::ThirdEntity::payload";
4677    const THIRD_ENTITY_NAME: &str = "ThirdIdentityRow";
4678    const THIRD_ENTITY_TAG: EntityTag = EntityTag::new(97);
4679    const JOURNALED_STORE_PATH: &str = "session::write::identity_pre_key_tests::JournaledStore";
4680    const UNRELATED_STORE_PATH: &str = "session::write::identity_pre_key_tests::UnrelatedStore";
4681
4682    fn batch_rows(results: &[crate::db::DynamicMutationResult]) -> Vec<Vec<OutputValue>> {
4683        results
4684            .iter()
4685            .flat_map(|result| result.rows.iter().cloned())
4686            .collect()
4687    }
4688
4689    struct TestCanister;
4690
4691    impl Path for TestCanister {
4692        const PATH: &'static str = "session::write::identity_pre_key_tests::Canister";
4693    }
4694
4695    impl CanisterKind for TestCanister {
4696        const COMMIT_MEMORY_ID: u8 = 45;
4697        const COMMIT_STABLE_KEY: &'static str = "icydb.identity_pre_key_tests.commit.v1";
4698        const STARTUP_MEMORY_ID: u8 = 49;
4699        const STARTUP_STABLE_KEY: &'static str = "icydb.identity_pre_key_tests.startup.control.v1";
4700        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 46;
4701        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
4702            "icydb.identity_pre_key_tests.integrity.progress.v1";
4703    }
4704
4705    thread_local! {
4706        static STARTUP_WAKEUPS: Cell<u32> = const { Cell::new(0) };
4707        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
4708        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
4709        static SCHEMA_STORE: RefCell<SchemaStore> =
4710            const { RefCell::new(SchemaStore::init_heap()) };
4711        static UNRELATED_DATA_STORE: RefCell<DataStore> =
4712            const { RefCell::new(DataStore::init_heap()) };
4713        static UNRELATED_INDEX_STORE: RefCell<IndexStore> =
4714            const { RefCell::new(IndexStore::init_heap()) };
4715        static UNRELATED_SCHEMA_STORE: RefCell<SchemaStore> =
4716            const { RefCell::new(SchemaStore::init_heap()) };
4717        static STORE_REGISTRY: StoreRegistry = {
4718            let mut registry = StoreRegistry::new();
4719            registry.register_store(
4720                STORE_PATH,
4721                &DATA_STORE,
4722                &INDEX_STORE,
4723                &SCHEMA_STORE,
4724                StoreAllocationIdentities::absent(),
4725                StoreRuntimeStorageCapabilities::heap(),
4726            ).expect("identity pre-key test store should register");
4727            registry.register_store(
4728                UNRELATED_STORE_PATH,
4729                &UNRELATED_DATA_STORE,
4730                &UNRELATED_INDEX_STORE,
4731                &UNRELATED_SCHEMA_STORE,
4732                StoreAllocationIdentities::absent(),
4733                StoreRuntimeStorageCapabilities::heap(),
4734            ).expect("unrelated identity test store should register");
4735            registry
4736        };
4737        static JOURNALED_DATA_STORE: RefCell<DataStore> =
4738            RefCell::new(DataStore::init_journaled(test_memory(186)));
4739        static JOURNALED_INDEX_STORE: RefCell<IndexStore> =
4740            RefCell::new(IndexStore::init_journaled(test_memory(187)));
4741        static JOURNALED_SCHEMA_STORE: RefCell<SchemaStore> =
4742            RefCell::new(SchemaStore::init_journaled(test_memory(188)));
4743        static JOURNALED_TAIL_STORE: RefCell<JournalTailStore> =
4744            RefCell::new(JournalTailStore::init(test_memory(189)));
4745        static JOURNALED_STORE_REGISTRY: StoreRegistry = {
4746            let mut registry = StoreRegistry::new();
4747            registry.register_journaled_store(
4748                JOURNALED_STORE_PATH,
4749                &JOURNALED_DATA_STORE,
4750                &JOURNALED_INDEX_STORE,
4751                &JOURNALED_SCHEMA_STORE,
4752                &JOURNALED_TAIL_STORE,
4753                StoreAllocationIdentities::new_journaled(
4754                    StoreAllocationIdentity::new(186, "icydb.test.identity_range.data.v1"),
4755                    StoreAllocationIdentity::new(187, "icydb.test.identity_range.index.v1"),
4756                    StoreAllocationIdentity::new(188, "icydb.test.identity_range.schema.v1"),
4757                    StoreAllocationIdentity::new(189, "icydb.test.identity_range.journal.v1"),
4758                ),
4759                StoreRuntimeStorageCapabilities::journaled(),
4760            ).expect("identity range journaled store should register");
4761            registry
4762        };
4763    }
4764
4765    fn record_startup_wakeup() {
4766        STARTUP_WAKEUPS.with(|wakeups| wakeups.set(wakeups.get().saturating_add(1)));
4767    }
4768
4769    struct JournaledTestCanister;
4770
4771    impl Path for JournaledTestCanister {
4772        const PATH: &'static str = "session::write::identity_pre_key_tests::JournaledCanister";
4773    }
4774
4775    impl CanisterKind for JournaledTestCanister {
4776        const COMMIT_MEMORY_ID: u8 = 190;
4777        const COMMIT_STABLE_KEY: &'static str = "icydb.identity_range_tests.commit.v1";
4778        const STARTUP_MEMORY_ID: u8 = 192;
4779        const STARTUP_STABLE_KEY: &'static str = "icydb.identity_range_tests.startup.control.v1";
4780        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 191;
4781        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
4782            "icydb.identity_range_tests.integrity.progress.v1";
4783    }
4784
4785    fn source_key(source: &str) -> FieldSourceKey {
4786        FieldSourceKey::try_new(source).expect("identity test field source should admit")
4787    }
4788
4789    fn identity_snapshot(store_path: &str, payload_unique: bool) -> PersistedSchemaSnapshot {
4790        identity_snapshot_for_entity(
4791            store_path,
4792            payload_unique,
4793            false,
4794            false,
4795            ENTITY_SOURCE,
4796            ENTITY_NAME,
4797            None,
4798        )
4799    }
4800
4801    fn identity_snapshot_with_nullable_payload(store_path: &str) -> PersistedSchemaSnapshot {
4802        identity_snapshot_for_entity(
4803            store_path,
4804            false,
4805            false,
4806            true,
4807            ENTITY_SOURCE,
4808            ENTITY_NAME,
4809            None,
4810        )
4811    }
4812
4813    fn identity_snapshot_with_payload_index(
4814        store_path: &str,
4815        payload_unique: bool,
4816        composite: bool,
4817    ) -> PersistedSchemaSnapshot {
4818        identity_snapshot_for_entity(
4819            store_path,
4820            payload_unique,
4821            composite,
4822            false,
4823            ENTITY_SOURCE,
4824            ENTITY_NAME,
4825            None,
4826        )
4827    }
4828
4829    fn identity_snapshot_for_entity(
4830        store_path: &str,
4831        payload_unique: bool,
4832        composite: bool,
4833        payload_nullable: bool,
4834        entity_source: &str,
4835        entity_name: &str,
4836        relation_target: Option<&str>,
4837    ) -> PersistedSchemaSnapshot {
4838        let mut fields = vec![
4839            PersistedFieldSnapshot::new_initial_with_write_policy(
4840                FieldId::new(1),
4841                "id".to_string(),
4842                SchemaFieldSlot::new(0),
4843                AcceptedFieldKind::Nat64,
4844                Vec::new(),
4845                false,
4846                SchemaInsertDefault::None,
4847                SchemaFieldWritePolicy::from_model_policies(
4848                    Some(FieldInsertGeneration::Identity),
4849                    None,
4850                ),
4851                FieldStorageDecode::ByKind,
4852                LeafCodec::Scalar(ScalarCodec::Nat64),
4853            ),
4854            PersistedFieldSnapshot::new_initial(
4855                FieldId::new(2),
4856                "payload".to_string(),
4857                SchemaFieldSlot::new(1),
4858                AcceptedFieldKind::Nat64,
4859                Vec::new(),
4860                payload_nullable,
4861                SchemaInsertDefault::None,
4862                FieldStorageDecode::ByKind,
4863                LeafCodec::Scalar(ScalarCodec::Nat64),
4864            ),
4865        ];
4866        if relation_target.is_some() {
4867            fields.push(PersistedFieldSnapshot::new_initial(
4868                FieldId::new(3),
4869                "target_id".to_string(),
4870                SchemaFieldSlot::new(2),
4871                AcceptedFieldKind::Nat64,
4872                Vec::new(),
4873                true,
4874                SchemaInsertDefault::None,
4875                FieldStorageDecode::ByKind,
4876                LeafCodec::Scalar(ScalarCodec::Nat64),
4877            ));
4878        }
4879        let mut index_fields = vec![PersistedIndexFieldPathSnapshot::new(
4880            FieldId::new(2),
4881            SchemaFieldSlot::new(1),
4882            vec!["payload".to_string()],
4883            AcceptedFieldKind::Nat64,
4884            payload_nullable,
4885        )];
4886        if composite {
4887            index_fields.push(PersistedIndexFieldPathSnapshot::new(
4888                FieldId::new(1),
4889                SchemaFieldSlot::new(0),
4890                vec!["id".to_string()],
4891                AcceptedFieldKind::Nat64,
4892                false,
4893            ));
4894        }
4895        let snapshot = PersistedSchemaSnapshot::new_with_indexes(
4896            SchemaVersion::initial(),
4897            entity_source.to_string(),
4898            entity_name.to_string(),
4899            FieldId::new(1),
4900            SchemaRowLayout::initial(
4901                fields
4902                    .iter()
4903                    .map(|field| (field.id(), field.slot()))
4904                    .collect(),
4905            ),
4906            fields,
4907            vec![PersistedIndexSnapshot::new(
4908                SchemaIndexId::new(1).expect("identity test index ID should admit"),
4909                1,
4910                if composite {
4911                    "by_payload_id".to_string()
4912                } else {
4913                    "by_payload".to_string()
4914                },
4915                store_path.to_string(),
4916                payload_unique,
4917                PersistedIndexKeySnapshot::FieldPath(index_fields),
4918                None,
4919            )],
4920        );
4921        let Some(relation_target) = relation_target else {
4922            return snapshot;
4923        };
4924        let snapshot = snapshot.with_relations(vec![PersistedRelationEdgeSnapshot::new(
4925            RelationId::new(1).expect("mixed recovery relation identity should be non-zero"),
4926            "target".to_string(),
4927            relation_target.to_string(),
4928            vec![FieldId::new(3)],
4929        )]);
4930        let constraints = AcceptedConstraintCatalog::initial(
4931            snapshot.fields(),
4932            snapshot.indexes(),
4933            snapshot.relations(),
4934        )
4935        .expect("mixed recovery relation constraints should close");
4936        snapshot.with_constraint_catalog(constraints)
4937    }
4938
4939    fn initialize() -> DbSession<TestCanister> {
4940        initialize_with_snapshot(identity_snapshot(STORE_PATH, false))
4941    }
4942
4943    fn initialize_with_composite_payload_index() -> DbSession<TestCanister> {
4944        initialize_with_snapshot(identity_snapshot_with_payload_index(
4945            STORE_PATH, false, true,
4946        ))
4947    }
4948
4949    fn initialize_with_snapshot(snapshot: PersistedSchemaSnapshot) -> DbSession<TestCanister> {
4950        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
4951        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
4952        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
4953        UNRELATED_DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
4954        UNRELATED_INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
4955        UNRELATED_SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
4956        let session = DbSession::<TestCanister>::new(
4957            &STORE_REGISTRY,
4958            &crate::db::RequestExecutionRoot::__new_runtime_root(),
4959        );
4960        session
4961            .db
4962            .drive_startup_recovery_page()
4963            .expect("identity pre-key test database should initialize");
4964        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
4965            STORE_PATH,
4966            AcceptedSchemaRevision::INITIAL,
4967            BTreeMap::from([(ENTITY_TAG, snapshot)]),
4968            BTreeMap::from([
4969                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
4970                ((ENTITY_TAG, source_key(PAYLOAD_SOURCE)), FieldId::new(2)),
4971            ]),
4972        );
4973        let store = session
4974            .db
4975            .store_handle(STORE_PATH)
4976            .expect("identity pre-key test store should resolve");
4977        crate::db::commit::publish_accepted_schema_candidate(
4978            STORE_PATH,
4979            store,
4980            AcceptedSchemaRevision::NONE,
4981            &candidate,
4982        )
4983        .expect("identity candidate should publish with explicit zero state");
4984        session
4985    }
4986
4987    fn initialize_journaled_with_root_and_payload_uniqueness(
4988        payload_unique: bool,
4989    ) -> (
4990        DbSession<JournaledTestCanister>,
4991        crate::db::RequestExecutionRoot,
4992    ) {
4993        let root = crate::db::RequestExecutionRoot::__new_runtime_root();
4994        let session = DbSession::<JournaledTestCanister>::new(&JOURNALED_STORE_REGISTRY, &root);
4995        session
4996            .db
4997            .drive_startup_recovery_page()
4998            .expect("journaled identity database should initialize");
4999        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
5000            JOURNALED_STORE_PATH,
5001            AcceptedSchemaRevision::INITIAL,
5002            BTreeMap::from([(
5003                ENTITY_TAG,
5004                identity_snapshot(JOURNALED_STORE_PATH, payload_unique),
5005            )]),
5006            BTreeMap::from([
5007                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
5008                ((ENTITY_TAG, source_key(PAYLOAD_SOURCE)), FieldId::new(2)),
5009            ]),
5010        );
5011        let store = session
5012            .db
5013            .store_handle(JOURNALED_STORE_PATH)
5014            .expect("journaled identity store should resolve");
5015        crate::db::commit::publish_accepted_schema_candidate(
5016            JOURNALED_STORE_PATH,
5017            store,
5018            AcceptedSchemaRevision::NONE,
5019            &candidate,
5020        )
5021        .expect("journaled identity candidate should publish");
5022        (session, root)
5023    }
5024
5025    fn initialize_journaled_with_root() -> (
5026        DbSession<JournaledTestCanister>,
5027        crate::db::RequestExecutionRoot,
5028    ) {
5029        initialize_journaled_with_root_and_payload_uniqueness(false)
5030    }
5031
5032    fn initialize_journaled_multi_entity() -> DbSession<JournaledTestCanister> {
5033        let root = crate::db::RequestExecutionRoot::__new_runtime_root();
5034        let session = DbSession::<JournaledTestCanister>::new(&JOURNALED_STORE_REGISTRY, &root);
5035        session
5036            .db
5037            .drive_startup_recovery_page()
5038            .expect("multi-entity journaled database should initialize");
5039        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
5040            JOURNALED_STORE_PATH,
5041            AcceptedSchemaRevision::INITIAL,
5042            BTreeMap::from([
5043                (ENTITY_TAG, identity_snapshot(JOURNALED_STORE_PATH, false)),
5044                (
5045                    SECOND_ENTITY_TAG,
5046                    identity_snapshot_for_entity(
5047                        JOURNALED_STORE_PATH,
5048                        false,
5049                        false,
5050                        false,
5051                        SECOND_ENTITY_SOURCE,
5052                        SECOND_ENTITY_NAME,
5053                        Some(ENTITY_SOURCE),
5054                    ),
5055                ),
5056                (
5057                    THIRD_ENTITY_TAG,
5058                    identity_snapshot_for_entity(
5059                        JOURNALED_STORE_PATH,
5060                        false,
5061                        false,
5062                        false,
5063                        THIRD_ENTITY_SOURCE,
5064                        THIRD_ENTITY_NAME,
5065                        None,
5066                    ),
5067                ),
5068            ]),
5069            BTreeMap::from([
5070                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
5071                ((ENTITY_TAG, source_key(PAYLOAD_SOURCE)), FieldId::new(2)),
5072                (
5073                    (SECOND_ENTITY_TAG, source_key(SECOND_ID_SOURCE)),
5074                    FieldId::new(1),
5075                ),
5076                (
5077                    (SECOND_ENTITY_TAG, source_key(SECOND_PAYLOAD_SOURCE)),
5078                    FieldId::new(2),
5079                ),
5080                (
5081                    (SECOND_ENTITY_TAG, source_key(SECOND_TARGET_SOURCE)),
5082                    FieldId::new(3),
5083                ),
5084                (
5085                    (THIRD_ENTITY_TAG, source_key(THIRD_ID_SOURCE)),
5086                    FieldId::new(1),
5087                ),
5088                (
5089                    (THIRD_ENTITY_TAG, source_key(THIRD_PAYLOAD_SOURCE)),
5090                    FieldId::new(2),
5091                ),
5092            ]),
5093        );
5094        let store = session
5095            .db
5096            .store_handle(JOURNALED_STORE_PATH)
5097            .expect("multi-entity journaled store should resolve");
5098        crate::db::commit::publish_accepted_schema_candidate(
5099            JOURNALED_STORE_PATH,
5100            store,
5101            AcceptedSchemaRevision::NONE,
5102            &candidate,
5103        )
5104        .expect("multi-entity journaled candidate should publish");
5105        session
5106    }
5107
5108    fn initialize_journaled() -> DbSession<JournaledTestCanister> {
5109        initialize_journaled_with_root().0
5110    }
5111
5112    fn initialize_journaled_with_unique_payload() -> DbSession<JournaledTestCanister> {
5113        initialize_journaled_with_root_and_payload_uniqueness(true).0
5114    }
5115
5116    fn drive_journaled_recovery_to_completion(session: &DbSession<JournaledTestCanister>) {
5117        for _ in 0..8 {
5118            if session
5119                .db
5120                .drive_startup_recovery_page()
5121                .expect("dedicated driver recovery should remain valid")
5122            {
5123                return;
5124            }
5125        }
5126        panic!("dedicated driver recovery should quiesce within eight complete batches");
5127    }
5128
5129    fn drive_journaled_cardinality_to_ready(session: &DbSession<JournaledTestCanister>) {
5130        let handle = session
5131            .db
5132            .store_handle(JOURNALED_STORE_PATH)
5133            .expect("journaled cardinality store should resolve");
5134        for _ in 0..8 {
5135            let outcome = handle
5136                .with_data(|data| {
5137                    handle.with_index(|index| {
5138                        handle.with_schema_mut(|schema| {
5139                            drive_cardinality_generation_page(data, index, schema, |schema| {
5140                                let watermark = JOURNALED_TAIL_STORE
5141                                    .with(|tail| tail.borrow().fold_watermark())?;
5142                                CardinalityBuildAuthority::derive(
5143                                    schema,
5144                                    database_incarnation_id()?,
5145                                    handle.allocation_identities(),
5146                                    watermark,
5147                                )
5148                            })
5149                        })
5150                    })
5151                })
5152                .expect("bounded cardinality generation should advance");
5153            if outcome == CardinalityGenerationPageOutcome::Quiescent {
5154                return;
5155            }
5156        }
5157        panic!("cardinality generation should become Ready within eight bounded pages");
5158    }
5159
5160    fn journaled_user_index_prefix() -> (IndexId, Vec<Vec<u8>>) {
5161        JOURNALED_INDEX_STORE.with(|store| {
5162            let mut selected = None;
5163            store
5164                .borrow()
5165                .visit_entries(|raw_key, _value| {
5166                    let key = IndexKey::try_from_raw(raw_key)
5167                        .expect("accepted user index key should decode");
5168                    if key.key_kind() != IndexKeyKind::User {
5169                        return Ok::<_, InternalError>(IndexStoreVisit::Continue);
5170                    }
5171                    let components = (0..key.component_count())
5172                        .map(|index| {
5173                            key.component(index)
5174                                .expect("accepted index component should exist")
5175                                .to_vec()
5176                        })
5177                        .collect::<Vec<_>>();
5178                    selected = Some((*key.index_id(), components));
5179                    Ok(IndexStoreVisit::Stop)
5180                })
5181                .expect("accepted user index should be inspectable");
5182            selected.expect("the cardinality fixture should contain one user index entry")
5183        })
5184    }
5185
5186    fn reset_journaled_cardinality_projections() -> u64 {
5187        JOURNALED_DATA_STORE.with(|store| {
5188            store
5189                .borrow_mut()
5190                .reset_journaled_live_projection()
5191                .expect("row projection should reset without a count scan");
5192        });
5193        let data_generation = JOURNALED_DATA_STORE.with(|store| store.borrow().generation());
5194        let fold_watermark = JOURNALED_TAIL_STORE
5195            .with(|store| store.borrow().fold_watermark())
5196            .expect("journal watermark should remain current-form");
5197        JOURNALED_INDEX_STORE.with(|store| {
5198            store
5199                .borrow_mut()
5200                .reset_journaled_live_projection(data_generation, fold_watermark)
5201                .expect("index projection should reset without a count scan");
5202        });
5203        data_generation
5204    }
5205
5206    fn assert_journaled_cardinality(
5207        handle: StoreHandle,
5208        index_id: IndexId,
5209        prefix_components: &[Vec<u8>],
5210        expected: u64,
5211    ) {
5212        assert_eq!(handle.exact_entity_count(ENTITY_TAG), Some(expected));
5213        let data_generation = JOURNALED_DATA_STORE.with(|store| store.borrow().generation());
5214        assert_eq!(
5215            handle.exact_user_index_prefix_count(
5216                data_generation,
5217                IndexKeyKind::User,
5218                index_id,
5219                prefix_components,
5220            ),
5221            Some(expected),
5222        );
5223    }
5224
5225    fn mark_journaled_cardinality_building() {
5226        let current = JOURNALED_SCHEMA_STORE.with(|store| {
5227            store
5228                .borrow()
5229                .cardinality_generation_header()
5230                .expect("Ready header should decode")
5231                .expect("Ready header should exist")
5232        });
5233        JOURNALED_SCHEMA_STORE.with(|store| {
5234            store
5235                .borrow_mut()
5236                .write_cardinality_generation_header(CardinalityGenerationHeader::new(
5237                    current.generation(),
5238                    CardinalityGenerationState::Building,
5239                    current.slot(),
5240                    current.source(),
5241                ))
5242                .expect("Building fallback fixture should persist");
5243        });
5244    }
5245
5246    fn payload_patch(value: u64) -> AcceptedMutationIntentPatch {
5247        AcceptedMutationIntentPatch::new()
5248            .set_authored(FieldSlot::from_validated_index(1), InputValue::nat64(value))
5249    }
5250
5251    fn dynamic_payload_patch(value: u64) -> DynamicStructuralPatch {
5252        DynamicStructuralPatch::new(vec![(
5253            "payload".to_string(),
5254            DynamicWriteCell::Value(InputValue::nat64(value)),
5255        )])
5256    }
5257
5258    fn related_dynamic_payload_patch(value: u64, target_id: u64) -> DynamicStructuralPatch {
5259        DynamicStructuralPatch::new(vec![
5260            (
5261                "payload".to_string(),
5262                DynamicWriteCell::Value(InputValue::nat64(value)),
5263            ),
5264            (
5265                "target_id".to_string(),
5266                DynamicWriteCell::Value(InputValue::nat64(target_id)),
5267            ),
5268        ])
5269    }
5270
5271    fn expected_dynamic_row(id: u64, payload: u64) -> Vec<OutputValue> {
5272        vec![OutputValue::nat64(id), OutputValue::nat64(payload)]
5273    }
5274
5275    fn exact_key_binding<C: CanisterKind>(session: &DbSession<C>) -> DynamicTypedEntityBinding {
5276        session
5277            .issue_typed_entity_binding(&TYPED_DESCRIPTOR)
5278            .expect("exact-key test binding should issue")
5279    }
5280
5281    fn typed_payload_insert(
5282        binding: &DynamicTypedEntityBinding,
5283        payload: u64,
5284    ) -> DynamicTypedMutation {
5285        let patch = binding
5286            .bind_write_ordinals(vec![(
5287                1,
5288                DynamicWriteCell::Value(InputValue::nat64(payload)),
5289            )])
5290            .expect("typed payload patch should bind");
5291        DynamicTypedMutation::Insert { patch }
5292    }
5293
5294    fn typed_payload_delete(id: u64) -> DynamicTypedMutation {
5295        DynamicTypedMutation::Delete {
5296            key: InputValue::nat64(id),
5297        }
5298    }
5299
5300    fn insert_exact_key_fixture<C: CanisterKind>(session: &DbSession<C>, payload: u64) -> u64 {
5301        let output = session
5302            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
5303                entity: ENTITY_NAME.to_string(),
5304                patch: dynamic_payload_patch(payload),
5305            })
5306            .expect("exact-key fixture insert should commit");
5307        match output.rows.as_slice() {
5308            [row] => match row.as_slice() {
5309                [id, actual_payload] if matches!(actual_payload.as_public(), crate::value::PublicValue::Nat64(value) if *value == payload) =>
5310                {
5311                    let crate::value::PublicValue::Nat64(id) = id.as_public() else {
5312                        panic!("exact-key fixture should return a natural identity");
5313                    };
5314                    *id
5315                }
5316                _ => panic!("exact-key fixture should return its identity and payload"),
5317            },
5318            _ => panic!("exact-key fixture insert should return one row"),
5319        }
5320    }
5321
5322    #[cfg(feature = "sql")]
5323    fn sql_projection_rows(session: &DbSession<TestCanister>, sql: &str) -> Vec<Vec<OutputValue>> {
5324        let crate::db::SqlStatementResult::Projection { rows, .. } = session
5325            .execute_trusted_sql_query(sql)
5326            .expect("focused SQL projection should execute")
5327        else {
5328            panic!("focused SQL projection should return rows")
5329        };
5330
5331        rows
5332    }
5333
5334    #[cfg(feature = "sql")]
5335    #[test]
5336    fn secondary_ordered_covering_limit_stops_at_the_present_row_window() {
5337        let session = initialize_with_composite_payload_index();
5338        for payload in [30, 10, 20, 20, 40] {
5339            insert_exact_key_fixture(&session, payload);
5340        }
5341
5342        assert_eq!(
5343            sql_projection_rows(
5344                &session,
5345                "SELECT payload FROM IdentityRow ORDER BY payload ASC, id ASC LIMIT 1",
5346            ),
5347            vec![vec![OutputValue::nat64(10)]],
5348        );
5349        #[cfg(feature = "diagnostics")]
5350        assert_sql_query_fits_resource_limit(
5351            &session,
5352            "SELECT payload FROM IdentityRow ORDER BY payload ASC, id ASC LIMIT 1",
5353            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::KeyIndexEntriesVisited,
5354            1,
5355        );
5356
5357        assert_eq!(
5358            sql_projection_rows(
5359                &session,
5360                "SELECT payload FROM IdentityRow ORDER BY payload DESC, id DESC LIMIT 1",
5361            ),
5362            vec![vec![OutputValue::nat64(40)]],
5363        );
5364        #[cfg(feature = "diagnostics")]
5365        assert_sql_query_fits_resource_limit(
5366            &session,
5367            "SELECT payload FROM IdentityRow ORDER BY payload DESC, id DESC LIMIT 1",
5368            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::KeyIndexEntriesVisited,
5369            1,
5370        );
5371
5372        assert_eq!(
5373            sql_projection_rows(
5374                &session,
5375                "SELECT id, payload FROM IdentityRow \
5376                 ORDER BY payload ASC, id ASC LIMIT 2 OFFSET 1",
5377            ),
5378            vec![
5379                vec![OutputValue::nat64(3), OutputValue::nat64(20)],
5380                vec![OutputValue::nat64(4), OutputValue::nat64(20)],
5381            ],
5382        );
5383        #[cfg(feature = "diagnostics")]
5384        assert_sql_query_fits_resource_limit(
5385            &session,
5386            "SELECT id, payload FROM IdentityRow \
5387             ORDER BY payload ASC, id ASC LIMIT 2 OFFSET 1",
5388            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::KeyIndexEntriesVisited,
5389            3,
5390        );
5391
5392        assert_eq!(
5393            sql_projection_rows(
5394                &session,
5395                "SELECT payload FROM IdentityRow ORDER BY payload ASC, id ASC",
5396            ),
5397            [10, 20, 20, 30, 40]
5398                .into_iter()
5399                .map(|payload| vec![OutputValue::nat64(payload)])
5400                .collect::<Vec<_>>(),
5401        );
5402    }
5403
5404    #[cfg(feature = "sql")]
5405    #[test]
5406    fn secondary_ordered_covering_limit_fails_on_an_accessed_missing_row() {
5407        let session = initialize_with_composite_payload_index();
5408        let first = insert_exact_key_fixture(&session, 10);
5409        insert_exact_key_fixture(&session, 20);
5410        let raw_key =
5411            DecodedDataStoreKey::try_from_structural_key(ENTITY_TAG, &Value::Nat64(first))
5412                .expect("missing-row fixture key should decode")
5413                .to_raw()
5414                .expect("missing-row fixture key should encode");
5415        let store = session
5416            .db
5417            .store_handle(STORE_PATH)
5418            .expect("missing-row fixture store should resolve");
5419        assert!(
5420            store.with_data_mut(|data| data.remove(&raw_key)).is_some(),
5421            "fixture must remove only the authoritative row",
5422        );
5423
5424        let error = session
5425            .execute_trusted_sql_query(
5426                "SELECT payload FROM IdentityRow ORDER BY payload ASC, id ASC LIMIT 1",
5427            )
5428            .expect_err("an accessed accepted-index row must remain fail-closed");
5429        assert!(matches!(
5430            error,
5431            crate::db::QueryError::Execute(QueryExecutionError::Corruption(_))
5432        ));
5433    }
5434
5435    #[cfg(feature = "sql")]
5436    #[test]
5437    fn secondary_indexed_max_uses_one_descending_edge_across_ties() {
5438        let session = initialize_with_composite_payload_index();
5439        let mut inserted = Vec::new();
5440        for payload in [30, 10, 20, 20, 40, 40] {
5441            inserted.push(insert_exact_key_fixture(&session, payload));
5442        }
5443
5444        let sql = "SELECT MAX(payload) FROM IdentityRow";
5445        let data_reads_before = DataStore::current_get_call_count();
5446        let index_reads_before = IndexStore::current_entry_read_count();
5447        assert_eq!(
5448            sql_projection_rows(&session, sql),
5449            vec![vec![OutputValue::nat64(40)]],
5450        );
5451        assert_eq!(DataStore::current_get_call_count() - data_reads_before, 1);
5452        assert!(IndexStore::current_entry_read_count() - index_reads_before <= 1);
5453
5454        let range_sql = "SELECT MAX(payload) FROM IdentityRow WHERE payload < 40";
5455        let data_reads_before = DataStore::current_get_call_count();
5456        let index_reads_before = IndexStore::current_entry_read_count();
5457        assert_eq!(
5458            sql_projection_rows(&session, range_sql),
5459            vec![vec![OutputValue::nat64(30)]],
5460        );
5461        assert_eq!(DataStore::current_get_call_count() - data_reads_before, 1);
5462        assert!(IndexStore::current_entry_read_count() - index_reads_before <= 1);
5463
5464        let last = inserted
5465            .last()
5466            .copied()
5467            .expect("secondary MAX fixture should retain its last identity");
5468        let raw_key = DecodedDataStoreKey::try_from_structural_key(ENTITY_TAG, &Value::Nat64(last))
5469            .expect("missing-row fixture key should decode")
5470            .to_raw()
5471            .expect("missing-row fixture key should encode");
5472        let store = session
5473            .db
5474            .store_handle(STORE_PATH)
5475            .expect("missing-row fixture store should resolve");
5476        assert!(
5477            store.with_data_mut(|data| data.remove(&raw_key)).is_some(),
5478            "fixture must remove only the descending edge row",
5479        );
5480
5481        let error = session
5482            .execute_trusted_sql_query("SELECT MAX(payload) FROM IdentityRow")
5483            .expect_err("an accessed accepted-index row must remain fail-closed");
5484        assert!(matches!(
5485            error,
5486            crate::db::QueryError::Execute(QueryExecutionError::Corruption(_))
5487        ));
5488    }
5489
5490    #[cfg(feature = "sql")]
5491    #[test]
5492    fn secondary_indexed_max_upper_range_fails_on_an_accessed_missing_row() {
5493        let session = initialize_with_composite_payload_index();
5494        let upper_range_edge = insert_exact_key_fixture(&session, 30);
5495        for payload in [10, 20, 40] {
5496            insert_exact_key_fixture(&session, payload);
5497        }
5498        let raw_key = DecodedDataStoreKey::try_from_structural_key(
5499            ENTITY_TAG,
5500            &Value::Nat64(upper_range_edge),
5501        )
5502        .expect("missing-row fixture key should decode")
5503        .to_raw()
5504        .expect("missing-row fixture key should encode");
5505        let store = session
5506            .db
5507            .store_handle(STORE_PATH)
5508            .expect("missing-row fixture store should resolve");
5509        assert!(
5510            store.with_data_mut(|data| data.remove(&raw_key)).is_some(),
5511            "fixture must remove only the upper-range edge row",
5512        );
5513
5514        let error = session
5515            .execute_trusted_sql_query("SELECT MAX(payload) FROM IdentityRow WHERE payload < 40")
5516            .expect_err("an accessed upper-range edge row must remain fail-closed");
5517        assert!(matches!(
5518            error,
5519            crate::db::QueryError::Execute(QueryExecutionError::Corruption(_))
5520        ));
5521    }
5522
5523    #[test]
5524    fn exact_counts_use_entity_and_bounded_index_metadata_without_physical_reads() {
5525        let session = initialize();
5526        for payload in [10, 10, 20] {
5527            insert_exact_key_fixture(&session, payload);
5528        }
5529        let binding = exact_key_binding(&session);
5530        let entity = DynamicQuery::new(ENTITY_NAME);
5531        let tens =
5532            DynamicQuery::new(ENTITY_NAME).filter(crate::db::FieldRef::new("payload").eq(10_u64));
5533        let selected = DynamicQuery::new(ENTITY_NAME)
5534            .filter(crate::db::FieldRef::new("payload").in_list([10_u64, 10, 20, 99]));
5535        let missing =
5536            DynamicQuery::new(ENTITY_NAME).filter(crate::db::FieldRef::new("payload").eq(99_u64));
5537        let data_reads_before = DataStore::current_get_call_count();
5538        let index_reads_before = IndexStore::current_entry_read_count();
5539
5540        assert_eq!(session.execute_public_exact_count(&entity).unwrap(), 3);
5541        assert_eq!(session.execute_public_exact_count(&tens).unwrap(), 2);
5542        assert_eq!(session.execute_public_exact_count(&selected).unwrap(), 3);
5543        assert_eq!(session.execute_public_exact_count(&missing).unwrap(), 0);
5544        assert_eq!(
5545            session
5546                .execute_public_exact_count_for_typed_binding(&binding, &tens)
5547                .unwrap(),
5548            Some(2),
5549        );
5550        assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5551        assert_eq!(IndexStore::current_entry_read_count(), index_reads_before);
5552
5553        session
5554            .execute_trusted_dynamic_insert_batch(
5555                ENTITY_NAME,
5556                (0..64).map(|_| dynamic_payload_patch(10)).collect(),
5557            )
5558            .expect("a larger matching population should commit");
5559        let data_reads_before = DataStore::current_get_call_count();
5560        let index_reads_before = IndexStore::current_entry_read_count();
5561        assert_eq!(session.execute_public_exact_count(&tens).unwrap(), 66);
5562        assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5563        assert_eq!(IndexStore::current_entry_read_count(), index_reads_before);
5564    }
5565
5566    #[test]
5567    fn exact_count_accepts_the_leading_field_of_a_composite_user_index() {
5568        let session = initialize_with_composite_payload_index();
5569        for payload in [10, 10, 20] {
5570            insert_exact_key_fixture(&session, payload);
5571        }
5572        let tens =
5573            DynamicQuery::new(ENTITY_NAME).filter(crate::db::FieldRef::new("payload").eq(10_u64));
5574        let selected = DynamicQuery::new(ENTITY_NAME)
5575            .filter(crate::db::FieldRef::new("payload").in_list([10_u64, 20, 99]));
5576        let data_reads_before = DataStore::current_get_call_count();
5577        let index_reads_before = IndexStore::current_entry_read_count();
5578
5579        assert_eq!(session.execute_public_exact_count(&tens).unwrap(), 2);
5580        assert_eq!(session.execute_public_exact_count(&selected).unwrap(), 3);
5581        assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5582        assert_eq!(IndexStore::current_entry_read_count(), index_reads_before);
5583    }
5584
5585    #[cfg(feature = "sql")]
5586    #[test]
5587    fn exact_count_shared_executor_preserves_sql_direct_count_results() {
5588        let session = initialize();
5589        let data_reads_before = DataStore::current_get_call_count();
5590        let crate::db::SqlStatementResult::Projection { rows, .. } = session
5591            .execute_trusted_sql_query("SELECT COUNT(*) FROM IdentityRow")
5592            .expect("empty SQL direct count should succeed")
5593        else {
5594            panic!("empty SQL direct count should return one projection row")
5595        };
5596        assert_eq!(rows, vec![vec![OutputValue::nat64(0)]]);
5597        assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5598
5599        for payload in [10, 10, 20] {
5600            insert_exact_key_fixture(&session, payload);
5601        }
5602
5603        let data_reads_before = DataStore::current_get_call_count();
5604        let index_reads_before = IndexStore::current_entry_read_count();
5605        for sql in [
5606            "SELECT COUNT(*) FROM IdentityRow",
5607            "SELECT COUNT(payload) FROM IdentityRow",
5608            "SELECT COUNT(1) FROM IdentityRow",
5609            "SELECT COUNT(*) FROM IdentityRow WHERE true",
5610            "SELECT COUNT(*) FROM IdentityRow WHERE payload IN (10, 10, 20, 99)",
5611        ] {
5612            let crate::db::SqlStatementResult::Projection { rows, .. } = session
5613                .execute_trusted_sql_query(sql)
5614                .expect("SQL direct count should use the shared exact executor")
5615            else {
5616                panic!("SQL direct count should return one projection row")
5617            };
5618            assert_eq!(rows, vec![vec![OutputValue::nat64(3)]], "{sql}");
5619        }
5620        assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5621        assert_eq!(IndexStore::current_entry_read_count(), index_reads_before);
5622
5623        let crate::db::SqlStatementResult::Projection { rows, .. } = session
5624            .execute_trusted_sql_query("SELECT COUNT(*) FROM IdentityRow WHERE payload = 10")
5625            .expect("nontrivial exact-prefix count should preserve its predicate")
5626        else {
5627            panic!("nontrivial exact-prefix count should return one projection row")
5628        };
5629        assert_eq!(rows, vec![vec![OutputValue::nat64(2)]]);
5630        assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5631        assert_eq!(IndexStore::current_entry_read_count(), index_reads_before);
5632
5633        let data_reads_before = DataStore::current_get_call_count();
5634        for (sql, expected) in [
5635            ("SELECT COUNT(*) FROM IdentityRow WHERE false", 0_u64),
5636            ("SELECT COUNT(*) FROM IdentityRow WHERE id = 1", 1),
5637        ] {
5638            let crate::db::SqlStatementResult::Projection { rows, .. } = session
5639                .execute_trusted_sql_query(sql)
5640                .expect("non-entity count control should succeed")
5641            else {
5642                panic!("non-entity count control should return one projection row")
5643            };
5644            assert_eq!(rows, vec![vec![OutputValue::nat64(expected)]], "{sql}");
5645        }
5646        assert!(DataStore::current_get_call_count() > data_reads_before);
5647
5648        session
5649            .execute_trusted_sql_query("SELECT COUNT(*) FROM IdentityRow LIMIT 1")
5650            .expect_err("unordered aggregate input pagination must remain rejected");
5651
5652        let data_reads_before = DataStore::current_get_call_count();
5653        let crate::db::SqlStatementResult::Projection { rows, .. } = session
5654            .execute_trusted_sql_query("SELECT COUNT(DISTINCT payload) FROM IdentityRow")
5655            .expect("distinct count should retain prepared execution")
5656        else {
5657            panic!("distinct count should return one projection row")
5658        };
5659        assert_eq!(rows, vec![vec![OutputValue::nat64(2)]]);
5660        assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5661    }
5662
5663    #[cfg(feature = "sql")]
5664    #[test]
5665    fn exact_count_composite_prefix_admits_seventeen_canonical_keys_only() {
5666        let session = initialize_with_composite_payload_index();
5667        for payload in [10, 10, 20] {
5668            insert_exact_key_fixture(&session, payload);
5669        }
5670        let ids_at_count_cap = (1_u64..=17)
5671            .map(|id| id.to_string())
5672            .collect::<Vec<_>>()
5673            .join(", ");
5674        let at_count_cap_sql = format!(
5675            "SELECT COUNT(*) FROM IdentityRow WHERE payload = 10 AND id IN ({ids_at_count_cap})",
5676        );
5677        let data_reads_before = DataStore::current_get_call_count();
5678        let index_reads_before = IndexStore::current_entry_read_count();
5679        assert_eq!(
5680            sql_projection_rows(&session, at_count_cap_sql.as_str()),
5681            vec![vec![OutputValue::nat64(2)]],
5682        );
5683        assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5684        assert_eq!(IndexStore::current_entry_read_count(), index_reads_before);
5685
5686        let authored_duplicate_sql = format!(
5687            "SELECT COUNT(*) FROM IdentityRow WHERE payload = 10 AND id IN (1, {ids_at_count_cap})",
5688        );
5689        assert_eq!(
5690            sql_projection_rows(&session, authored_duplicate_sql.as_str()),
5691            vec![vec![OutputValue::nat64(2)]],
5692        );
5693        assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5694        assert_eq!(IndexStore::current_entry_read_count(), index_reads_before);
5695
5696        let ids_over_count_cap = format!("{ids_at_count_cap}, 18");
5697        let over_count_cap_sql = format!(
5698            "SELECT COUNT(*) FROM IdentityRow WHERE payload = 10 AND id IN ({ids_over_count_cap})",
5699        );
5700        assert_eq!(
5701            sql_projection_rows(&session, over_count_cap_sql.as_str()),
5702            vec![vec![OutputValue::nat64(2)]],
5703        );
5704        assert!(DataStore::current_get_call_count() > data_reads_before);
5705    }
5706
5707    #[cfg(feature = "sql")]
5708    #[test]
5709    fn exact_count_nullable_field_uses_prepared_borrowed_primary_scan() {
5710        let session = initialize_with_snapshot(identity_snapshot_with_nullable_payload(STORE_PATH));
5711        session
5712            .execute_trusted_dynamic_insert_batch(
5713                ENTITY_NAME,
5714                vec![
5715                    dynamic_payload_patch(10),
5716                    DynamicStructuralPatch::new(Vec::new()),
5717                ],
5718            )
5719            .expect("nullable count fixture should insert");
5720
5721        let data_reads_before = DataStore::current_get_call_count();
5722        let crate::db::SqlStatementResult::Projection { rows, .. } = session
5723            .execute_trusted_sql_query("SELECT COUNT(payload) FROM IdentityRow")
5724            .expect("nullable count should retain prepared execution")
5725        else {
5726            panic!("nullable count should return one projection row")
5727        };
5728        assert_eq!(rows, vec![vec![OutputValue::nat64(1)]]);
5729        assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5730    }
5731
5732    #[cfg(feature = "sql")]
5733    #[test]
5734    fn indexed_extrema_nullable_field_uses_prepared_borrowed_primary_scan() {
5735        let session = initialize_with_snapshot(identity_snapshot_with_nullable_payload(STORE_PATH));
5736        session
5737            .execute_trusted_dynamic_insert_batch(
5738                ENTITY_NAME,
5739                vec![DynamicStructuralPatch::new(Vec::new())],
5740            )
5741            .expect("all-null extrema fixture should insert");
5742
5743        for sql in [
5744            "SELECT MIN(payload) FROM IdentityRow",
5745            "SELECT MAX(payload) FROM IdentityRow",
5746        ] {
5747            let data_reads_before = DataStore::current_get_call_count();
5748            let crate::db::SqlStatementResult::Projection { rows, .. } = session
5749                .execute_trusted_sql_query(sql)
5750                .expect("all-null extrema should retain complete reduction")
5751            else {
5752                panic!("all-null extrema should return one projection row")
5753            };
5754            assert_eq!(rows, vec![vec![OutputValue::null()]], "{sql}");
5755            assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5756        }
5757
5758        session
5759            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, vec![dynamic_payload_patch(10)])
5760            .expect("mixed nullable extrema fixture should insert");
5761
5762        for sql in [
5763            "SELECT MIN(payload) FROM IdentityRow",
5764            "SELECT MAX(payload) FROM IdentityRow",
5765        ] {
5766            let data_reads_before = DataStore::current_get_call_count();
5767            let crate::db::SqlStatementResult::Projection { rows, .. } = session
5768                .execute_trusted_sql_query(sql)
5769                .expect("mixed nullable extrema should retain complete reduction")
5770            else {
5771                panic!("mixed nullable extrema should return one projection row")
5772            };
5773            assert_eq!(rows, vec![vec![OutputValue::nat64(10)]], "{sql}");
5774            assert_eq!(DataStore::current_get_call_count(), data_reads_before);
5775        }
5776    }
5777
5778    #[test]
5779    fn exact_count_rejects_non_metadata_shapes_and_unready_cardinality() {
5780        let session = initialize();
5781        insert_exact_key_fixture(&session, 10);
5782        let rejected = [
5783            DynamicQuery::new(ENTITY_NAME).limit(1),
5784            DynamicQuery::new(ENTITY_NAME).select(["payload"]),
5785            DynamicQuery::new(ENTITY_NAME).order_by(crate::db::asc("payload")),
5786            DynamicQuery::new(ENTITY_NAME).filter(crate::db::FieldRef::new("id").eq(1_u64)),
5787            DynamicQuery::new(ENTITY_NAME).filter(crate::db::FilterExpr::and(vec![
5788                crate::db::FieldRef::new("payload").eq(10_u64),
5789                crate::db::FieldRef::new("id").eq(1_u64),
5790            ])),
5791            DynamicQuery::new(ENTITY_NAME)
5792                .filter(crate::db::FieldRef::new("payload").in_list(0_u64..=16)),
5793        ];
5794        for request in rejected {
5795            assert!(matches!(
5796                session.execute_public_exact_count(&request),
5797                Err(crate::db::QueryError::Execute(
5798                    QueryExecutionError::Unsupported(_)
5799                )),
5800            ));
5801        }
5802
5803        let journaled = initialize_journaled();
5804        insert_exact_key_fixture(&journaled, 10);
5805        assert!(matches!(
5806            journaled.execute_public_exact_count(&DynamicQuery::new(ENTITY_NAME)),
5807            Err(crate::db::QueryError::Execute(
5808                QueryExecutionError::Unsupported(_)
5809            )),
5810        ));
5811    }
5812
5813    #[test]
5814    fn exact_count_typed_binding_fails_closed_after_accepted_revision_changes() {
5815        let session = initialize();
5816        let binding = exact_key_binding(&session);
5817        let request = DynamicQuery::new(ENTITY_NAME);
5818        assert_eq!(
5819            session
5820                .execute_public_exact_count_for_typed_binding(&binding, &request)
5821                .unwrap(),
5822            Some(0),
5823        );
5824
5825        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
5826            STORE_PATH,
5827            AcceptedSchemaRevision::new(2),
5828            BTreeMap::from([(ENTITY_TAG, identity_snapshot(STORE_PATH, false))]),
5829            BTreeMap::from([
5830                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
5831                ((ENTITY_TAG, source_key(PAYLOAD_SOURCE)), FieldId::new(2)),
5832            ]),
5833        );
5834        let store = session
5835            .db
5836            .store_handle(STORE_PATH)
5837            .expect("exact-count store should resolve");
5838        crate::db::commit::publish_accepted_schema_candidate(
5839            STORE_PATH,
5840            store,
5841            AcceptedSchemaRevision::INITIAL,
5842            &candidate,
5843        )
5844        .expect("successor accepted schema should publish");
5845
5846        assert_eq!(
5847            session
5848                .execute_public_exact_count_for_typed_binding(&binding, &request)
5849                .unwrap(),
5850            None,
5851        );
5852    }
5853
5854    #[cfg(all(feature = "sql", feature = "diagnostics"))]
5855    fn identity_row_stored_bytes<C: CanisterKind>(
5856        session: &DbSession<C>,
5857        store_path: &'static str,
5858        key: u64,
5859    ) -> u64 {
5860        let data_key = DecodedDataStoreKey::try_from_structural_key(ENTITY_TAG, &Value::Nat64(key))
5861            .expect("identity row key should encode");
5862        let raw_key = data_key.to_raw().expect("identity raw key should encode");
5863        let store = session
5864            .db
5865            .recovered_store(store_path)
5866            .expect("identity store should resolve");
5867        store.with_data(|data_store| {
5868            u64::try_from(
5869                data_store
5870                    .get(&raw_key)
5871                    .expect("inserted identity row should exist")
5872                    .len(),
5873            )
5874            .expect("bounded row length should fit u64")
5875        })
5876    }
5877
5878    #[cfg(all(feature = "sql", feature = "diagnostics"))]
5879    fn with_stored_bytes_limit<T>(
5880        limit: u64,
5881        shape_fingerprint_prefix: u64,
5882        operation: impl FnOnce() -> Result<T, crate::db::query::intent::QueryError>,
5883    ) -> Result<T, crate::db::query::intent::QueryError> {
5884        let budget = HardExecutionBudget::uniform_for_tests(
5885            u64::MAX,
5886            HardExecutionFailureHeadroom::new(500, 256),
5887        )
5888        .with_limit_for_tests(
5889            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::StoredBytesRead,
5890            limit,
5891        );
5892        let context = HardExecutionContext::new(
5893            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
5894            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
5895            shape_fingerprint_prefix,
5896        );
5897
5898        with_query_execution_budget_for_tests(budget, context, operation)
5899    }
5900
5901    #[cfg(all(feature = "sql", feature = "diagnostics"))]
5902    fn advance_with_exhausted_mutation_predicate_budget(
5903        session: &DbSession<JournaledTestCanister>,
5904        request: &MutationJobAdvanceRequest,
5905    ) -> Result<crate::db::MutationJobAdvanceReceipt, MutationJobError> {
5906        let budget = HardExecutionBudget::uniform_for_tests(
5907            u64::MAX,
5908            HardExecutionFailureHeadroom::new(1_000_000_000, 64 * 1_024),
5909        )
5910        .with_limit_for_tests(
5911            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::PredicateExpressionSteps,
5912            0,
5913        );
5914        let context = HardExecutionContext::new(
5915            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
5916            icydb_diagnostic_code::DiagnosticExecutionLane::Mutation,
5917            0x6d75_7461_7465_7465,
5918        );
5919        with_execution_budget_for_tests(
5920            budget,
5921            context,
5922            || session.advance_trusted_mutation_job(request),
5923            |_| MutationJobError::Internal,
5924        )
5925    }
5926
5927    #[cfg(all(feature = "sql", feature = "diagnostics"))]
5928    const fn exact_key(value: u64) -> PrimaryKeyValue {
5929        PrimaryKeyValue::Scalar(PrimaryKeyComponent::Nat64(value))
5930    }
5931
5932    #[cfg(all(feature = "sql", feature = "diagnostics"))]
5933    fn assert_exact_key_batch<C: CanisterKind>(session: &DbSession<C>) {
5934        let first = insert_exact_key_fixture(session, 41);
5935        let second = insert_exact_key_fixture(session, 42);
5936        let missing = u64::MAX;
5937        let binding = exact_key_binding(session);
5938        let gets_before = DataStore::current_get_call_count();
5939        let result = session
5940            .execute_public_exact_key_batch_for_typed_binding(
5941                &binding,
5942                &[
5943                    exact_key(second),
5944                    exact_key(missing),
5945                    exact_key(first),
5946                    exact_key(second),
5947                ],
5948            )
5949            .expect("exact-key batch should execute")
5950            .expect("exact-key binding should remain current");
5951
5952        assert_eq!(result.positions, vec![0, 1, 2, 0]);
5953        assert_eq!(
5954            result.distinct_rows,
5955            vec![
5956                Some(expected_dynamic_row(second, 42)),
5957                None,
5958                Some(expected_dynamic_row(first, 41)),
5959            ],
5960        );
5961        assert_eq!(
5962            DataStore::current_get_call_count().saturating_sub(gets_before),
5963            3,
5964            "four input positions with one duplicate must perform three physical reads",
5965        );
5966    }
5967
5968    #[cfg(all(feature = "sql", feature = "diagnostics"))]
5969    #[test]
5970    fn exact_key_batches_preserve_semantics_across_heap_and_journaled_stores() {
5971        assert_exact_key_batch(&initialize());
5972        assert_exact_key_batch(&initialize_journaled());
5973    }
5974
5975    #[cfg(all(feature = "sql", feature = "diagnostics"))]
5976    fn assert_primary_range_materialization_fetches_once<C: CanisterKind>(
5977        session: &DbSession<C>,
5978        store_path: &'static str,
5979    ) {
5980        let key = insert_exact_key_fixture(session, 41);
5981        let stored_bytes = identity_row_stored_bytes(session, store_path, key);
5982
5983        let scalar = DynamicQuery::new(ENTITY_NAME)
5984            .select(["id", "payload"])
5985            .order_by(asc("id"))
5986            .limit(1);
5987        let gets_before = DataStore::current_get_call_count();
5988        let scalar_page = with_stored_bytes_limit(stored_bytes, 0x7072_696d_6172_792d, || {
5989            session.execute_trusted_live_page(&scalar, None)
5990        })
5991        .expect("one scalar primary-range row should fit one payload-read allowance");
5992        assert_eq!(scalar_page.row_count, 1);
5993        assert_eq!(
5994            DataStore::current_get_call_count().saturating_sub(gets_before),
5995            1,
5996            "scalar primary traversal should fetch its emitted row exactly once",
5997        );
5998
5999        let grouped = DynamicQuery::new(ENTITY_NAME)
6000            .group_by("payload")
6001            .aggregate(crate::db::count())
6002            .grouped_limits(10, 16 * 1_024)
6003            .limit(1);
6004        let gets_before = DataStore::current_get_call_count();
6005        let grouped_page = with_stored_bytes_limit(stored_bytes, 0x6772_6f75_7065_642d, || {
6006            session.execute_trusted_dynamic_grouped_query(&grouped)
6007        })
6008        .expect("one grouped primary-range row should fit one payload-read allowance");
6009        assert_eq!(grouped_page.row_count, 1);
6010        assert_eq!(
6011            DataStore::current_get_call_count().saturating_sub(gets_before),
6012            1,
6013            "grouped primary traversal should fetch its source row exactly once",
6014        );
6015    }
6016
6017    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6018    #[test]
6019    fn row_materialization_fetches_each_required_payload_at_most_once() {
6020        assert_primary_range_materialization_fetches_once(&initialize(), STORE_PATH);
6021        assert_primary_range_materialization_fetches_once(
6022            &initialize_journaled(),
6023            JOURNALED_STORE_PATH,
6024        );
6025    }
6026
6027    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6028    #[test]
6029    fn ordered_grouped_pages_close_a_group_spanning_physical_refills_before_resume() {
6030        let session = initialize();
6031        let mut patches = Vec::new();
6032        for _ in 0..70 {
6033            patches.push(dynamic_payload_patch(10));
6034        }
6035        for _ in 0..3 {
6036            patches.push(dynamic_payload_patch(20));
6037        }
6038        patches.push(dynamic_payload_patch(30));
6039        let inserted = session
6040            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, patches)
6041            .expect("ordered grouped continuation rows should insert");
6042        assert_eq!(inserted.rows.len(), 74);
6043
6044        let query = DynamicQuery::new(ENTITY_NAME)
6045            .group_by("payload")
6046            .aggregate(crate::db::count())
6047            .aggregate(crate::db::sum("id"))
6048            .order_by(asc("payload"))
6049            .grouped_limits(4, 16 * 1_024)
6050            .limit(1);
6051        let expected = [
6052            (10_u64, 70_u64, crate::types::Decimal::new(2_485, 0)),
6053            (20, 3, crate::types::Decimal::new(216, 0)),
6054            (30, 1, crate::types::Decimal::new(74, 0)),
6055        ];
6056        let mut continuation: Option<String> = None;
6057        let mut seen_cursors = std::collections::BTreeSet::new();
6058
6059        for (page_index, (group_key, row_count, id_sum)) in expected.into_iter().enumerate() {
6060            let request = continuation.as_ref().map_or_else(
6061                || query.clone(),
6062                |cursor| query.clone().cursor(cursor.clone()),
6063            );
6064            let entries_before = IndexStore::current_entry_read_count();
6065            let rows_before = DataStore::current_get_call_count();
6066            let page = session
6067                .execute_trusted_dynamic_grouped_query(&request)
6068                .unwrap_or_else(|error| {
6069                    panic!("ordered grouped page {page_index} should execute: {error:?}")
6070                });
6071            let entries_read =
6072                IndexStore::current_entry_read_count().saturating_sub(entries_before);
6073            let rows_read = DataStore::current_get_call_count().saturating_sub(rows_before);
6074
6075            assert_eq!(page.row_count, 1);
6076            let [row] = page.rows.as_slice() else {
6077                panic!("ordered grouped page must contain exactly one closed group")
6078            };
6079            assert_eq!(row.group_key(), &[OutputValue::nat64(group_key)]);
6080            assert_eq!(
6081                row.aggregate_values(),
6082                &[OutputValue::nat64(row_count), OutputValue::decimal(id_sum),],
6083            );
6084            if page_index == 0 {
6085                assert!(
6086                    entries_read.saturating_add(rows_read) >= 70,
6087                    "the first closed group must span the maintained 64-entry physical refill",
6088                );
6089            }
6090
6091            continuation = page.next_cursor;
6092            if page_index + 1 < expected.len() {
6093                let cursor = continuation
6094                    .as_ref()
6095                    .expect("another closed group should retain continuation");
6096                assert!(
6097                    seen_cursors.insert(cursor.clone()),
6098                    "ordered grouped continuation must advance monotonically",
6099                );
6100            } else {
6101                assert_eq!(continuation, None);
6102            }
6103        }
6104    }
6105
6106    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6107    #[test]
6108    fn exhaustive_pages_require_and_recompare_the_complete_source_proof() {
6109        let session = initialize();
6110        let first = insert_exact_key_fixture(&session, 41);
6111        let second = insert_exact_key_fixture(&session, 42);
6112        let third = insert_exact_key_fixture(&session, 43);
6113        let query = DynamicQuery::new(ENTITY_NAME)
6114            .select(["id", "payload"])
6115            .order_by(asc("id"));
6116
6117        let page = session
6118            .execute_trusted_exhaustive_page(&query, None, None)
6119            .expect("initial exhaustive page should capture its source proof");
6120        assert_eq!(
6121            page.rows,
6122            vec![
6123                expected_dynamic_row(first, 41),
6124                expected_dynamic_row(second, 42),
6125            ],
6126        );
6127        let continuation = page
6128            .continuation
6129            .as_deref()
6130            .expect("unreturned row should retain exhaustive continuation");
6131        assert!(matches!(
6132            session.execute_trusted_exhaustive_page(&query, Some(continuation), None),
6133            Err(ExhaustiveReadError::Revision(
6134                ReadSetRevisionError::ResumeProofRequired
6135            )),
6136        ));
6137        let resumed = session
6138            .execute_trusted_exhaustive_page(&query, Some(continuation), Some(&page.proof))
6139            .expect("unchanged proof should resume exhaustive traversal");
6140        assert_eq!(resumed.rows, vec![expected_dynamic_row(third, 43)]);
6141        assert_eq!(resumed.continuation, None);
6142
6143        let stale_page = session
6144            .execute_trusted_exhaustive_page(&query, None, None)
6145            .expect("fresh exhaustive page should capture current revision");
6146        let stale_continuation = stale_page
6147            .continuation
6148            .as_deref()
6149            .expect("fresh three-row traversal should retain continuation");
6150        let _ = insert_exact_key_fixture(&session, 44);
6151        assert!(matches!(
6152            session.execute_trusted_exhaustive_page(
6153                &query,
6154                Some(stale_continuation),
6155                Some(&stale_page.proof),
6156            ),
6157            Err(ExhaustiveReadError::Revision(
6158                ReadSetRevisionError::StoreDataChanged { .. }
6159            )),
6160        ));
6161    }
6162
6163    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6164    #[test]
6165    fn heap_sources_cannot_back_durable_resumable_jobs() {
6166        let session = initialize();
6167        let proof = session
6168            .capture_read_set_revision_proof(&[ENTITY_NAME])
6169            .expect("heap source proof should capture for one-call exhaustive reads");
6170        let job_id = ResumableJobId::try_from_bytes([70; 32])
6171            .expect("nonzero heap test job identity should admit");
6172
6173        assert!(matches!(
6174            session.start_resumable_job(job_id, proof, Vec::new()),
6175            Err(ResumableJobError::SourceProof(
6176                ReadSetRevisionError::DurableStoreRequired { .. }
6177            )),
6178        ));
6179    }
6180
6181    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6182    #[test]
6183    fn proof_and_progress_controls_charge_one_shared_request_scope() {
6184        let (session, root) = initialize_journaled_with_root();
6185        let resource = icydb_diagnostic_code::DiagnosticExecutionBudgetResource::QueryExecutions;
6186        let before = root.observed(resource);
6187        let proof = session
6188            .capture_read_set_revision_proof(&[ENTITY_NAME])
6189            .expect("proof capture should use the retained request scope");
6190        let job_id = ResumableJobId::try_from_bytes([75; 32])
6191            .expect("nonzero accounting job identity should admit");
6192        session
6193            .start_resumable_job(job_id, proof, Vec::new())
6194            .expect("job start should use the same retained request scope");
6195        let _ = session
6196            .resumable_job_state(job_id)
6197            .expect("job load should use the same retained request scope");
6198
6199        assert_eq!(root.observed(resource).saturating_sub(before), 3);
6200    }
6201
6202    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6203    #[test]
6204    fn source_proofs_ignore_unrelated_stores_but_bind_access_state_changes() {
6205        let session = initialize();
6206        let proof = session
6207            .capture_read_set_revision_proof(&[ENTITY_NAME])
6208            .expect("source proof should cover only the entity's physical store");
6209        let shared_store_proof = session
6210            .capture_read_set_revision_proof(&[ENTITY_NAME, ENTITY_NAME])
6211            .expect("entities sharing one physical source should deduplicate");
6212        assert_eq!(shared_store_proof, proof);
6213        assert_eq!(shared_store_proof.stores().len(), 1);
6214        let unrelated = session
6215            .db
6216            .store_handle(UNRELATED_STORE_PATH)
6217            .expect("unrelated registered store should resolve");
6218        unrelated.with_data_mut(|store| {
6219            let _ = store.remove(&RawDataStoreKey::from_persisted_bytes(vec![1]));
6220        });
6221        session
6222            .verify_read_set_revision_proof(&proof)
6223            .expect("a nonparticipating store mutation must not invalidate the proof");
6224
6225        let source = session
6226            .db
6227            .store_handle(STORE_PATH)
6228            .expect("participating source store should resolve");
6229        source
6230            .mark_index_building()
6231            .expect("source access-state transition should advance its revision");
6232        assert!(matches!(
6233            session.verify_read_set_revision_proof(&proof),
6234            Err(ExhaustiveReadError::Revision(
6235                ReadSetRevisionError::StoreAccessChanged { .. }
6236            )),
6237        ));
6238    }
6239
6240    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6241    #[expect(
6242        clippy::too_many_lines,
6243        reason = "one lifecycle test proves successful replay plus pre-page and post-page source invalidation without sharing progress state across tests"
6244    )]
6245    #[test]
6246    fn journaled_job_advance_is_idempotent_and_revision_checked_on_both_sides() {
6247        let session = initialize_journaled();
6248        let proof = session
6249            .capture_read_set_revision_proof(&[ENTITY_NAME])
6250            .expect("journaled source proof should capture");
6251        let job_id =
6252            ResumableJobId::try_from_bytes([71; 32]).expect("nonzero job identity should admit");
6253        session
6254            .start_resumable_job(job_id, proof, vec![0])
6255            .expect("journaled job should start outside its protected source revision");
6256        let request = ResumableJobAdvanceRequest::new(
6257            job_id,
6258            0,
6259            ResumableJobIdempotencyKey::new("page-0")
6260                .expect("bounded idempotency key should admit"),
6261        );
6262        let calls = Cell::new(0_u8);
6263        let receipt = session
6264            .compare_proof_and_advance(&request, |state| {
6265                calls.set(calls.get() + 1);
6266                assert_eq!(state.application_state, vec![0]);
6267                Ok::<_, ()>(
6268                    ResumableJobAdvance::new(Some("cursor-1".to_string()), vec![1], vec![9])
6269                        .expect("bounded application advance should admit"),
6270                )
6271            })
6272            .expect("unchanged source should advance exactly once");
6273        assert_eq!(calls.get(), 1);
6274        assert_eq!(receipt.status, ResumableJobAdvanceStatus::Advanced);
6275        assert_eq!(receipt.committed_sequence, 1);
6276
6277        let replay = session
6278            .compare_proof_and_advance::<()>(&request, |_| {
6279                panic!("lost-response replay must not execute application work")
6280            })
6281            .expect("same request identity should return its persisted receipt");
6282        assert_eq!(replay, receipt);
6283        let retained = session
6284            .resumable_job_state(job_id)
6285            .expect("advanced state should remain durable");
6286        assert_eq!(retained.sequence, 1);
6287        assert_eq!(retained.application_state, vec![1]);
6288
6289        let _ = insert_exact_key_fixture(&session, 51);
6290        let pre_change_request = ResumableJobAdvanceRequest::new(
6291            job_id,
6292            1,
6293            ResumableJobIdempotencyKey::new("page-1")
6294                .expect("bounded idempotency key should admit"),
6295        );
6296        let pre_change_calls = Cell::new(0_u8);
6297        let invalidated = session
6298            .compare_proof_and_advance::<()>(&pre_change_request, |_| {
6299                pre_change_calls.set(pre_change_calls.get() + 1);
6300                unreachable!("pre-page proof failure must reject before application work")
6301            })
6302            .expect("source drift should persist one replayable invalidation receipt");
6303        assert_eq!(pre_change_calls.get(), 0);
6304        assert_eq!(invalidated.status, ResumableJobAdvanceStatus::Invalidated);
6305        let invalidated_state = session
6306            .resumable_job_state(job_id)
6307            .expect("invalidated job should remain inspectable");
6308        assert_eq!(invalidated_state.status, ResumableJobStatus::Invalidated);
6309        assert_eq!(invalidated_state.continuation, None);
6310        assert_eq!(invalidated_state.application_state, vec![1]);
6311        assert_eq!(
6312            session
6313                .compare_proof_and_advance::<()>(&pre_change_request, |_| {
6314                    panic!("invalidation replay must not execute application work")
6315                })
6316                .expect("lost invalidation reply should replay exactly"),
6317            invalidated,
6318        );
6319
6320        let post_proof = session
6321            .capture_read_set_revision_proof(&[ENTITY_NAME])
6322            .expect("post-change journaled proof should capture");
6323        let post_job_id = ResumableJobId::try_from_bytes([72; 32])
6324            .expect("nonzero post-change job identity should admit");
6325        session
6326            .start_resumable_job(post_job_id, post_proof, vec![7])
6327            .expect("post-change journaled job should start");
6328        let post_request = ResumableJobAdvanceRequest::new(
6329            post_job_id,
6330            0,
6331            ResumableJobIdempotencyKey::new("post-page-0")
6332                .expect("bounded idempotency key should admit"),
6333        );
6334        let post_receipt = session
6335            .compare_proof_and_advance::<()>(&post_request, |_| {
6336                let _ = insert_exact_key_fixture(&session, 52);
6337                Ok(ResumableJobAdvance::new(None, vec![8], vec![10])
6338                    .expect("bounded post-change candidate should admit"))
6339            })
6340            .expect("post-page drift should discard the candidate and persist invalidation");
6341        assert_eq!(post_receipt.status, ResumableJobAdvanceStatus::Invalidated);
6342        let post_state = session
6343            .resumable_job_state(post_job_id)
6344            .expect("post-page invalidation should remain inspectable");
6345        assert_eq!(post_state.status, ResumableJobStatus::Invalidated);
6346        assert_eq!(post_state.application_state, vec![7]);
6347        session
6348            .acknowledge_resumable_job(post_job_id, post_state.sequence)
6349            .expect("terminal job acknowledgement should remove retained progress");
6350        session
6351            .acknowledge_resumable_job(post_job_id, post_state.sequence)
6352            .expect("lost acknowledgement reply should be safely replayable");
6353        assert_eq!(
6354            session.resumable_job_state(post_job_id),
6355            Err(ResumableJobError::NotFound),
6356        );
6357
6358        let completed_job_id = ResumableJobId::try_from_bytes([74; 32])
6359            .expect("nonzero completed job identity should admit");
6360        let completed_proof = session
6361            .capture_read_set_revision_proof(&[ENTITY_NAME])
6362            .expect("completed-job source proof should capture");
6363        session
6364            .start_resumable_job(completed_job_id, completed_proof, Vec::new())
6365            .expect("completed-job fixture should start");
6366        let completed_request = ResumableJobAdvanceRequest::new(
6367            completed_job_id,
6368            0,
6369            ResumableJobIdempotencyKey::new("complete")
6370                .expect("bounded completion key should admit"),
6371        );
6372        let completed_receipt = session
6373            .compare_proof_and_advance::<()>(&completed_request, |_| {
6374                Ok(ResumableJobAdvance::new(None, vec![99], vec![100])
6375                    .expect("bounded terminal advance should admit"))
6376            })
6377            .expect("null continuation should commit terminal completion");
6378        let completed_state = session
6379            .resumable_job_state(completed_job_id)
6380            .expect("completed state should remain replayable before acknowledgement");
6381        assert_eq!(completed_state.status, ResumableJobStatus::Completed);
6382        assert_eq!(
6383            session
6384                .compare_proof_and_advance::<()>(&completed_request, |_| {
6385                    panic!("completed request replay must not execute application work")
6386                })
6387                .expect("completed request should replay until acknowledgement"),
6388            completed_receipt,
6389        );
6390        let after_completion = ResumableJobAdvanceRequest::new(
6391            completed_job_id,
6392            1,
6393            ResumableJobIdempotencyKey::new("after-complete")
6394                .expect("bounded post-completion key should admit"),
6395        );
6396        assert!(matches!(
6397            session.compare_proof_and_advance::<()>(&after_completion, |_| {
6398                panic!("completed jobs cannot execute another page")
6399            }),
6400            Err(CompareProofAndAdvanceError::Protocol(
6401                ResumableJobError::Completed
6402            )),
6403        ));
6404        session
6405            .acknowledge_resumable_job(completed_job_id, completed_state.sequence)
6406            .expect("completed job should acknowledge and free capacity");
6407        session
6408            .acknowledge_resumable_job(completed_job_id, completed_state.sequence)
6409            .expect("completion acknowledgement should be idempotent");
6410
6411        let stale_job_id = ResumableJobId::try_from_bytes([73; 32])
6412            .expect("nonzero stale-sequence job identity should admit");
6413        let stale_proof = session
6414            .capture_read_set_revision_proof(&[ENTITY_NAME])
6415            .expect("stale-sequence source proof should capture");
6416        session
6417            .start_resumable_job(stale_job_id, stale_proof, Vec::new())
6418            .expect("stale-sequence job should start");
6419        let stale_request = ResumableJobAdvanceRequest::new(
6420            stale_job_id,
6421            4,
6422            ResumableJobIdempotencyKey::new("stale").expect("bounded idempotency key should admit"),
6423        );
6424        assert!(matches!(
6425            session.compare_proof_and_advance::<()>(&stale_request, |_| {
6426                panic!("stale sequence must reject before application work")
6427            }),
6428            Err(CompareProofAndAdvanceError::Protocol(
6429                ResumableJobError::StaleSequence {
6430                    expected: 4,
6431                    actual: 0,
6432                }
6433            )),
6434        ));
6435        assert_eq!(
6436            session.acknowledge_resumable_job(stale_job_id, 0),
6437            Err(ResumableJobError::NotTerminal),
6438        );
6439    }
6440
6441    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6442    #[test]
6443    fn exact_key_batch_uses_typed_hard_execution_budget() {
6444        let session = initialize();
6445        let binding = exact_key_binding(&session);
6446        let budget =
6447            HardExecutionBudget::uniform_for_tests(0, HardExecutionFailureHeadroom::new(500, 256));
6448        let error = session
6449            .execute_exact_key_batch_with_hard_budget_for_tests(
6450                &binding,
6451                &[exact_key(u64::MAX)],
6452                &budget,
6453            )
6454            .expect_err("zero query budget should reject the exact-key route");
6455
6456        assert!(matches!(
6457            error.diagnostic().detail(),
6458            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
6459                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
6460            })
6461        ));
6462        let facts = error.diagnostic_facts();
6463        assert_eq!(
6464            &facts[..5],
6465            &[
6466                (
6467                    icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
6468                    icydb_diagnostic_code::DiagnosticExecutionBudgetResource::QueryExecutions.raw(),
6469                ),
6470                (icydb_diagnostic_code::DiagnosticFactTag::Limit, 0),
6471                (icydb_diagnostic_code::DiagnosticFactTag::Actual, 1),
6472                (
6473                    icydb_diagnostic_code::DiagnosticFactTag::ExecutionBudgetScope,
6474                    icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution.raw(),
6475                ),
6476                (
6477                    icydb_diagnostic_code::DiagnosticFactTag::ExecutionLane,
6478                    icydb_diagnostic_code::DiagnosticExecutionLane::PublicRead.raw(),
6479                ),
6480            ],
6481        );
6482        assert_eq!(
6483            facts[5].0,
6484            icydb_diagnostic_code::DiagnosticFactTag::QueryShapeFingerprintPrefix,
6485        );
6486        assert_ne!(facts[5].1, 0);
6487    }
6488
6489    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6490    fn assert_planned_query_exhausts(
6491        session: &DbSession<TestCanister>,
6492        query: &crate::db::DynamicQuery,
6493        resource: icydb_diagnostic_code::DiagnosticExecutionBudgetResource,
6494    ) {
6495        let budget = HardExecutionBudget::uniform_for_tests(
6496            u64::MAX,
6497            HardExecutionFailureHeadroom::new(500, 256),
6498        )
6499        .with_limit_for_tests(resource, 0);
6500        let context = HardExecutionContext::new(
6501            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
6502            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
6503            0x7068_7973_6963_616c,
6504        );
6505        let error = with_query_execution_budget_for_tests(budget, context, || {
6506            session.execute_trusted_live_page(query, None)
6507        })
6508        .expect_err("the injected zero resource allowance should reject planned execution");
6509
6510        assert!(matches!(
6511            error.diagnostic().detail(),
6512            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
6513                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
6514            })
6515        ));
6516        assert_eq!(
6517            error.diagnostic_facts()[0],
6518            (
6519                icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
6520                resource.raw(),
6521            ),
6522        );
6523    }
6524
6525    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6526    fn assert_grouped_query_exhausts(
6527        session: &DbSession<TestCanister>,
6528        query: &crate::db::DynamicQuery,
6529        resource: icydb_diagnostic_code::DiagnosticExecutionBudgetResource,
6530    ) {
6531        let budget = HardExecutionBudget::uniform_for_tests(
6532            u64::MAX,
6533            HardExecutionFailureHeadroom::new(500, 256),
6534        )
6535        .with_limit_for_tests(resource, 0);
6536        let context = HardExecutionContext::new(
6537            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
6538            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
6539            0x6772_6f75_7065_642d,
6540        );
6541        let error = with_query_execution_budget_for_tests(budget, context, || {
6542            session.execute_trusted_dynamic_grouped_query(query)
6543        })
6544        .expect_err("the injected zero resource allowance should reject grouped execution");
6545
6546        assert!(matches!(
6547            error.diagnostic().detail(),
6548            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
6549                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
6550            })
6551        ));
6552        assert_eq!(
6553            error.diagnostic_facts()[0],
6554            (
6555                icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
6556                resource.raw(),
6557            ),
6558        );
6559    }
6560
6561    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6562    fn assert_sql_query_exhausts(
6563        session: &DbSession<TestCanister>,
6564        sql: &str,
6565        resource: icydb_diagnostic_code::DiagnosticExecutionBudgetResource,
6566    ) {
6567        let budget = HardExecutionBudget::uniform_for_tests(
6568            u64::MAX,
6569            HardExecutionFailureHeadroom::new(500, 256),
6570        )
6571        .with_limit_for_tests(resource, 0);
6572        let context = HardExecutionContext::new(
6573            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
6574            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
6575            0x7371_6c2d_736f_7274,
6576        );
6577        let error = with_query_execution_budget_for_tests(budget, context, || {
6578            session.execute_trusted_sql_query(sql)
6579        })
6580        .expect_err("the injected zero resource allowance should reject SQL execution");
6581
6582        assert!(matches!(
6583            error.diagnostic().detail(),
6584            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
6585                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
6586            })
6587        ));
6588        assert_eq!(
6589            error.diagnostic_facts()[0],
6590            (
6591                icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
6592                resource.raw(),
6593            ),
6594        );
6595    }
6596
6597    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6598    fn assert_sql_query_fits_resource_limit(
6599        session: &DbSession<TestCanister>,
6600        sql: &str,
6601        resource: icydb_diagnostic_code::DiagnosticExecutionBudgetResource,
6602        limit: u64,
6603    ) {
6604        let budget = HardExecutionBudget::uniform_for_tests(
6605            u64::MAX,
6606            HardExecutionFailureHeadroom::new(500, 256),
6607        )
6608        .with_limit_for_tests(resource, limit);
6609        let context = HardExecutionContext::new(
6610            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
6611            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
6612            0x7371_6c2d_626f_756e,
6613        );
6614        with_query_execution_budget_for_tests(budget, context, || {
6615            session.execute_trusted_sql_query(sql)
6616        })
6617        .expect("bounded SQL execution should fit its physical-work limit");
6618    }
6619
6620    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6621    #[test]
6622    fn planned_read_routes_share_physical_resource_accounting() {
6623        let session = initialize();
6624        let first = insert_exact_key_fixture(&session, 41);
6625        insert_exact_key_fixture(&session, 42);
6626
6627        let fallback = crate::db::DynamicQuery::new(ENTITY_NAME)
6628            .filter(crate::db::FieldRef::new("id").eq(first))
6629            .select(["id", "payload"])
6630            .order_by(crate::db::asc("id"))
6631            .limit(1);
6632        assert_eq!(
6633            session
6634                .execute_trusted_live_page(&fallback, None)
6635                .expect("bounded fallback execution should preserve its result")
6636                .row_count,
6637            1,
6638        );
6639        assert_planned_query_exhausts(
6640            &session,
6641            &fallback,
6642            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::RowsVisited,
6643        );
6644
6645        let covering = crate::db::DynamicQuery::new(ENTITY_NAME)
6646            .filter(crate::db::FieldRef::new("payload").eq(41_u64))
6647            .select(["payload"])
6648            .order_by(crate::db::asc("payload"))
6649            .limit(1);
6650        assert_eq!(
6651            session
6652                .execute_trusted_live_page(&covering, None)
6653                .expect("bounded covering execution should preserve its result")
6654                .row_count,
6655            1,
6656        );
6657        assert_planned_query_exhausts(
6658            &session,
6659            &covering,
6660            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::KeyIndexEntriesVisited,
6661        );
6662
6663        let residual = crate::db::DynamicQuery::new(ENTITY_NAME)
6664            .filter(crate::db::FieldRef::new("payload").eq_field("id"))
6665            .select(["id"])
6666            .order_by(crate::db::asc("id"))
6667            .limit(1);
6668        assert_eq!(
6669            session
6670                .execute_trusted_live_page(&residual, None)
6671                .expect("bounded residual execution should preserve its result")
6672                .row_count,
6673            0,
6674        );
6675        assert_planned_query_exhausts(
6676            &session,
6677            &residual,
6678            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::PredicateExpressionSteps,
6679        );
6680
6681        assert_planned_query_exhausts(
6682            &session,
6683            &fallback,
6684            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::ResultBytes,
6685        );
6686
6687        let grouped = crate::db::DynamicQuery::new(ENTITY_NAME)
6688            .group_by("payload")
6689            .aggregate(crate::db::count())
6690            .order_by(crate::db::asc("payload"))
6691            .grouped_limits(10, 16 * 1_024)
6692            .limit(1);
6693        let grouped_result = session
6694            .execute_trusted_dynamic_grouped_query(&grouped)
6695            .expect("bounded grouped execution should preserve its result");
6696        assert_eq!(grouped_result.row_count, 1);
6697        assert!(grouped_result.next_cursor.is_some());
6698        assert_grouped_query_exhausts(
6699            &session,
6700            &grouped,
6701            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::GroupDistinctEntries,
6702        );
6703        assert_grouped_query_exhausts(
6704            &session,
6705            &grouped,
6706            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::CursorSteps,
6707        );
6708
6709        assert_sql_query_exhausts(
6710            &session,
6711            "SELECT payload, COUNT(*) AS row_count FROM IdentityRow \
6712             GROUP BY payload ORDER BY row_count DESC, payload ASC LIMIT 1",
6713            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::SortEntries,
6714        );
6715    }
6716
6717    #[cfg(all(feature = "sql", feature = "diagnostics"))]
6718    #[test]
6719    fn mutation_execution_budget_exhaustion_terminalizes_forward_and_verify() {
6720        let (session, _root) = initialize_journaled_with_root();
6721        assert_eq!(insert_exact_key_fixture(&session, 41), 1);
6722
6723        for (identity, sql, expected_phase) in [
6724            (
6725                91_u8,
6726                "UPDATE IdentityRow SET payload = 42 WHERE id = 1",
6727                MutationJobPhase::Forward,
6728            ),
6729            (
6730                92_u8,
6731                "UPDATE IdentityRow SET payload = 42 WHERE id = 999",
6732                MutationJobPhase::Verify,
6733            ),
6734        ] {
6735            let job_id = MutationJobId::try_from_bytes([identity; 32])
6736                .expect("budget fixture identity should admit");
6737            let mut state = session
6738                .start_trusted_sql_mutation_job(job_id, sql)
6739                .expect("budget fixture job should start");
6740            if expected_phase == MutationJobPhase::Verify {
6741                let forward = MutationJobAdvanceRequest::new(
6742                    job_id,
6743                    state.sequence,
6744                    MutationJobIdempotencyKey::new(format!("budget-forward-{identity}"))
6745                        .expect("bounded Forward replay identity should admit"),
6746                );
6747                let receipt = session
6748                    .advance_trusted_mutation_job(&forward)
6749                    .expect("nonmatching Forward page should enter Verify");
6750                assert_eq!(receipt.phase, MutationJobPhase::Verify);
6751                state = session
6752                    .mutation_job_state(job_id)
6753                    .expect("Verify predecessor should remain readable");
6754            }
6755            assert_eq!(state.phase, expected_phase);
6756
6757            let request = MutationJobAdvanceRequest::new(
6758                job_id,
6759                state.sequence,
6760                MutationJobIdempotencyKey::new(format!("budget-exhaust-{identity}"))
6761                    .expect("bounded exhaustion replay identity should admit"),
6762            );
6763            let terminal = advance_with_exhausted_mutation_predicate_budget(&session, &request)
6764                .expect("admitted execution-budget failure should commit terminal progress");
6765            assert_eq!(
6766                terminal.status,
6767                MutationJobStatus::RestartRequired(
6768                    MutationJobRestartReason::ExecutionBudgetPolicyExceeded,
6769                ),
6770            );
6771            assert_eq!(terminal.rows_updated, 0);
6772            assert_eq!(
6773                session.advance_trusted_mutation_job(&request),
6774                Ok(terminal.clone()),
6775                "exact terminal replay must not execute the exhausted page again",
6776            );
6777            assert_dynamic_payload(&session, 1, 41);
6778            session
6779                .acknowledge_mutation_job(job_id, terminal.committed_sequence)
6780                .expect("terminal budget fixture should acknowledge");
6781        }
6782    }
6783
6784    fn assert_dynamic_payload<C: CanisterKind>(
6785        session: &DbSession<C>,
6786        key: u64,
6787        expected_payload: u64,
6788    ) {
6789        let unchanged = session
6790            .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
6791                entity: ENTITY_NAME.to_string(),
6792                key: InputValue::nat64(key),
6793                patch: dynamic_payload_patch(expected_payload),
6794            })
6795            .expect("the expected row should remain readable through a no-op update");
6796        assert_eq!(unchanged.affected_rows, 0);
6797        assert_eq!(
6798            unchanged.rows,
6799            vec![expected_dynamic_row(key, expected_payload)],
6800        );
6801    }
6802
6803    fn assert_exact_batch_backlog_pressure(
6804        pressure: &InternalError,
6805        before: JournalTailControl,
6806        next_sequence: u64,
6807    ) {
6808        assert_eq!(
6809            pressure.diagnostic().error_code(),
6810            icydb_diagnostic_code::ErrorCode::RUNTIME_BOUNDARY_CONVERGENCE_BACKLOG_PRESSURE,
6811        );
6812        assert_eq!(
6813            pressure.diagnostic_facts(),
6814            vec![
6815                (
6816                    icydb_diagnostic_code::DiagnosticFactTag::BacklogResource,
6817                    icydb_diagnostic_code::DiagnosticBacklogResource::Batches.raw(),
6818                ),
6819                (icydb_diagnostic_code::DiagnosticFactTag::CurrentCount, 64),
6820                (icydb_diagnostic_code::DiagnosticFactTag::ProposedCount, 1),
6821                (icydb_diagnostic_code::DiagnosticFactTag::Limit, 64),
6822            ],
6823        );
6824        assert_eq!(
6825            crate::db::commit::next_database_commit_sequence()
6826                .expect("pressure must leave the database sequence readable"),
6827            next_sequence,
6828        );
6829        assert!(matches!(
6830            crate::db::commit::observe_commit_control()
6831                .expect("pressure must leave commit control observable"),
6832            crate::db::commit::CommitControlObservation::Present {
6833                marker_present: false,
6834                ..
6835            },
6836        ));
6837        assert_eq!(
6838            JOURNALED_TAIL_STORE.with(|tail| {
6839                tail.borrow()
6840                    .current_tail_control()
6841                    .expect("pressure must preserve the exact tail control")
6842            }),
6843            before,
6844        );
6845    }
6846
6847    fn batch(values: &[u64]) -> Vec<AcceptedStructuralMutation> {
6848        values
6849            .iter()
6850            .map(|value| {
6851                AcceptedStructuralMutation::save(
6852                    MutationMode::Insert,
6853                    AcceptedStructuralMutationTarget::ResolveFromAfterImage,
6854                    payload_patch(*value),
6855                )
6856            })
6857            .collect()
6858    }
6859
6860    fn atomic_progress_fixture(
6861        identity_byte: u8,
6862    ) -> (
6863        MutationJobRecord,
6864        MutationJobRecord,
6865        MutationProgressRecordOp,
6866    ) {
6867        let job_id = MutationJobId::try_from_bytes([identity_byte; 32])
6868            .expect("nonzero atomic progress job id should admit");
6869        let before = MutationJobRecord::new(job_id, vec![1, identity_byte], vec![2])
6870            .expect("atomic progress predecessor should admit");
6871        let request = MutationJobAdvanceRequest::new(
6872            job_id,
6873            0,
6874            MutationJobIdempotencyKey::new(format!("atomic-{identity_byte}"))
6875                .expect("atomic progress replay key should admit"),
6876        );
6877        let (after, _) = before
6878            .apply_transition(
6879                &request,
6880                MutationJobTransition::new(
6881                    MutationJobStatus::Active,
6882                    MutationJobPhase::Forward,
6883                    vec![3],
6884                    1,
6885                    1,
6886                    0,
6887                ),
6888            )
6889            .expect("atomic progress successor should admit");
6890        let operation = MutationProgressRecordOp::replace(&before, &after)
6891            .expect("atomic progress replacement should admit");
6892        (before, after, operation)
6893    }
6894
6895    fn assert_identity_boundary(error: &InternalError) {
6896        assert_eq!(error.class(), ErrorClass::Unsupported);
6897        assert_eq!(error.origin(), ErrorOrigin::Identity);
6898    }
6899
6900    #[test]
6901    fn generated_candidate_collision_is_identity_corruption_before_generic_uniqueness() {
6902        let generated = insert_key_exists_after_generation(true);
6903        assert_eq!(generated.class(), ErrorClass::Corruption);
6904        assert_eq!(generated.origin(), ErrorOrigin::Identity);
6905
6906        let ordinary = insert_key_exists_after_generation(false);
6907        assert_ne!(ordinary.origin(), ErrorOrigin::Identity);
6908    }
6909
6910    #[cfg(target_pointer_width = "64")]
6911    #[test]
6912    fn pre_key_candidate_count_rejects_values_beyond_the_persisted_u32_bound() {
6913        let error = checked_pre_key_candidate_count(
6914            usize::try_from(u64::from(u32::MAX) + 1).expect("64-bit usize should hold u32 + 1"),
6915        )
6916        .expect_err("candidate counts beyond u32 must reject");
6917        assert_identity_boundary(&error);
6918    }
6919
6920    #[test]
6921    #[expect(
6922        clippy::too_many_lines,
6923        reason = "one holding lifecycle proves split, merge, transfer, late-failure neutrality, result order, and Identity state"
6924    )]
6925    fn mixed_structural_batch_preserves_holding_conservation_and_failure_atomicity() {
6926        let session = initialize();
6927        let seeded = session
6928            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, vec![dynamic_payload_patch(100)])
6929            .expect("seed rows should commit");
6930        assert_eq!(seeded.affected_rows, 1);
6931
6932        let split = session
6933            .execute_trusted_dynamic_mutation_batch(vec![
6934                DynamicMutation::Update {
6935                    entity: ENTITY_NAME.to_string(),
6936                    key: InputValue::nat64(1),
6937                    patch: dynamic_payload_patch(60),
6938                },
6939                DynamicMutation::Insert {
6940                    entity: ENTITY_NAME.to_string(),
6941                    patch: dynamic_payload_patch(40),
6942                },
6943            ])
6944            .expect("one holding should split atomically");
6945        assert_eq!(
6946            split.iter().map(|result| result.affected_rows).sum::<u32>(),
6947            2,
6948        );
6949        assert_eq!(
6950            batch_rows(&split),
6951            vec![expected_dynamic_row(1, 60), expected_dynamic_row(2, 40),],
6952            "split after-images must retain input order and exact quantity",
6953        );
6954
6955        let rejected_split = session
6956            .execute_trusted_dynamic_mutation_batch(vec![
6957                DynamicMutation::Update {
6958                    entity: ENTITY_NAME.to_string(),
6959                    key: InputValue::nat64(1),
6960                    patch: dynamic_payload_patch(50),
6961                },
6962                DynamicMutation::Insert {
6963                    entity: ENTITY_NAME.to_string(),
6964                    patch: DynamicStructuralPatch::new(Vec::new()),
6965                },
6966            ])
6967            .expect_err("an invalid split output must reject the staged source update");
6968        assert_eq!(rejected_split.class(), ErrorClass::Unsupported);
6969        assert_eq!(rejected_split.origin(), ErrorOrigin::Executor);
6970        assert_eq!(
6971            rejected_split.diagnostic_facts(),
6972            vec![
6973                (
6974                    icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
6975                    ENTITY_TAG.value(),
6976                ),
6977                (icydb_diagnostic_code::DiagnosticFactTag::FieldId, 2),
6978                (
6979                    icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
6980                    icydb_diagnostic_code::DiagnosticMutationOperation::Insert.raw(),
6981                ),
6982                (icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 1,),
6983            ],
6984        );
6985        assert_dynamic_payload(&session, 1, 60);
6986        assert_dynamic_payload(&session, 2, 40);
6987
6988        let transfer = session
6989            .execute_trusted_dynamic_mutation_batch(vec![
6990                DynamicMutation::Update {
6991                    entity: ENTITY_NAME.to_string(),
6992                    key: InputValue::nat64(1),
6993                    patch: dynamic_payload_patch(70),
6994                },
6995                DynamicMutation::Update {
6996                    entity: ENTITY_NAME.to_string(),
6997                    key: InputValue::nat64(2),
6998                    patch: dynamic_payload_patch(30),
6999                },
7000            ])
7001            .expect("distinct transfer patches should share one atomic batch");
7002        assert_eq!(
7003            batch_rows(&transfer),
7004            vec![expected_dynamic_row(1, 70), expected_dynamic_row(2, 30),],
7005            "the transfer must preserve the exact total quantity",
7006        );
7007
7008        let merge = session
7009            .execute_trusted_dynamic_mutation_batch(vec![
7010                DynamicMutation::Delete {
7011                    entity: ENTITY_NAME.to_string(),
7012                    key: InputValue::nat64(2),
7013                },
7014                DynamicMutation::Update {
7015                    entity: ENTITY_NAME.to_string(),
7016                    key: InputValue::nat64(1),
7017                    patch: dynamic_payload_patch(100),
7018                },
7019            ])
7020            .expect("two holdings should merge atomically");
7021        assert_eq!(
7022            batch_rows(&merge),
7023            vec![expected_dynamic_row(2, 30), expected_dynamic_row(1, 100),],
7024            "delete before-images and update after-images must retain input order",
7025        );
7026
7027        let resplit = session
7028            .execute_trusted_dynamic_mutation_batch(vec![
7029                DynamicMutation::Update {
7030                    entity: ENTITY_NAME.to_string(),
7031                    key: InputValue::nat64(1),
7032                    patch: dynamic_payload_patch(60),
7033                },
7034                DynamicMutation::Insert {
7035                    entity: ENTITY_NAME.to_string(),
7036                    patch: dynamic_payload_patch(40),
7037                },
7038            ])
7039            .expect("the merged holding should split again");
7040        assert_eq!(
7041            batch_rows(&resplit),
7042            vec![expected_dynamic_row(1, 60), expected_dynamic_row(3, 40),],
7043        );
7044
7045        let rejected_merge = session
7046            .execute_trusted_dynamic_mutation_batch(vec![
7047                DynamicMutation::Delete {
7048                    entity: ENTITY_NAME.to_string(),
7049                    key: InputValue::nat64(3),
7050                },
7051                DynamicMutation::Update {
7052                    entity: ENTITY_NAME.to_string(),
7053                    key: InputValue::nat64(99),
7054                    patch: dynamic_payload_patch(100),
7055                },
7056            ])
7057            .expect_err("a late missing merge target must preserve the earlier staged delete");
7058        assert_eq!(rejected_merge.class(), ErrorClass::NotFound);
7059        assert_dynamic_payload(&session, 1, 60);
7060        assert_dynamic_payload(&session, 3, 40);
7061
7062        SCHEMA_STORE.with(|store| {
7063            let cursor = store
7064                .borrow()
7065                .identity_statement_cursor(
7066                    database_incarnation_id().expect("database incarnation should remain readable"),
7067                    ENTITY_TAG,
7068                    FieldId::new(1),
7069                    &AcceptedFieldKind::Nat64,
7070                )
7071                .expect("mixed Identity state should remain readable");
7072            assert_eq!(cursor.expected_high_water(), 3);
7073            assert!(!cursor.has_allocations());
7074        });
7075    }
7076
7077    #[test]
7078    fn mixed_structural_batch_rejects_duplicate_holding_targets_without_mutation() {
7079        let session = initialize();
7080        session
7081            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, vec![dynamic_payload_patch(100)])
7082            .expect("the holding fixture should initialize");
7083
7084        let duplicate = session
7085            .execute_trusted_dynamic_mutation_batch(vec![
7086                DynamicMutation::Update {
7087                    entity: ENTITY_NAME.to_string(),
7088                    key: InputValue::nat64(1),
7089                    patch: dynamic_payload_patch(60),
7090                },
7091                DynamicMutation::Delete {
7092                    entity: ENTITY_NAME.to_string(),
7093                    key: InputValue::nat64(1),
7094                },
7095            ])
7096            .expect_err("duplicate targets across operation kinds must reject");
7097        assert!(matches!(
7098            duplicate.diagnostic().detail(),
7099            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
7100                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchDuplicateKey,
7101            }),
7102        ));
7103        assert_eq!(
7104            duplicate.diagnostic_facts(),
7105            vec![
7106                (
7107                    icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
7108                    ENTITY_TAG.value(),
7109                ),
7110                (
7111                    icydb_diagnostic_code::DiagnosticFactTag::FirstBatchPosition,
7112                    0,
7113                ),
7114                (
7115                    icydb_diagnostic_code::DiagnosticFactTag::DuplicateBatchPosition,
7116                    1,
7117                ),
7118            ],
7119        );
7120        assert_dynamic_payload(&session, 1, 100);
7121    }
7122
7123    #[test]
7124    fn mixed_structural_batch_rejects_empty_and_over_bound_before_resolution() {
7125        let session = initialize();
7126        let empty = session
7127            .execute_trusted_dynamic_mutation_batch(Vec::new())
7128            .expect_err("an empty public batch must reject");
7129        assert!(matches!(
7130            empty.diagnostic().detail(),
7131            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
7132                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchEmpty,
7133            }),
7134        ));
7135        assert_eq!(
7136            empty.diagnostic_facts(),
7137            vec![(icydb_diagnostic_code::DiagnosticFactTag::ActualCount, 0,)],
7138        );
7139
7140        let requests = (0..=MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS)
7141            .map(|_| DynamicMutation::Delete {
7142                entity: ENTITY_NAME.to_string(),
7143                key: InputValue::nat64(1),
7144            })
7145            .collect();
7146        let over_bound = session
7147            .execute_trusted_dynamic_mutation_batch(requests)
7148            .expect_err("operation cap plus one must reject before row resolution");
7149        assert!(matches!(
7150            over_bound.diagnostic().detail(),
7151            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
7152                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchTooManyItems,
7153            }),
7154        ));
7155        assert_eq!(
7156            over_bound.diagnostic_facts(),
7157            vec![
7158                (
7159                    icydb_diagnostic_code::DiagnosticFactTag::ActualCount,
7160                    (MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS + 1) as u64,
7161                ),
7162                (
7163                    icydb_diagnostic_code::DiagnosticFactTag::Limit,
7164                    MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS as u64,
7165                ),
7166            ],
7167        );
7168    }
7169
7170    #[test]
7171    fn mixed_structural_batch_staged_byte_bound_uses_checked_exact_boundary() {
7172        assert_eq!(
7173            structural_mutation_staged_charge([11, 13, 17])
7174                .expect("the writer-owned formula should sum all three row-image components"),
7175            41,
7176        );
7177        let mut exact = 0;
7178        add_structural_mutation_staged_bytes(
7179            &mut exact,
7180            [MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES],
7181        )
7182        .expect("the exact staged-byte boundary should admit");
7183        assert_eq!(exact, MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES);
7184
7185        let error = add_structural_mutation_staged_bytes(&mut exact, [1])
7186            .expect_err("one byte above the staged-byte boundary must reject");
7187        assert!(matches!(
7188            error.diagnostic().detail(),
7189            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
7190                boundary:
7191                    icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchStagedBytesExceeded,
7192            }),
7193        ));
7194        assert_eq!(
7195            error.diagnostic_facts(),
7196            vec![
7197                (
7198                    icydb_diagnostic_code::DiagnosticFactTag::ActualLength,
7199                    (MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES + 1) as u64,
7200                ),
7201                (
7202                    icydb_diagnostic_code::DiagnosticFactTag::Limit,
7203                    MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES as u64,
7204                ),
7205            ],
7206        );
7207
7208        let mut prefix = 0;
7209        assert_eq!(
7210            admit_structural_mutation_staged_charge(
7211                &mut prefix,
7212                [MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES],
7213                AcceptedStructuralMutationPacking::BoundedPrefix,
7214            )
7215            .expect("the exact prefix boundary should calculate"),
7216            AcceptedStructuralMutationStagedAdmission::Admitted,
7217        );
7218        assert_eq!(prefix, MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES);
7219        assert_eq!(
7220            admit_structural_mutation_staged_charge(
7221                &mut prefix,
7222                [1],
7223                AcceptedStructuralMutationPacking::BoundedPrefix,
7224            )
7225            .expect("the next prefix candidate should calculate"),
7226            AcceptedStructuralMutationStagedAdmission::PageFull,
7227        );
7228        assert_eq!(prefix, MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES);
7229
7230        let mut empty_prefix = 0;
7231        assert_eq!(
7232            admit_structural_mutation_staged_charge(
7233                &mut empty_prefix,
7234                [MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES + 1],
7235                AcceptedStructuralMutationPacking::BoundedPrefix,
7236            )
7237            .expect("one oversized candidate should classify without mutating the prefix"),
7238            AcceptedStructuralMutationStagedAdmission::CandidateExceedsPolicy,
7239        );
7240        assert_eq!(empty_prefix, 0);
7241
7242        validate_structural_mutation_result_bytes(MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES)
7243            .expect("the exact result-byte boundary should admit");
7244        let error = validate_structural_mutation_result_bytes(
7245            MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES + 1,
7246        )
7247        .expect_err("one byte above the result-byte boundary must reject");
7248        assert!(matches!(
7249            error.diagnostic().detail(),
7250            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
7251                boundary:
7252                    icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchResultBytesExceeded,
7253            }),
7254        ));
7255        assert_eq!(
7256            error.diagnostic_facts(),
7257            vec![
7258                (
7259                    icydb_diagnostic_code::DiagnosticFactTag::ActualLength,
7260                    (MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES + 1) as u64,
7261                ),
7262                (
7263                    icydb_diagnostic_code::DiagnosticFactTag::Limit,
7264                    MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES as u64,
7265                ),
7266            ],
7267        );
7268    }
7269
7270    #[expect(
7271        clippy::too_many_lines,
7272        reason = "one lifecycle proves shared materialization and every maintained frontend against the same zero-state owner"
7273    )]
7274    #[test]
7275    fn identity_insert_frontends_share_one_committed_range_without_rejected_consumption() {
7276        let session = initialize();
7277        let catalog = session
7278            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
7279            .expect("identity catalog should resolve");
7280        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
7281            .expect("identity row layout should build");
7282        let initial_description = session
7283            .try_describe_entity_by_name(ENTITY_NAME)
7284            .expect("accepted Identity description should resolve");
7285        assert_eq!(
7286            initial_description.entity_tag(),
7287            catalog.identity().entity_tag().value()
7288        );
7289        assert_eq!(
7290            initial_description.accepted_schema_fingerprint_method(),
7291            catalog.fingerprint_method_version()
7292        );
7293        assert_eq!(
7294            initial_description.accepted_schema_fingerprint(),
7295            catalog.fingerprint()
7296        );
7297        let initial_identity = initial_description
7298            .identity()
7299            .expect("accepted Identity policy should be described");
7300        assert_eq!(initial_identity.field(), "id");
7301        assert_eq!(initial_identity.generator(), "Identity::next");
7302        assert_eq!(initial_identity.accepted_kind(), "nat64");
7303        assert_eq!(initial_identity.minimum(), 1);
7304        assert_eq!(initial_identity.maximum(), u128::from(u64::MAX));
7305        assert_eq!(initial_identity.high_water(), 0);
7306        assert_eq!(initial_identity.remaining(), u128::from(u64::MAX));
7307        assert!(!initial_identity.exhausted());
7308
7309        let rejected = session
7310            .execute_accepted_structural_save_batch(
7311                &catalog,
7312                &descriptor,
7313                batch(&[1_000, 2_000]),
7314                Timestamp::from_millis(6),
7315                |_| Err::<(), _>(InternalError::executor_unsupported()),
7316            )
7317            .expect_err("a rejected precommit result must not publish its tentative range");
7318        assert_eq!(rejected.class(), ErrorClass::Unsupported);
7319        assert_eq!(DATA_STORE.with(|store| store.borrow().len()), 0);
7320
7321        let rows = session
7322            .execute_accepted_structural_save_batch(
7323                &catalog,
7324                &descriptor,
7325                batch(&[10, 20, 30]),
7326                Timestamp::from_millis(7),
7327                Ok,
7328            )
7329            .expect("one accepted batch should commit rows and one identity range");
7330        assert_eq!(
7331            rows.into_iter().map(|row| row.values).collect::<Vec<_>>(),
7332            vec![
7333                vec![Value::Nat64(1), Value::Nat64(10)],
7334                vec![Value::Nat64(2), Value::Nat64(20)],
7335                vec![Value::Nat64(3), Value::Nat64(30)],
7336            ],
7337        );
7338
7339        let dynamic = session
7340            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
7341                entity: ENTITY_NAME.to_string(),
7342                patch: DynamicStructuralPatch::new(vec![(
7343                    "payload".to_string(),
7344                    DynamicWriteCell::Value(InputValue::nat64(40)),
7345                )]),
7346            })
7347            .expect("dynamic omission should commit through shared Identity generation");
7348        assert_eq!(dynamic.affected_rows, 1);
7349
7350        for (request, operation) in [
7351            (
7352                DynamicMutation::Insert {
7353                    entity: ENTITY_NAME.to_string(),
7354                    patch: DynamicStructuralPatch::new(vec![
7355                        (
7356                            "id".to_string(),
7357                            DynamicWriteCell::Value(InputValue::nat64(41)),
7358                        ),
7359                        (
7360                            "payload".to_string(),
7361                            DynamicWriteCell::Value(InputValue::nat64(42)),
7362                        ),
7363                    ]),
7364                },
7365                icydb_diagnostic_code::DiagnosticMutationOperation::Insert,
7366            ),
7367            (
7368                DynamicMutation::Update {
7369                    entity: ENTITY_NAME.to_string(),
7370                    key: InputValue::nat64(1),
7371                    patch: DynamicStructuralPatch::new(vec![(
7372                        "id".to_string(),
7373                        DynamicWriteCell::Default,
7374                    )]),
7375                },
7376                icydb_diagnostic_code::DiagnosticMutationOperation::Update,
7377            ),
7378        ] {
7379            let error = session
7380                .execute_trusted_dynamic_mutation(&request)
7381                .expect_err("structural Identity authorship and regeneration must reject");
7382            assert_eq!(error.class(), ErrorClass::Unsupported);
7383            assert_eq!(error.origin(), ErrorOrigin::Executor);
7384            assert_eq!(
7385                error.diagnostic_facts(),
7386                vec![
7387                    (
7388                        icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
7389                        ENTITY_TAG.value(),
7390                    ),
7391                    (icydb_diagnostic_code::DiagnosticFactTag::FieldId, 1),
7392                    (
7393                        icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
7394                        operation.raw(),
7395                    ),
7396                    (icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 0,),
7397                ],
7398            );
7399        }
7400
7401        let binding = session
7402            .issue_typed_entity_binding(&TYPED_DESCRIPTOR)
7403            .expect("typed output should bind the Identity field");
7404        let typed_patch = binding
7405            .bind_write_ordinals(vec![(1, DynamicWriteCell::Value(InputValue::nat64(50)))])
7406            .expect("typed payload should lower");
7407        let typed = session
7408            .execute_trusted_typed_mutation(
7409                &binding,
7410                &DynamicTypedMutation::Insert { patch: typed_patch },
7411            )
7412            .expect("typed omission should commit through shared Identity generation");
7413        assert_eq!(
7414            typed
7415                .expect("typed insert should return one mutation result")
7416                .affected_rows,
7417            1,
7418        );
7419        let explicit_typed_patch = binding
7420            .bind_write_ordinals(vec![
7421                (0, DynamicWriteCell::Value(InputValue::nat64(51))),
7422                (1, DynamicWriteCell::Value(InputValue::nat64(52))),
7423            ])
7424            .expect("the low-level binding should retain exact authored intent");
7425        let explicit_typed_error = session
7426            .execute_trusted_typed_mutation(
7427                &binding,
7428                &DynamicTypedMutation::Insert {
7429                    patch: explicit_typed_patch,
7430                },
7431            )
7432            .expect_err("typed Identity authorship must reject before allocation");
7433        assert_eq!(explicit_typed_error.class(), ErrorClass::Unsupported);
7434        assert_eq!(explicit_typed_error.origin(), ErrorOrigin::Executor);
7435        assert_eq!(
7436            explicit_typed_error.diagnostic_facts(),
7437            vec![
7438                (
7439                    icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
7440                    ENTITY_TAG.value(),
7441                ),
7442                (icydb_diagnostic_code::DiagnosticFactTag::FieldId, 1),
7443                (
7444                    icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
7445                    icydb_diagnostic_code::DiagnosticMutationOperation::Insert.raw(),
7446                ),
7447                (icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 0,),
7448            ],
7449        );
7450
7451        let replace_error = session
7452            .execute_trusted_dynamic_mutation(&DynamicMutation::Replace {
7453                entity: ENTITY_NAME.to_string(),
7454                key: InputValue::nat64(99),
7455                patch: DynamicStructuralPatch::new(vec![(
7456                    "payload".to_string(),
7457                    DynamicWriteCell::Value(InputValue::nat64(60)),
7458                )]),
7459            })
7460            .expect_err("save-as-insert with a chosen Identity must reject");
7461        assert_eq!(replace_error.class(), ErrorClass::Unsupported);
7462        assert_eq!(replace_error.origin(), ErrorOrigin::Executor);
7463
7464        #[cfg(feature = "sql")]
7465        {
7466            for sql in [
7467                "INSERT INTO IdentityRow (payload) VALUES (70) RETURNING id, payload",
7468                "INSERT INTO IdentityRow (id, payload) VALUES (DEFAULT, 80) RETURNING id",
7469            ] {
7470                let _result = session
7471                    .execute_trusted_sql_mutation(sql)
7472                    .expect("SQL omission and DEFAULT should commit Identity generation");
7473            }
7474
7475            let error = session
7476                .execute_trusted_sql_mutation(
7477                    "INSERT INTO IdentityRow (id, payload) VALUES (42, 90)",
7478                )
7479                .expect_err("an explicit SQL Identity value must reject before allocation");
7480            let diagnostic = error.diagnostic();
7481            assert_eq!(
7482                diagnostic.code(),
7483                icydb_diagnostic_code::DiagnosticCode::QuerySqlWriteBoundary,
7484            );
7485            assert!(matches!(
7486                diagnostic.detail(),
7487                Some(icydb_diagnostic_code::DiagnosticDetail::SqlWriteBoundary {
7488                    boundary: icydb_diagnostic_code::SqlWriteBoundaryCode::ExplicitGeneratedField,
7489                }),
7490            ));
7491        }
7492
7493        let expected_committed = if cfg!(feature = "sql") { 7 } else { 5 };
7494        assert_eq!(
7495            DATA_STORE.with(|store| store.borrow().len()),
7496            expected_committed
7497        );
7498        SCHEMA_STORE.with(|store| {
7499            let cursor = store
7500                .borrow()
7501                .identity_statement_cursor(
7502                    database_incarnation_id().expect("database incarnation should remain readable"),
7503                    ENTITY_TAG,
7504                    FieldId::new(1),
7505                    &AcceptedFieldKind::Nat64,
7506                )
7507                .expect("committed writes must leave active state readable");
7508            assert_eq!(cursor.expected_high_water(), u128::from(expected_committed),);
7509            assert!(!cursor.has_allocations());
7510        });
7511        let committed_description = session
7512            .try_describe_entity_by_name(ENTITY_NAME)
7513            .expect("committed Identity description should resolve");
7514        let committed_identity = committed_description
7515            .identity()
7516            .expect("accepted Identity policy should remain described");
7517        assert_eq!(
7518            committed_identity.high_water(),
7519            u128::from(expected_committed),
7520        );
7521        assert_eq!(
7522            committed_identity.remaining(),
7523            u128::from(u64::MAX - expected_committed),
7524        );
7525        assert!(!committed_identity.exhausted());
7526    }
7527
7528    #[test]
7529    #[expect(
7530        clippy::too_many_lines,
7531        reason = "one ordered scenario proves target/progress atomicity, every interruption wake-up, state-only admission, and successful no-op wake-up behavior"
7532    )]
7533    fn mutation_progress_and_target_rows_recover_as_one_marker_transition() {
7534        let session = initialize_journaled();
7535        let initial_entity_revision = JOURNALED_TAIL_STORE
7536            .with(|tail| tail.borrow().entity_mutation_revision(ENTITY_TAG))
7537            .expect("direct initial schema publication must install entity revision authority");
7538        assert_eq!(initial_entity_revision, 1);
7539        install_startup_recovery_wakeup(record_startup_wakeup);
7540        let catalog = session
7541            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
7542            .expect("journaled atomic-progress catalog should resolve");
7543        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
7544            .expect("journaled atomic-progress row layout should build");
7545
7546        for (ordinal, interruption) in [
7547            MutationCommitInterruption::MarkerPersisted,
7548            MutationCommitInterruption::JournalPublished,
7549            MutationCommitInterruption::RowsPublished,
7550            MutationCommitInterruption::ProgressReplaced,
7551        ]
7552        .into_iter()
7553        .enumerate()
7554        {
7555            let identity_byte = 31 + u8::try_from(ordinal).expect("small ordinal should fit");
7556            let (before, after, operation) = atomic_progress_fixture(identity_byte);
7557            with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
7558                match store.insert_mutation(&before)? {
7559                    InsertMutationJobResult::Inserted => Ok(()),
7560                    InsertMutationJobResult::Occupied(_) => {
7561                        Err(crate::db::MutationJobError::IdentityConflict)
7562                    }
7563                }
7564            })
7565            .expect("atomic predecessor should insert once");
7566
7567            let wakeups_before = STARTUP_WAKEUPS.with(Cell::get);
7568            interrupt_next_mutation_commit_for_tests(interruption);
7569            let interrupted = session.execute_accepted_structural_update_with_mutation_progress(
7570                &catalog,
7571                &descriptor,
7572                batch(&[700 + u64::try_from(ordinal).expect("small ordinal should fit")]),
7573                Timestamp::from_millis(17),
7574                operation,
7575            );
7576            assert!(
7577                interrupted.is_err(),
7578                "selected atomic boundary should interrupt"
7579            );
7580            assert_eq!(
7581                STARTUP_WAKEUPS.with(Cell::get),
7582                wakeups_before.saturating_add(1),
7583                "a normally returned retained-marker error must register its wake-up",
7584            );
7585
7586            forget_recovered_domain_for_tests(&session.db)
7587                .expect("interruption should reset volatile recovery ownership");
7588            let retained_before =
7589                with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
7590                    store.load_mutation(before.state().job_id)
7591                })
7592                .expect("pre-driver progress should load");
7593            let row_count_before = JOURNALED_DATA_STORE.with(|store| store.borrow().len());
7594            let pending = session
7595                .db
7596                .ensure_recovered_state()
7597                .expect_err("ordinary admission must not drive retained-marker recovery");
7598            assert_eq!(
7599                pending.diagnostic().error_code(),
7600                icydb_diagnostic_code::ErrorCode::RUNTIME_BOUNDARY_DATABASE_STARTUP_RECOVERY_PENDING,
7601            );
7602            assert_eq!(
7603                with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
7604                    store.load_mutation(before.state().job_id)
7605                })
7606                .expect("post-admission progress should load"),
7607                retained_before,
7608            );
7609            assert_eq!(
7610                JOURNALED_DATA_STORE.with(|store| store.borrow().len()),
7611                row_count_before,
7612                "state-only admission must not mutate target rows",
7613            );
7614            assert!(
7615                session
7616                    .db
7617                    .drive_startup_recovery_page()
7618                    .expect("dedicated driver should finish target and progress together"),
7619            );
7620            let retained = with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
7621                store.load_mutation(before.state().job_id)
7622            })
7623            .expect("recovered successor should load");
7624            assert_eq!(retained, after);
7625            assert_eq!(
7626                JOURNALED_DATA_STORE.with(|store| store.borrow().len()),
7627                u64::try_from(ordinal + 1).expect("small row count should fit"),
7628            );
7629            assert_eq!(
7630                JOURNALED_TAIL_STORE
7631                    .with(|tail| tail.borrow().entity_mutation_revision(ENTITY_TAG))
7632                    .expect("recovery must publish the target entity revision"),
7633                initial_entity_revision
7634                    + u64::try_from(ordinal + 1).expect("small revision delta should fit"),
7635                "target rows, entity revision, and progress must recover as one transition",
7636            );
7637        }
7638
7639        let (before, after, operation) = atomic_progress_fixture(39);
7640        with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
7641            match store.insert_mutation(&before)? {
7642                InsertMutationJobResult::Inserted => Ok(()),
7643                InsertMutationJobResult::Occupied(_) => {
7644                    Err(crate::db::MutationJobError::IdentityConflict)
7645                }
7646            }
7647        })
7648        .expect("final predecessor should insert once");
7649        let wakeups_before_success = STARTUP_WAKEUPS.with(Cell::get);
7650        session
7651            .execute_accepted_structural_update_with_mutation_progress(
7652                &catalog,
7653                &descriptor,
7654                batch(&[799]),
7655                Timestamp::from_millis(18),
7656                operation,
7657            )
7658            .expect("uninterrupted atomic transition should clear its marker");
7659        assert_eq!(
7660            STARTUP_WAKEUPS.with(Cell::get),
7661            wakeups_before_success.saturating_add(1),
7662            "a successful retained commit must request online convergence",
7663        );
7664        let retained = with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
7665            store.load_mutation(before.state().job_id)
7666        })
7667        .expect("final successor should load");
7668        assert_eq!(retained, after);
7669        forget_recovered_domain_for_tests(&session.db)
7670            .expect("post-clear recovery ownership should reset");
7671        let pending = session
7672            .db
7673            .ensure_recovered_state()
7674            .expect_err("an upgrade epoch must remain gated until its driver runs");
7675        assert_eq!(
7676            pending.diagnostic().error_code(),
7677            icydb_diagnostic_code::ErrorCode::RUNTIME_BOUNDARY_DATABASE_STARTUP_RECOVERY_PENDING,
7678        );
7679        assert!(
7680            session
7681                .db
7682                .drive_startup_recovery_page()
7683                .expect("post-clear driver recovery should fold the retained batch"),
7684        );
7685        assert_eq!(
7686            JOURNALED_TAIL_STORE
7687                .with(|tail| tail.borrow().entity_mutation_revision(ENTITY_TAG))
7688                .expect("uninterrupted transition must retain its entity revision"),
7689            initial_entity_revision + 5,
7690        );
7691    }
7692
7693    fn assert_mixed_entity_recovered_state(session: &DbSession<JournaledTestCanister>) {
7694        for (entity_name, payload) in [
7695            (ENTITY_NAME, 100_u64),
7696            (SECOND_ENTITY_NAME, 1_100),
7697            (THIRD_ENTITY_NAME, 2_100),
7698        ] {
7699            let result = session
7700                .execute_trusted_live_page(
7701                    &DynamicQuery::new(entity_name)
7702                        .filter(crate::db::FieldRef::new("payload").eq(payload))
7703                        .select(["id", "payload"])
7704                        .order_by(crate::db::asc("id"))
7705                        .limit(64),
7706                    None,
7707                )
7708                .expect("every recovered mixed entity should remain queryable");
7709            assert_eq!(result.rows.len(), 1);
7710        }
7711        let retained_relation = session
7712            .execute_trusted_dynamic_mutation_batch(vec![DynamicMutation::Delete {
7713                entity: ENTITY_NAME.to_string(),
7714                key: InputValue::nat64(1),
7715            }])
7716            .expect_err("the recovered reverse relation must protect its target");
7717        assert!(retained_relation.diagnostic_facts().contains(&(
7718            icydb_diagnostic_code::DiagnosticFactTag::ConstraintKind,
7719            icydb_diagnostic_code::DiagnosticConstraintKind::Relation.raw(),
7720        )));
7721        JOURNALED_SCHEMA_STORE.with(|store| {
7722            let store = store.borrow();
7723            for entity_tag in [ENTITY_TAG, SECOND_ENTITY_TAG, THIRD_ENTITY_TAG] {
7724                let cursor = store
7725                    .identity_statement_cursor(
7726                        database_incarnation_id()
7727                            .expect("database incarnation should remain readable"),
7728                        entity_tag,
7729                        FieldId::new(1),
7730                        &AcceptedFieldKind::Nat64,
7731                    )
7732                    .expect("every mixed Identity owner should remain readable");
7733                assert_eq!(cursor.expected_high_water(), 1);
7734                assert!(!cursor.has_allocations());
7735            }
7736        });
7737        JOURNALED_TAIL_STORE.with(|tail| {
7738            let tail = tail.borrow();
7739            assert_eq!(
7740                tail.entity_mutation_revision(ENTITY_TAG)
7741                    .expect("first entity revision should remain readable"),
7742                2,
7743            );
7744            assert_eq!(
7745                tail.entity_mutation_revision(SECOND_ENTITY_TAG)
7746                    .expect("second entity revision should remain readable"),
7747                2,
7748            );
7749            assert_eq!(
7750                tail.entity_mutation_revision(THIRD_ENTITY_TAG)
7751                    .expect("third entity revision should remain readable"),
7752                2,
7753            );
7754        });
7755    }
7756
7757    fn assert_mixed_entity_recovery(interruption: MutationCommitInterruption) {
7758        let session = initialize_journaled_multi_entity();
7759        interrupt_next_mutation_commit_for_tests(interruption);
7760        let interrupted = session.execute_trusted_dynamic_mutation_batch(vec![
7761            DynamicMutation::Insert {
7762                entity: ENTITY_NAME.to_string(),
7763                patch: dynamic_payload_patch(100),
7764            },
7765            DynamicMutation::Insert {
7766                entity: SECOND_ENTITY_NAME.to_string(),
7767                patch: related_dynamic_payload_patch(1_100, 1),
7768            },
7769            DynamicMutation::Insert {
7770                entity: THIRD_ENTITY_NAME.to_string(),
7771                patch: dynamic_payload_patch(2_100),
7772            },
7773        ]);
7774        let interruption_error =
7775            interrupted.expect_err("the selected marker boundary should interrupt");
7776        assert_eq!(interruption_error.class(), ErrorClass::InvariantViolation);
7777        if interruption == MutationCommitInterruption::MarkerPersisted {
7778            let (marker_bytes, journal_batch_bytes) =
7779                crate::db::commit::retained_commit_marker_measurement_for_tests()
7780                    .expect("the retained marker measurement should remain readable")
7781                    .expect("marker persistence should retain one marker");
7782            assert_eq!(marker_bytes, 770);
7783            assert_eq!(journal_batch_bytes, vec![740]);
7784        }
7785        if interruption != MutationCommitInterruption::MarkerPersisted {
7786            let retained_batch = JOURNALED_TAIL_STORE.with(|tail| {
7787                let tail = tail.borrow();
7788                let watermark = tail
7789                    .fold_watermark()
7790                    .expect("the interrupted fold watermark should decode")
7791                    .highest_folded_journal_sequence();
7792                tail.next_batch_after(watermark)
7793                    .expect("the interrupted journal tail should decode")
7794                    .expect("the interrupted marker should publish one journal batch")
7795            });
7796            let row_paths = retained_batch
7797                .records()
7798                .iter()
7799                .filter_map(|record| match record {
7800                    JournalRecord::RowPut { entity_path, .. }
7801                    | JournalRecord::RowDelete { entity_path, .. } => Some(entity_path.as_str()),
7802                    _ => None,
7803                })
7804                .collect::<Vec<_>>();
7805            assert_eq!(
7806                row_paths,
7807                vec![ENTITY_SOURCE, SECOND_ENTITY_SOURCE, THIRD_ENTITY_SOURCE],
7808            );
7809        }
7810
7811        forget_recovered_domain_for_tests(&session.db)
7812            .expect("the retained mixed marker should reset volatile recovery ownership");
7813        drive_journaled_recovery_to_completion(&session);
7814        assert_mixed_entity_recovered_state(&session);
7815    }
7816
7817    #[test]
7818    fn mixed_entity_recovery_after_marker_persistence() {
7819        assert_mixed_entity_recovery(MutationCommitInterruption::MarkerPersisted);
7820    }
7821
7822    #[test]
7823    fn mixed_entity_recovery_after_journal_publication() {
7824        assert_mixed_entity_recovery(MutationCommitInterruption::JournalPublished);
7825    }
7826
7827    #[test]
7828    fn mixed_entity_recovery_after_row_prefix_publication() {
7829        assert_mixed_entity_recovery(MutationCommitInterruption::RowPrefixPublished);
7830    }
7831
7832    #[test]
7833    fn mixed_entity_recovery_after_all_rows_publish() {
7834        assert_mixed_entity_recovery(MutationCommitInterruption::RowsPublished);
7835    }
7836
7837    #[test]
7838    fn mixed_entity_recovery_after_state_materialization() {
7839        assert_mixed_entity_recovery(MutationCommitInterruption::StateMaterialized);
7840    }
7841
7842    #[test]
7843    fn startup_recovery_initializes_missing_entity_revisions_from_the_store_revision() {
7844        let session = initialize_journaled();
7845        let catalog = session
7846            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
7847            .expect("journaled predecessor catalog should resolve");
7848        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
7849            .expect("journaled predecessor row layout should build");
7850        session
7851            .execute_accepted_structural_save_batch(
7852                &catalog,
7853                &descriptor,
7854                batch(&[901]),
7855                Timestamp::from_millis(21),
7856                Ok,
7857            )
7858            .expect("predecessor row should advance the store-wide revision");
7859        let baseline = JOURNALED_TAIL_STORE.with(|tail| {
7860            let mut tail = tail.borrow_mut();
7861            let baseline = tail
7862                .data_mutation_revision()
7863                .expect("predecessor store-wide revision should load");
7864            tail.clear_entity_mutation_revisions_for_tests();
7865            baseline
7866        });
7867
7868        forget_recovered_domain_for_tests(&session.db)
7869            .expect("upgrade should reset volatile recovery ownership");
7870        drive_journaled_recovery_to_completion(&session);
7871
7872        let recovered = JOURNALED_TAIL_STORE
7873            .with(|tail| tail.borrow().entity_mutation_revision(ENTITY_TAG))
7874            .expect("recovery should publish the current entity authority");
7875        assert_eq!(recovered, baseline);
7876    }
7877
7878    #[test]
7879    fn mutation_progress_neither_side_mismatch_blocks_recovery() {
7880        let session = initialize_journaled();
7881        let catalog = session
7882            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
7883            .expect("journaled corruption catalog should resolve");
7884        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
7885            .expect("journaled corruption row layout should build");
7886        let (before, _after, operation) = atomic_progress_fixture(41);
7887        with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
7888            match store.insert_mutation(&before)? {
7889                InsertMutationJobResult::Inserted => Ok(()),
7890                InsertMutationJobResult::Occupied(_) => {
7891                    Err(crate::db::MutationJobError::IdentityConflict)
7892                }
7893            }
7894        })
7895        .expect("corruption predecessor should insert once");
7896
7897        interrupt_next_mutation_commit_for_tests(MutationCommitInterruption::MarkerPersisted);
7898        assert!(
7899            session
7900                .execute_accepted_structural_update_with_mutation_progress(
7901                    &catalog,
7902                    &descriptor,
7903                    batch(&[811]),
7904                    Timestamp::from_millis(19),
7905                    operation,
7906                )
7907                .is_err(),
7908            "marker interruption should retain recovery authority",
7909        );
7910        let (unexpected, _) = before
7911            .apply_transition(
7912                &MutationJobAdvanceRequest::new(
7913                    before.state().job_id,
7914                    0,
7915                    MutationJobIdempotencyKey::new("unexpected-third-state")
7916                        .expect("unexpected replay key should admit"),
7917                ),
7918                MutationJobTransition::new(
7919                    MutationJobStatus::Active,
7920                    MutationJobPhase::Forward,
7921                    vec![99],
7922                    2,
7923                    0,
7924                    0,
7925                ),
7926            )
7927            .expect("unexpected but valid progress state should admit");
7928        with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
7929            store.replace_mutation(&unexpected)
7930        })
7931        .expect("test should install the neither-side state");
7932
7933        forget_recovered_domain_for_tests(&session.db)
7934            .expect("corrupt recovery ownership should reset");
7935        let error = session
7936            .db
7937            .drive_startup_recovery_page()
7938            .expect_err("neither-side progress must block recovery");
7939        assert_eq!(error.class(), ErrorClass::Corruption);
7940        assert_eq!(error.origin(), ErrorOrigin::Recovery);
7941        assert_eq!(
7942            with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
7943                store.load_mutation(before.state().job_id)
7944            })
7945            .expect("unexpected state should remain inspectable to the test"),
7946            unexpected,
7947        );
7948        assert!(
7949            session.db.drive_startup_recovery_page().is_err(),
7950            "a retained corrupt marker must continue blocking database access",
7951        );
7952    }
7953
7954    #[test]
7955    #[expect(
7956        clippy::too_many_lines,
7957        reason = "one ordered scenario exercises every durable interruption boundary, guarded recovery, derived rebuild, and both integrity tiers"
7958    )]
7959    fn journaled_identity_recovery_quiesces_every_publication_interruption_before_reallocation() {
7960        let session = initialize_journaled();
7961        let catalog = session
7962            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
7963            .expect("journaled identity catalog should resolve");
7964        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
7965            .expect("journaled identity row layout should build");
7966
7967        for (ordinal, interruption) in [
7968            MutationCommitInterruption::MarkerPersisted,
7969            MutationCommitInterruption::JournalPublished,
7970            MutationCommitInterruption::RowsPublished,
7971            MutationCommitInterruption::StateMaterialized,
7972        ]
7973        .into_iter()
7974        .enumerate()
7975        {
7976            interrupt_next_mutation_commit_for_tests(interruption);
7977            let interrupted = session.execute_accepted_structural_save_batch(
7978                &catalog,
7979                &descriptor,
7980                batch(&[u64::try_from(ordinal).expect("ordinal should fit")]),
7981                Timestamp::from_millis(8),
7982                Ok,
7983            );
7984            assert!(
7985                interrupted.is_err(),
7986                "the selected durable boundary should interrupt",
7987            );
7988
7989            let Err(pending) = session.execute_accepted_structural_save_batch(
7990                &catalog,
7991                &descriptor,
7992                batch(&[100 + u64::try_from(ordinal).expect("ordinal should fit")]),
7993                Timestamp::from_millis(9),
7994                Ok,
7995            ) else {
7996                panic!("ordinary mutation must not drive retained-marker recovery");
7997            };
7998            assert_eq!(
7999                pending.diagnostic().error_code(),
8000                icydb_diagnostic_code::ErrorCode::RUNTIME_BOUNDARY_DATABASE_STARTUP_RECOVERY_PENDING,
8001            );
8002            drive_journaled_recovery_to_completion(&session);
8003
8004            let committed = session
8005                .execute_accepted_structural_save_batch(
8006                    &catalog,
8007                    &descriptor,
8008                    batch(&[100 + u64::try_from(ordinal).expect("ordinal should fit")]),
8009                    Timestamp::from_millis(9),
8010                    Ok,
8011                )
8012                .expect("the next mutation must recover before allocating");
8013            let expected_high_water =
8014                u64::try_from((ordinal + 1) * 2).expect("small test high-water should fit");
8015            assert_eq!(
8016                committed
8017                    .into_iter()
8018                    .map(|row| row.values)
8019                    .collect::<Vec<_>>(),
8020                vec![vec![
8021                    Value::Nat64(expected_high_water),
8022                    Value::Nat64(100 + u64::try_from(ordinal).expect("ordinal should fit")),
8023                ]],
8024            );
8025            assert_eq!(
8026                JOURNALED_DATA_STORE.with(|store| store.borrow().len()),
8027                expected_high_water,
8028            );
8029            JOURNALED_SCHEMA_STORE.with(|store| {
8030                let cursor = store
8031                    .borrow()
8032                    .identity_statement_cursor(
8033                        database_incarnation_id()
8034                            .expect("database incarnation should remain readable"),
8035                        ENTITY_TAG,
8036                        FieldId::new(1),
8037                        &AcceptedFieldKind::Nat64,
8038                    )
8039                    .expect("guarded recovery must leave quiescent active state");
8040                assert_eq!(
8041                    cursor.expected_high_water(),
8042                    u128::from(expected_high_water),
8043                );
8044                assert!(!cursor.has_allocations());
8045            });
8046        }
8047
8048        for (ordinal, (interruption, deleted_key)) in [
8049            (MutationCommitInterruption::MarkerPersisted, 2),
8050            (MutationCommitInterruption::JournalPublished, 4),
8051            (MutationCommitInterruption::RowPrefixPublished, 6),
8052            (MutationCommitInterruption::RowsPublished, 8),
8053            (MutationCommitInterruption::StateMaterialized, 7),
8054        ]
8055        .into_iter()
8056        .enumerate()
8057        {
8058            let expected_payload =
8059                501 + u64::try_from(ordinal).expect("small interruption ordinal should fit");
8060            interrupt_next_mutation_commit_for_tests(interruption);
8061            let interrupted = session.execute_trusted_dynamic_mutation_batch(vec![
8062                DynamicMutation::Update {
8063                    entity: ENTITY_NAME.to_string(),
8064                    key: InputValue::nat64(1),
8065                    patch: dynamic_payload_patch(expected_payload),
8066                },
8067                DynamicMutation::Delete {
8068                    entity: ENTITY_NAME.to_string(),
8069                    key: InputValue::nat64(deleted_key),
8070                },
8071            ]);
8072            assert!(
8073                interrupted.is_err(),
8074                "the selected caller-key mixed publication boundary should interrupt",
8075            );
8076            let pending = session
8077                .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
8078                    entity: ENTITY_NAME.to_string(),
8079                    key: InputValue::nat64(1),
8080                    patch: dynamic_payload_patch(expected_payload),
8081                })
8082                .expect_err("ordinary update must not drive retained-marker recovery");
8083            assert_eq!(
8084                pending.diagnostic().error_code(),
8085                icydb_diagnostic_code::ErrorCode::RUNTIME_BOUNDARY_DATABASE_STARTUP_RECOVERY_PENDING,
8086            );
8087            drive_journaled_recovery_to_completion(&session);
8088            let recovered_update = session
8089                .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
8090                    entity: ENTITY_NAME.to_string(),
8091                    key: InputValue::nat64(1),
8092                    patch: dynamic_payload_patch(expected_payload),
8093                })
8094                .expect("guarded reentry should complete the marker-authorized mixed batch");
8095            assert_eq!(
8096                recovered_update.affected_rows, 0,
8097                "the recovered update must already expose its admitted final image",
8098            );
8099            let recovered_delete = session
8100                .execute_trusted_dynamic_mutation(&DynamicMutation::Delete {
8101                    entity: ENTITY_NAME.to_string(),
8102                    key: InputValue::nat64(deleted_key),
8103                })
8104                .expect_err("the recovered delete must already be materialized");
8105            assert_eq!(recovered_delete.class(), ErrorClass::NotFound);
8106            JOURNALED_SCHEMA_STORE.with(|store| {
8107                let cursor = store
8108                    .borrow()
8109                    .identity_statement_cursor(
8110                        database_incarnation_id()
8111                            .expect("database incarnation should remain readable"),
8112                        ENTITY_TAG,
8113                        FieldId::new(1),
8114                        &AcceptedFieldKind::Nat64,
8115                    )
8116                    .expect("caller-key recovery must preserve active Identity state");
8117                assert_eq!(cursor.expected_high_water(), 8);
8118                assert!(!cursor.has_allocations());
8119            });
8120        }
8121
8122        forget_recovered_domain_for_tests(&session.db)
8123            .expect("the final journal tail should remain recoverable");
8124        session
8125            .db
8126            .drive_startup_recovery_page()
8127            .expect("derived rebuild must not allocate another identity");
8128
8129        let data_generation = JOURNALED_DATA_STORE.with(|store| store.borrow().generation());
8130        let index_generation = JOURNALED_INDEX_STORE.with(|store| store.borrow().generation());
8131        let data_len = JOURNALED_DATA_STORE.with(|store| store.borrow().len());
8132        let index_len = JOURNALED_INDEX_STORE.with(|store| store.borrow().len());
8133        forget_recovered_domain_for_tests(&session.db)
8134            .expect("an empty-tail upgrade should reset recovery ownership");
8135        session
8136            .db
8137            .drive_startup_recovery_page()
8138            .expect("an empty-tail upgrade should admit without rebuilding stored rows or indexes");
8139        assert_eq!(
8140            JOURNALED_DATA_STORE.with(|store| store.borrow().generation()),
8141            data_generation
8142                .checked_add(1)
8143                .expect("test generation should advance once"),
8144            "empty-tail recovery must reset the disposable row projection exactly once",
8145        );
8146        assert_eq!(
8147            JOURNALED_INDEX_STORE.with(|store| store.borrow().generation()),
8148            index_generation
8149                .checked_add(1)
8150                .expect("test generation should advance once"),
8151            "empty-tail recovery must reset the disposable index projection exactly once",
8152        );
8153        assert_eq!(
8154            JOURNALED_DATA_STORE.with(|store| store.borrow().len()),
8155            data_len,
8156            "empty-tail recovery must not rebuild or remove authoritative rows",
8157        );
8158        assert_eq!(
8159            JOURNALED_INDEX_STORE.with(|store| store.borrow().len()),
8160            index_len,
8161            "empty-tail recovery must not clear or rebuild canonical secondary indexes",
8162        );
8163
8164        let quick = execute_quick_integrity(
8165            &session.db,
8166            catalog.inspection_plan(),
8167            catalog.runtime_root_identity().database_incarnation(),
8168        )
8169        .expect("quiescent Identity control inventory should be inspectable");
8170        assert_eq!(quick.status(), &QuickIntegrityStatus::CompleteClean);
8171        let row_page = execute_row_integrity_page(
8172            &session.db,
8173            catalog.inspection_plan(),
8174            PhysicalUnitCheckpoint::BeforeFirst,
8175            RowInspectionLimits::standard(),
8176        )
8177        .expect("Identity rows should remain within committed high-water");
8178        assert!(row_page.exhausted());
8179        assert!(row_page.findings().is_empty());
8180
8181        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 3);
8182        assert!(
8183            JOURNALED_INDEX_STORE.with(|store| !store.borrow().is_empty()),
8184            "derived index rebuild should restore witnesses without allocating identities",
8185        );
8186        assert!(!JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
8187        JOURNALED_SCHEMA_STORE.with(|store| {
8188            let cursor = store
8189                .borrow()
8190                .identity_statement_cursor(
8191                    database_incarnation_id().expect("database incarnation should remain readable"),
8192                    ENTITY_TAG,
8193                    FieldId::new(1),
8194                    &AcceptedFieldKind::Nat64,
8195                )
8196                .expect("folded identity state should reopen without allocating");
8197            assert_eq!(cursor.expected_high_water(), 8);
8198            assert!(!cursor.has_allocations());
8199        });
8200    }
8201
8202    #[test]
8203    fn journaled_online_convergence_drains_the_full_backlog_in_complete_batch_callbacks_without_reallocating_ids()
8204     {
8205        const SUBMISSION: &str = "generated/8899aabbccddeeff";
8206        let session = initialize_journaled();
8207        let catalog = session
8208            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
8209            .expect("journaled identity catalog should resolve");
8210        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
8211            .expect("journaled identity row layout should build");
8212
8213        for payload in 0_u64..64 {
8214            session
8215                .execute_accepted_structural_save_batch(
8216                    &catalog,
8217                    &descriptor,
8218                    batch(&[payload]),
8219                    Timestamp::from_millis(8),
8220                    Ok,
8221                )
8222                .unwrap_or_else(|error| {
8223                    panic!("journaled identity fixture row {payload} should commit: {error:?}")
8224                });
8225        }
8226
8227        let before = JOURNALED_TAIL_STORE.with(|tail| {
8228            tail.borrow()
8229                .current_tail_control()
8230                .expect("online backlog control should remain valid")
8231        });
8232        assert_eq!(before.batch_count(), 64);
8233        let next_sequence = crate::db::commit::next_database_commit_sequence()
8234            .expect("database sequence preview should remain readable");
8235        let Err(pressure) = session.execute_accepted_structural_save_batch(
8236            &catalog,
8237            &descriptor,
8238            batch(&[64]),
8239            Timestamp::from_millis(8),
8240            Ok,
8241        ) else {
8242            panic!("the exact cumulative batch ceiling should reject one more batch")
8243        };
8244        assert_exact_batch_backlog_pressure(&pressure, before, next_sequence);
8245
8246        for folded_batches in 1..=64 {
8247            let complete = session
8248                .db
8249                .drive_startup_recovery_page()
8250                .expect("online complete-batch callback should commit");
8251            assert_eq!(complete, folded_batches == 64);
8252        }
8253
8254        assert!(!JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
8255        session
8256            .execute_accepted_structural_save_batch(
8257                &catalog,
8258                &descriptor,
8259                batch(&[64]),
8260                Timestamp::from_millis(8),
8261                Ok,
8262            )
8263            .expect("drain should make the rejected mutation retryable");
8264        assert!(
8265            session
8266                .db
8267                .drive_startup_recovery_page()
8268                .expect("the retry tail should converge"),
8269        );
8270
8271        assert_eq!(
8272            drive_generated_startup_recovery_page(&session, &JOURNALED_STORE_REGISTRY, SUBMISSION,)
8273                .expect("online convergence should commit"),
8274            GeneratedStartupDriverStep::Terminal,
8275            "the quiescent generated driver should stop",
8276        );
8277
8278        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 65);
8279        assert!(!JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
8280        assert_dynamic_payload(&session, 1, 0);
8281        assert_dynamic_payload(&session, 65, 64);
8282        JOURNALED_SCHEMA_STORE.with(|store| {
8283            let cursor = store
8284                .borrow()
8285                .identity_statement_cursor(
8286                    database_incarnation_id().expect("database incarnation should remain readable"),
8287                    ENTITY_TAG,
8288                    FieldId::new(1),
8289                    &AcceptedFieldKind::Nat64,
8290                )
8291                .expect("online convergence must preserve active Identity state");
8292            assert_eq!(cursor.expected_high_water(), 65);
8293            assert!(!cursor.has_allocations());
8294        });
8295    }
8296
8297    #[test]
8298    fn journaled_online_convergence_reconstructs_same_key_batches_from_canonical_predecessors() {
8299        let session = initialize_journaled();
8300        session
8301            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
8302                entity: ENTITY_NAME.to_string(),
8303                patch: dynamic_payload_patch(10),
8304            })
8305            .expect("the initial positioned row should commit");
8306        for payload in [20, 30] {
8307            session
8308                .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
8309                    entity: ENTITY_NAME.to_string(),
8310                    key: InputValue::nat64(1),
8311                    patch: dynamic_payload_patch(payload),
8312                })
8313                .unwrap_or_else(|error| {
8314                    panic!("the positioned same-key update should commit: {error:?}")
8315                });
8316        }
8317
8318        assert_dynamic_payload(&session, 1, 30);
8319        assert_eq!(
8320            JOURNALED_INDEX_STORE.with(|store| store.borrow().len()),
8321            1,
8322            "the newest live index effect should hide every predecessor",
8323        );
8324        for folded_batches in 1..=3 {
8325            let complete = session
8326                .db
8327                .drive_startup_recovery_page()
8328                .expect("the positioned same-key batch should converge");
8329            assert_eq!(complete, folded_batches == 3);
8330        }
8331
8332        assert_dynamic_payload(&session, 1, 30);
8333        assert_eq!(
8334            JOURNALED_INDEX_STORE.with(|store| store.borrow().len()),
8335            1,
8336            "canonical derived state must contain only the newest membership",
8337        );
8338        assert!(!JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
8339    }
8340
8341    #[test]
8342    fn ready_cardinality_combines_durable_base_with_exact_live_delta_and_fold_maintenance() {
8343        let session = initialize_journaled();
8344        session
8345            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
8346                entity: ENTITY_NAME.to_string(),
8347                patch: dynamic_payload_patch(10),
8348            })
8349            .expect("initial cardinality row should commit");
8350        assert!(
8351            session
8352                .db
8353                .drive_startup_recovery_page()
8354                .expect("initial cardinality row should fold"),
8355        );
8356        drive_journaled_cardinality_to_ready(&session);
8357        let handle = session
8358            .db
8359            .store_handle(JOURNALED_STORE_PATH)
8360            .expect("journaled cardinality store should resolve");
8361        let (index_id, prefix_components) = journaled_user_index_prefix();
8362        reset_journaled_cardinality_projections();
8363        assert_eq!(
8364            JOURNALED_DATA_STORE.with(|store| store.borrow().exact_entity_count(ENTITY_TAG)),
8365            None,
8366            "the reopened-style volatile full count must remain unavailable",
8367        );
8368        assert_journaled_cardinality(handle, index_id, prefix_components.as_slice(), 1);
8369
8370        session
8371            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
8372                entity: ENTITY_NAME.to_string(),
8373                patch: dynamic_payload_patch(10),
8374            })
8375            .expect("post-Ready row should commit into the live overlay");
8376        for payload in [20, 10] {
8377            session
8378                .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
8379                    entity: ENTITY_NAME.to_string(),
8380                    key: InputValue::nat64(2),
8381                    patch: dynamic_payload_patch(payload),
8382                })
8383                .expect("same-key post-Ready overlay should commit");
8384        }
8385        assert_journaled_cardinality(handle, index_id, prefix_components.as_slice(), 2);
8386        for folded in 1..=3 {
8387            let complete = session
8388                .db
8389                .drive_startup_recovery_page()
8390                .expect("post-Ready row should fold with exact maintenance");
8391            assert_eq!(complete, folded == 3);
8392            assert_journaled_cardinality(handle, index_id, prefix_components.as_slice(), 2);
8393        }
8394        assert_journaled_cardinality(handle, index_id, prefix_components.as_slice(), 2);
8395        session
8396            .execute_trusted_dynamic_mutation(&DynamicMutation::Delete {
8397                entity: ENTITY_NAME.to_string(),
8398                key: InputValue::nat64(2),
8399            })
8400            .expect("post-Ready delete should commit into the live overlay");
8401        assert_journaled_cardinality(handle, index_id, prefix_components.as_slice(), 1);
8402        assert!(
8403            session
8404                .db
8405                .drive_startup_recovery_page()
8406                .expect("post-Ready delete should fold with exact maintenance"),
8407        );
8408        assert_journaled_cardinality(handle, index_id, prefix_components.as_slice(), 1);
8409        mark_journaled_cardinality_building();
8410        assert_eq!(
8411            handle.exact_entity_count(ENTITY_TAG),
8412            None,
8413            "non-Ready evidence must select the conservative path",
8414        );
8415        #[cfg(feature = "sql")]
8416        {
8417            let data_reads_before = DataStore::current_get_call_count();
8418            let crate::db::SqlStatementResult::Projection { rows, .. } = session
8419                .execute_trusted_sql_query("SELECT COUNT(*) FROM IdentityRow")
8420                .expect("non-Ready entity cardinality should retain SQL fallback")
8421            else {
8422                panic!("fallback count should return one projection row")
8423            };
8424            assert_eq!(rows, vec![vec![OutputValue::nat64(1)]]);
8425            assert_eq!(DataStore::current_get_call_count(), data_reads_before);
8426        }
8427    }
8428
8429    #[test]
8430    fn journaled_cardinality_rejects_volatile_counts_and_unfolded_accepted_root_drift() {
8431        let session = initialize_journaled();
8432        session
8433            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
8434                entity: ENTITY_NAME.to_string(),
8435                patch: dynamic_payload_patch(10),
8436            })
8437            .expect("cardinality fixture row should commit");
8438        assert!(
8439            session
8440                .db
8441                .drive_startup_recovery_page()
8442                .expect("cardinality fixture row should fold"),
8443        );
8444        drive_journaled_cardinality_to_ready(&session);
8445        let handle = session
8446            .db
8447            .store_handle(JOURNALED_STORE_PATH)
8448            .expect("journaled cardinality store should resolve");
8449        let (index_id, prefix_components) = journaled_user_index_prefix();
8450        let data_generation = JOURNALED_DATA_STORE.with(|store| store.borrow().generation());
8451
8452        assert_eq!(
8453            JOURNALED_DATA_STORE.with(|store| store.borrow().exact_entity_count(ENTITY_TAG)),
8454            Some(1),
8455            "the live full-count cache should be populated before accepted-root drift",
8456        );
8457        assert_eq!(
8458            JOURNALED_INDEX_STORE.with(|store| {
8459                store.borrow().exact_prefix_cardinality(
8460                    data_generation,
8461                    IndexKeyKind::User,
8462                    index_id,
8463                    prefix_components.as_slice(),
8464                )
8465            }),
8466            Some(1),
8467            "the live prefix-count cache should be populated before accepted-root drift",
8468        );
8469        assert_eq!(
8470            JOURNALED_INDEX_STORE.with(|store| {
8471                store.borrow().exact_child_prefixes_for_parent_set(
8472                    data_generation,
8473                    IndexKeyKind::User,
8474                    index_id,
8475                    [prefix_components.as_slice()],
8476                    8,
8477                )
8478            }),
8479            Some(Vec::new()),
8480            "the volatile child-prefix cache should demonstrate the bypass fixture",
8481        );
8482        assert_eq!(
8483            handle.exact_user_index_child_prefixes_for_parent_set(
8484                data_generation,
8485                index_id,
8486                [prefix_components.as_slice()],
8487                8,
8488            ),
8489            None,
8490            "journaled child enumeration must use its conservative route instead of volatile authority",
8491        );
8492        assert_journaled_cardinality(handle, index_id, prefix_components.as_slice(), 1);
8493
8494        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
8495            JOURNALED_STORE_PATH,
8496            AcceptedSchemaRevision::new(2),
8497            BTreeMap::from([(ENTITY_TAG, identity_snapshot(JOURNALED_STORE_PATH, false))]),
8498            BTreeMap::from([
8499                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
8500                ((ENTITY_TAG, source_key(PAYLOAD_SOURCE)), FieldId::new(2)),
8501            ]),
8502        );
8503        crate::db::commit::publish_accepted_schema_candidate(
8504            JOURNALED_STORE_PATH,
8505            handle,
8506            AcceptedSchemaRevision::INITIAL,
8507            &candidate,
8508        )
8509        .expect("a successor accepted root should publish into the live overlay");
8510
8511        assert_eq!(
8512            handle.exact_entity_count(ENTITY_TAG),
8513            None,
8514            "an unfolded accepted root must invalidate durable evidence immediately",
8515        );
8516        assert_eq!(
8517            handle.exact_user_index_prefix_count(
8518                data_generation,
8519                IndexKeyKind::User,
8520                index_id,
8521                prefix_components.as_slice(),
8522            ),
8523            None,
8524            "journaled consumers must not fall back to a populated volatile prefix cache",
8525        );
8526    }
8527
8528    #[test]
8529    fn journaled_convergence_uses_final_batch_rows_for_unique_release() {
8530        let session = initialize_journaled_with_unique_payload();
8531        let inserted = session
8532            .execute_trusted_dynamic_insert_batch(
8533                ENTITY_NAME,
8534                vec![dynamic_payload_patch(10), dynamic_payload_patch(20)],
8535            )
8536            .expect("the unique journal fixture should commit");
8537        assert_eq!(
8538            inserted.rows,
8539            vec![expected_dynamic_row(1, 10), expected_dynamic_row(2, 20)],
8540        );
8541        assert!(
8542            session
8543                .db
8544                .drive_startup_recovery_page()
8545                .expect("the unique fixture should become canonical"),
8546        );
8547
8548        let swapped = session
8549            .execute_trusted_dynamic_mutation_batch(vec![
8550                DynamicMutation::Update {
8551                    entity: ENTITY_NAME.to_string(),
8552                    key: InputValue::nat64(1),
8553                    patch: dynamic_payload_patch(20),
8554                },
8555                DynamicMutation::Update {
8556                    entity: ENTITY_NAME.to_string(),
8557                    key: InputValue::nat64(2),
8558                    patch: dynamic_payload_patch(10),
8559                },
8560            ])
8561            .expect("one journal batch should admit a final-row unique swap");
8562        assert_eq!(
8563            batch_rows(&swapped),
8564            vec![expected_dynamic_row(1, 20), expected_dynamic_row(2, 10)],
8565        );
8566        assert!(
8567            session
8568                .db
8569                .drive_startup_recovery_page()
8570                .expect("the unique swap should converge in one complete batch"),
8571        );
8572
8573        let released = session
8574            .execute_trusted_dynamic_mutation_batch(vec![
8575                DynamicMutation::Delete {
8576                    entity: ENTITY_NAME.to_string(),
8577                    key: InputValue::nat64(1),
8578                },
8579                DynamicMutation::Insert {
8580                    entity: ENTITY_NAME.to_string(),
8581                    patch: dynamic_payload_patch(20),
8582                },
8583            ])
8584            .expect("a journaled delete should release its unique value to the final insert");
8585        assert_eq!(
8586            batch_rows(&released),
8587            vec![expected_dynamic_row(1, 20), expected_dynamic_row(3, 20)],
8588        );
8589        assert!(
8590            session
8591                .db
8592                .drive_startup_recovery_page()
8593                .expect("the delete and unique reuse should converge together"),
8594        );
8595
8596        assert_dynamic_payload(&session, 2, 10);
8597        assert_dynamic_payload(&session, 3, 20);
8598        assert_eq!(JOURNALED_INDEX_STORE.with(|store| store.borrow().len()), 2);
8599        assert!(
8600            session
8601                .execute_trusted_dynamic_insert_batch(ENTITY_NAME, vec![dynamic_payload_patch(20)],)
8602                .is_err(),
8603            "the converged unique index must remain authoritative",
8604        );
8605    }
8606
8607    #[test]
8608    fn journaled_startup_recovery_completes_one_large_batch_atomically() {
8609        let session = initialize_journaled();
8610        let catalog = session
8611            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
8612            .expect("journaled identity catalog should resolve");
8613        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
8614            .expect("journaled identity row layout should build");
8615        let payloads = (0_u64..129).collect::<Vec<_>>();
8616        session
8617            .execute_accepted_structural_save_batch(
8618                &catalog,
8619                &descriptor,
8620                batch(&payloads),
8621                Timestamp::from_millis(9),
8622                Ok,
8623            )
8624            .expect("one large journal batch should commit");
8625
8626        forget_recovered_domain_for_tests(&session.db)
8627            .expect("upgrade should reset recovery ownership");
8628        assert!(
8629            session
8630                .db
8631                .drive_startup_recovery_page()
8632                .expect("the complete batch recovery page should commit"),
8633        );
8634
8635        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 129);
8636        JOURNALED_TAIL_STORE.with(|tail| {
8637            let tail = tail.borrow();
8638            assert!(!tail.has_stored_batch());
8639        });
8640        assert_dynamic_payload(&session, 1, 0);
8641        assert_dynamic_payload(&session, 129, 128);
8642    }
8643
8644    #[test]
8645    fn complete_batch_validation_rejects_a_late_record_before_canonical_writes() {
8646        let session = initialize_journaled();
8647        let catalog = session
8648            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
8649            .expect("journaled identity catalog should resolve");
8650        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
8651            .expect("journaled identity row layout should build");
8652        session
8653            .execute_accepted_structural_save_batch(
8654                &catalog,
8655                &descriptor,
8656                batch(&[7]),
8657                Timestamp::from_millis(9),
8658                Ok,
8659            )
8660            .expect("journal batch predecessor should commit");
8661
8662        JOURNALED_TAIL_STORE.with(|tail| {
8663            let mut tail = tail.borrow_mut();
8664            let original = tail
8665                .next_batch_after(JournalSequence::new(0))
8666                .expect("journal batch should decode")
8667                .expect("journal batch should exist");
8668            let mut records = original.records().to_vec();
8669            records.push(
8670                JournalRecord::schema_put(JOURNALED_STORE_PATH, vec![0xff; 8])
8671                    .expect("bounded semantic corruption should build"),
8672            );
8673            let corrupted = JournalBatch::new_with_database_commit_sequence(
8674                original.batch_id(),
8675                original.commit_marker_id(),
8676                original.journal_sequence(),
8677                original.database_commit_sequence(),
8678                records,
8679            )
8680            .expect("current corrupt batch shape should build");
8681            let encoded = encode_journal_batch(&corrupted)
8682                .expect("current corrupt batch envelope should encode");
8683            tail.clear_batches_through(original.journal_sequence());
8684            tail.insert_raw_batch_for_tests(original.journal_sequence(), encoded)
8685                .expect("corrupt persisted batch should replace the predecessor");
8686        });
8687
8688        forget_recovered_domain_for_tests(&session.db)
8689            .expect("upgrade should reset recovery ownership");
8690        let error = session
8691            .db
8692            .drive_startup_recovery_page()
8693            .expect_err("late semantic corruption must fail before fold apply");
8694        assert_eq!(error.class(), ErrorClass::Corruption);
8695        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 0);
8696        JOURNALED_TAIL_STORE.with(|tail| {
8697            let tail = tail.borrow();
8698            assert_eq!(
8699                tail.fold_watermark()
8700                    .expect("watermark should remain readable")
8701                    .highest_folded_journal_sequence(),
8702                JournalSequence::new(0),
8703            );
8704            assert!(tail.has_stored_batch());
8705        });
8706    }
8707
8708    #[test]
8709    fn prepared_batch_row_evidence_rejects_a_late_malformed_row_before_canonical_writes() {
8710        let session = initialize_journaled();
8711        let catalog = session
8712            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
8713            .expect("journaled identity catalog should resolve");
8714        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
8715            .expect("journaled identity row layout should build");
8716        session
8717            .execute_accepted_structural_save_batch(
8718                &catalog,
8719                &descriptor,
8720                batch(&[7, 8]),
8721                Timestamp::from_millis(9),
8722                Ok,
8723            )
8724            .expect("two-row journal batch should commit");
8725
8726        JOURNALED_TAIL_STORE.with(|tail| {
8727            let mut tail = tail.borrow_mut();
8728            let original = tail
8729                .next_batch_after(JournalSequence::new(0))
8730                .expect("journal batch should decode")
8731                .expect("journal batch should exist");
8732            let mut records = original.records().to_vec();
8733            let mut row_ordinal = 0_u8;
8734            for record in &mut records {
8735                if let JournalRecord::RowPut { row_bytes, .. } = record {
8736                    row_ordinal = row_ordinal.saturating_add(1);
8737                    if row_ordinal == 2 {
8738                        *row_bytes = vec![0xff; 8];
8739                        break;
8740                    }
8741                }
8742            }
8743            assert_eq!(row_ordinal, 2, "the late row record should be present");
8744            let corrupted = JournalBatch::new_with_database_commit_sequence(
8745                original.batch_id(),
8746                original.commit_marker_id(),
8747                original.journal_sequence(),
8748                original.database_commit_sequence(),
8749                records,
8750            )
8751            .expect("current corrupt batch shape should build");
8752            let encoded = encode_journal_batch(&corrupted)
8753                .expect("current corrupt batch envelope should encode");
8754            tail.clear_batches_through(original.journal_sequence());
8755            tail.insert_raw_batch_for_tests(original.journal_sequence(), encoded)
8756                .expect("corrupt persisted batch should replace the predecessor");
8757        });
8758
8759        forget_recovered_domain_for_tests(&session.db)
8760            .expect("upgrade should reset recovery ownership");
8761        let error = session
8762            .db
8763            .drive_startup_recovery_page()
8764            .expect_err("late malformed row must fail during complete batch preparation");
8765        assert_eq!(error.class(), ErrorClass::Corruption);
8766        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 0);
8767        JOURNALED_TAIL_STORE.with(|tail| {
8768            let tail = tail.borrow();
8769            assert_eq!(
8770                tail.fold_watermark()
8771                    .expect("watermark should remain readable")
8772                    .highest_folded_journal_sequence(),
8773                JournalSequence::new(0),
8774            );
8775            assert!(tail.has_stored_batch());
8776        });
8777    }
8778
8779    #[test]
8780    fn typed_mutation_batch_recovers_as_one_marker_atomic_transition() {
8781        let session = initialize_journaled();
8782        let binding = exact_key_binding(&session);
8783        session
8784            .execute_trusted_same_entity_typed_mutation_batch(
8785                &binding,
8786                vec![
8787                    typed_payload_insert(&binding, 10),
8788                    typed_payload_insert(&binding, 20),
8789                ],
8790            )
8791            .expect("typed recovery fixture should commit")
8792            .expect("typed recovery fixture binding should remain current");
8793        interrupt_next_mutation_commit_for_tests(MutationCommitInterruption::RowsPublished);
8794
8795        let interrupted = session.execute_trusted_same_entity_typed_mutation_batch(
8796            &binding,
8797            vec![typed_payload_delete(1), typed_payload_insert(&binding, 30)],
8798        );
8799        assert!(
8800            interrupted.is_err(),
8801            "typed batch should expose the selected durable interruption",
8802        );
8803        let pending = session
8804            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
8805                entity: ENTITY_NAME.to_string(),
8806                patch: dynamic_payload_patch(30),
8807            })
8808            .expect_err("ordinary writes must not bypass retained-marker recovery");
8809        assert_eq!(
8810            pending.diagnostic().error_code(),
8811            icydb_diagnostic_code::ErrorCode::RUNTIME_BOUNDARY_DATABASE_STARTUP_RECOVERY_PENDING,
8812        );
8813
8814        drive_journaled_recovery_to_completion(&session);
8815        let recovered = session
8816            .execute_trusted_live_page(&crate::db::DynamicQuery::new(ENTITY_NAME), None)
8817            .expect("the recovered typed batch should be readable");
8818        assert_eq!(
8819            recovered.rows,
8820            vec![expected_dynamic_row(2, 20), expected_dynamic_row(3, 30)],
8821        );
8822    }
8823
8824    #[test]
8825    #[ignore = "release-closeout native timing probe for one marker-authorized driver recovery"]
8826    fn identity_recovery_closeout_reports_driver_time() {
8827        let session = initialize_journaled();
8828        let catalog = session
8829            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
8830            .expect("journaled identity catalog should resolve");
8831        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
8832            .expect("journaled identity row layout should build");
8833
8834        interrupt_next_mutation_commit_for_tests(MutationCommitInterruption::RowsPublished);
8835        let interrupted = session.execute_accepted_structural_save_batch(
8836            &catalog,
8837            &descriptor,
8838            batch(&[1]),
8839            Timestamp::from_millis(10),
8840            Ok,
8841        );
8842        assert!(
8843            interrupted.is_err(),
8844            "the selected publication boundary should interrupt",
8845        );
8846
8847        let start = Instant::now();
8848        assert!(
8849            session
8850                .db
8851                .drive_startup_recovery_page()
8852                .expect("dedicated driver should recover before allocation"),
8853        );
8854        let committed = session
8855            .execute_accepted_structural_save_batch(
8856                &catalog,
8857                &descriptor,
8858                batch(&[2]),
8859                Timestamp::from_millis(11),
8860                Ok,
8861            )
8862            .expect("post-recovery allocation should commit");
8863        let elapsed = start.elapsed();
8864        assert_eq!(
8865            committed
8866                .into_iter()
8867                .map(|row| row.values)
8868                .collect::<Vec<_>>(),
8869            vec![vec![Value::Nat64(2), Value::Nat64(2)]],
8870        );
8871
8872        println!(
8873            "identity recovery closeout: driver_nanos={}",
8874            elapsed.as_nanos(),
8875        );
8876    }
8877}
8878
8879#[cfg(test)]
8880mod targeted_rule_mutation_tests {
8881    use super::{
8882        DbSession, DynamicMutation, DynamicStructuralPatch, DynamicTypedMutation, DynamicWriteCell,
8883        TypedEntityDescriptor, TypedFieldType,
8884    };
8885    use crate::{
8886        db::{
8887            TypedFieldDescriptor,
8888            data::{DataStore, encode_input_value_for_candidate_field_contract},
8889            index::IndexStore,
8890            registry::{StoreAllocationIdentities, StoreRegistry, StoreRuntimeStorageCapabilities},
8891            schema::{
8892                AcceptedCheckLiteralV1, AcceptedCompositeCatalog, AcceptedFieldDecodeContract,
8893                AcceptedFieldKind, AcceptedNamedTypeIdentity, AcceptedRuleOperation,
8894                AcceptedRuleTarget, AcceptedSchemaRevision, AcceptedSourceBindingCatalog,
8895                ConstraintOrigin, FieldId, FieldStorageDecode, FieldWriteManagement, LeafCodec,
8896                PersistedFieldSnapshot, PersistedNestedLeafSnapshot, PersistedSchemaSnapshot,
8897                ScalarCodec, SchemaFieldSlot, SchemaFieldWritePolicy, SchemaInsertDefault,
8898                SchemaRowLayout, SchemaStore, SchemaVersion,
8899                accepted_schema_candidate_with_catalogs_for_tests,
8900                build_record_newtype_composite_catalog_for_tests,
8901                empty_accepted_enum_catalog_for_tests, enum_catalog::ValueAdmissionBudget,
8902            },
8903        },
8904        error::InternalError,
8905        traits::{CanisterKind, Path},
8906        types::EntityTag,
8907        value::InputValue,
8908    };
8909    use icydb_schema::{
8910        ConstraintSourceKey, EntitySourceKey, FieldSourceKey, ScalarType, TypeSourceKey,
8911    };
8912    use std::{cell::RefCell, collections::BTreeMap};
8913
8914    const STORE_PATH: &str = "session::write::targeted_rule_mutation_tests::Store";
8915    const ENTITY_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity";
8916    const ID_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity::id";
8917    const PROFILE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity::profile";
8918    const UPDATED_AT_SOURCE: &str =
8919        "session::write::targeted_rule_mutation_tests::Entity::updated_at";
8920    const PROFILE_TYPE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Profile";
8921    const DEGREE_TYPE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Degree";
8922    const DEGREE_MEMBER_SOURCE: &str =
8923        "session::write::targeted_rule_mutation_tests::Profile::degree";
8924    const DEGREE_RULE_SOURCE: &str =
8925        "session::write::targeted_rule_mutation_tests::Profile::degree_multiple";
8926    const TYPED_DESCRIPTOR: TypedEntityDescriptor = TypedEntityDescriptor::new(
8927        ENTITY_SOURCE,
8928        &[ID_SOURCE],
8929        &[
8930            TypedFieldDescriptor::new(ID_SOURCE, TypedFieldType::Scalar(ScalarType::Nat64), false),
8931            TypedFieldDescriptor::new(
8932                PROFILE_SOURCE,
8933                TypedFieldType::Named(PROFILE_TYPE_SOURCE),
8934                false,
8935            ),
8936            TypedFieldDescriptor::new(
8937                UPDATED_AT_SOURCE,
8938                TypedFieldType::Scalar(ScalarType::Timestamp),
8939                false,
8940            ),
8941        ],
8942    );
8943
8944    struct TestCanister;
8945
8946    impl Path for TestCanister {
8947        const PATH: &'static str = "session::write::targeted_rule_mutation_tests::Canister";
8948    }
8949
8950    impl CanisterKind for TestCanister {
8951        const COMMIT_MEMORY_ID: u8 = 43;
8952        const COMMIT_STABLE_KEY: &'static str = "icydb.targeted_mutation_tests.commit.v1";
8953        const STARTUP_MEMORY_ID: u8 = 49;
8954        const STARTUP_STABLE_KEY: &'static str = "icydb.targeted_mutation_tests.startup.control.v1";
8955        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 44;
8956        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
8957            "icydb.targeted_mutation_tests.integrity.progress.v1";
8958    }
8959
8960    thread_local! {
8961        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
8962        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
8963        static SCHEMA_STORE: RefCell<SchemaStore> =
8964            const { RefCell::new(SchemaStore::init_heap()) };
8965        static STORE_REGISTRY: StoreRegistry = {
8966            let mut registry = StoreRegistry::new();
8967            registry.register_store(
8968                STORE_PATH,
8969                &DATA_STORE,
8970                &INDEX_STORE,
8971                &SCHEMA_STORE,
8972                StoreAllocationIdentities::absent(),
8973                StoreRuntimeStorageCapabilities::heap(),
8974            ).expect("targeted mutation test store should register");
8975            registry
8976        };
8977    }
8978
8979    fn source<T, E: std::fmt::Debug>(raw: &str, parse: impl FnOnce(String) -> Result<T, E>) -> T {
8980        parse(raw.to_string()).expect("test source identity should admit")
8981    }
8982
8983    fn profile_input(degree: u64) -> InputValue {
8984        InputValue::map(vec![(
8985            InputValue::from("degree"),
8986            InputValue::nat64(degree),
8987        )])
8988    }
8989
8990    fn structural_patch(id: u64, degree: u64) -> DynamicStructuralPatch {
8991        DynamicStructuralPatch::new(vec![
8992            (
8993                "id".to_string(),
8994                DynamicWriteCell::Value(InputValue::nat64(id)),
8995            ),
8996            (
8997                "profile".to_string(),
8998                DynamicWriteCell::Value(profile_input(degree)),
8999            ),
9000        ])
9001    }
9002
9003    fn encoded_value(
9004        enum_catalog: &crate::db::schema::AcceptedEnumCatalog,
9005        composite_catalog: &AcceptedCompositeCatalog,
9006        name: &str,
9007        kind: &AcceptedFieldKind,
9008        storage_decode: FieldStorageDecode,
9009        leaf_codec: LeafCodec,
9010        value: InputValue,
9011    ) -> Vec<u8> {
9012        let field = AcceptedFieldDecodeContract::new(name, kind, false, storage_decode, leaf_codec);
9013        encode_input_value_for_candidate_field_contract(
9014            enum_catalog,
9015            composite_catalog,
9016            field,
9017            value,
9018            &mut ValueAdmissionBudget::standard(),
9019        )
9020        .expect("test accepted value should encode")
9021    }
9022
9023    fn nat64_literal(
9024        enum_catalog: &crate::db::schema::AcceptedEnumCatalog,
9025        composite_catalog: &AcceptedCompositeCatalog,
9026        value: u64,
9027    ) -> AcceptedCheckLiteralV1 {
9028        let kind = AcceptedFieldKind::Nat64;
9029        AcceptedCheckLiteralV1::from_accepted_parts(
9030            kind.clone(),
9031            FieldStorageDecode::ByKind,
9032            LeafCodec::Scalar(ScalarCodec::Nat64),
9033            encoded_value(
9034                enum_catalog,
9035                composite_catalog,
9036                "degree_bound",
9037                &kind,
9038                FieldStorageDecode::ByKind,
9039                LeafCodec::Scalar(ScalarCodec::Nat64),
9040                InputValue::nat64(value),
9041            ),
9042        )
9043    }
9044
9045    fn targeted_constraint_id(error: &InternalError) -> u32 {
9046        let facts = error.diagnostic_facts();
9047        assert!(facts.contains(&(
9048            icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
9049            icydb_diagnostic_code::DiagnosticMutationOperation::Insert.raw(),
9050        )));
9051        assert!(facts.contains(&(icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 0,)));
9052        assert!(facts.contains(&(
9053            icydb_diagnostic_code::DiagnosticFactTag::ConstraintKind,
9054            icydb_diagnostic_code::DiagnosticConstraintKind::TargetedRule.raw(),
9055        )));
9056        assert_eq!(
9057            facts
9058                .iter()
9059                .filter(|(tag, _)| matches!(
9060                    tag,
9061                    icydb_diagnostic_code::DiagnosticFactTag::RootField
9062                        | icydb_diagnostic_code::DiagnosticFactTag::RecordMember
9063                ))
9064                .copied()
9065                .collect::<Vec<_>>(),
9066            vec![
9067                (icydb_diagnostic_code::DiagnosticFactTag::RootField, 2),
9068                (
9069                    icydb_diagnostic_code::DiagnosticFactTag::RecordMember,
9070                    icydb_diagnostic_code::pack_u32_pair(1, 1),
9071                ),
9072            ]
9073        );
9074        let value = facts
9075            .iter()
9076            .find_map(|(tag, value)| {
9077                (*tag == icydb_diagnostic_code::DiagnosticFactTag::ConstraintId).then_some(*value)
9078            })
9079            .expect("targeted mutation should retain its accepted constraint ID");
9080        u32::try_from(value).expect("accepted constraint ID fits u32")
9081    }
9082
9083    #[expect(
9084        clippy::too_many_lines,
9085        reason = "one end-to-end fixture proves every maintained write frontend converges on the same accepted targeted-rule schedule"
9086    )]
9087    #[test]
9088    fn targeted_rules_converge_across_dynamic_typed_sql_default_timestamp_and_batch_writes() {
9089        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
9090        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
9091        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
9092
9093        let entity_tag = EntityTag::new(93);
9094        let enum_catalog = empty_accepted_enum_catalog_for_tests();
9095        let (composite_catalog, profile_type, degree_type, degree_member) =
9096            build_record_newtype_composite_catalog_for_tests(
9097                "tests::TargetedProfile".to_string(),
9098                "degree".to_string(),
9099                "tests::TargetedDegree".to_string(),
9100                AcceptedFieldKind::Nat64,
9101                &enum_catalog,
9102            )
9103            .expect("targeted mutation composites should close");
9104        let profile_kind = AcceptedFieldKind::Composite {
9105            type_id: profile_type,
9106        };
9107        let profile_default = encoded_value(
9108            &enum_catalog,
9109            &composite_catalog,
9110            "profile",
9111            &profile_kind,
9112            FieldStorageDecode::CatalogValue,
9113            LeafCodec::Structural,
9114            profile_input(12),
9115        );
9116        let fields = vec![
9117            PersistedFieldSnapshot::new_initial(
9118                FieldId::new(1),
9119                "id".to_string(),
9120                SchemaFieldSlot::new(0),
9121                AcceptedFieldKind::Nat64,
9122                Vec::new(),
9123                false,
9124                SchemaInsertDefault::None,
9125                FieldStorageDecode::ByKind,
9126                LeafCodec::Scalar(ScalarCodec::Nat64),
9127            ),
9128            PersistedFieldSnapshot::new_initial(
9129                FieldId::new(2),
9130                "profile".to_string(),
9131                SchemaFieldSlot::new(1),
9132                profile_kind,
9133                vec![PersistedNestedLeafSnapshot::new(
9134                    vec!["degree".to_string()],
9135                    AcceptedFieldKind::Composite {
9136                        type_id: degree_type,
9137                    },
9138                    false,
9139                )],
9140                false,
9141                SchemaInsertDefault::SlotPayload(profile_default),
9142                FieldStorageDecode::CatalogValue,
9143                LeafCodec::Structural,
9144            ),
9145            PersistedFieldSnapshot::new_initial_with_write_policy(
9146                FieldId::new(3),
9147                "updated_at".to_string(),
9148                SchemaFieldSlot::new(2),
9149                AcceptedFieldKind::Timestamp,
9150                Vec::new(),
9151                false,
9152                SchemaInsertDefault::None,
9153                SchemaFieldWritePolicy::from_model_policies(
9154                    None,
9155                    Some(FieldWriteManagement::UpdatedAt),
9156                ),
9157                FieldStorageDecode::ByKind,
9158                LeafCodec::Scalar(ScalarCodec::Timestamp),
9159            ),
9160        ];
9161        let mut snapshot = PersistedSchemaSnapshot::new(
9162            SchemaVersion::initial(),
9163            ENTITY_SOURCE.to_string(),
9164            "TargetedMutation".to_string(),
9165            FieldId::new(1),
9166            SchemaRowLayout::initial(
9167                fields
9168                    .iter()
9169                    .map(|field| (field.id(), field.slot()))
9170                    .collect(),
9171            ),
9172            fields,
9173        );
9174        let constraint_catalog = snapshot
9175            .constraint_catalog()
9176            .clone()
9177            .with_added_targeted_rule(
9178                "profile_degree_multiple".to_string(),
9179                ConstraintOrigin::Generated,
9180                AcceptedRuleTarget::new(
9181                    FieldId::new(2),
9182                    AcceptedNamedTypeIdentity::Composite(degree_type),
9183                ),
9184                AcceptedRuleOperation::MultipleOf {
9185                    divisor: nat64_literal(&enum_catalog, &composite_catalog, 5),
9186                },
9187            )
9188            .expect("targeted mutation rule should allocate");
9189        let targeted_rule_id = constraint_catalog
9190            .constraints()
9191            .last()
9192            .expect("targeted mutation rule should persist")
9193            .id();
9194        snapshot = snapshot.with_constraint_catalog(constraint_catalog);
9195
9196        let entity_source = source(ENTITY_SOURCE, EntitySourceKey::try_new);
9197        let id_source = source(ID_SOURCE, FieldSourceKey::try_new);
9198        let profile_source = source(PROFILE_SOURCE, FieldSourceKey::try_new);
9199        let updated_at_source = source(UPDATED_AT_SOURCE, FieldSourceKey::try_new);
9200        let profile_type_source = source(PROFILE_TYPE_SOURCE, TypeSourceKey::try_new);
9201        let degree_type_source = source(DEGREE_TYPE_SOURCE, TypeSourceKey::try_new);
9202        let degree_member_source = source(DEGREE_MEMBER_SOURCE, FieldSourceKey::try_new);
9203        let degree_rule_source = source(DEGREE_RULE_SOURCE, ConstraintSourceKey::try_new);
9204        let source_bindings = AcceptedSourceBindingCatalog::initial_for_tests(
9205            BTreeMap::from([(entity_source, entity_tag)]),
9206            BTreeMap::from([
9207                ((entity_tag, id_source), FieldId::new(1)),
9208                ((entity_tag, profile_source), FieldId::new(2)),
9209                ((entity_tag, updated_at_source), FieldId::new(3)),
9210            ]),
9211            BTreeMap::from([((entity_tag, degree_rule_source), targeted_rule_id)]),
9212            BTreeMap::new(),
9213            BTreeMap::new(),
9214        )
9215        .with_initial_named_types_for_tests(
9216            BTreeMap::from([
9217                (
9218                    profile_type_source,
9219                    AcceptedNamedTypeIdentity::Composite(profile_type),
9220                ),
9221                (
9222                    degree_type_source,
9223                    AcceptedNamedTypeIdentity::Composite(degree_type),
9224                ),
9225            ]),
9226            BTreeMap::new(),
9227            BTreeMap::from([((profile_type, degree_member_source), degree_member)]),
9228        );
9229        let candidate = accepted_schema_candidate_with_catalogs_for_tests(
9230            STORE_PATH,
9231            AcceptedSchemaRevision::INITIAL,
9232            enum_catalog,
9233            composite_catalog,
9234            source_bindings,
9235            BTreeMap::from([(entity_tag, snapshot)]),
9236        );
9237
9238        let session = DbSession::<TestCanister>::new(
9239            &STORE_REGISTRY,
9240            &crate::db::RequestExecutionRoot::__new_runtime_root(),
9241        );
9242        session
9243            .db
9244            .drive_startup_recovery_page()
9245            .expect("targeted mutation test database should initialize");
9246        let store = session
9247            .db
9248            .store_handle(STORE_PATH)
9249            .expect("targeted mutation test store should resolve");
9250        crate::db::commit::publish_accepted_schema_candidate(
9251            STORE_PATH,
9252            store,
9253            AcceptedSchemaRevision::NONE,
9254            &candidate,
9255        )
9256        .expect("targeted mutation candidate should publish");
9257
9258        let dynamic_error = session
9259            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
9260                entity: "TargetedMutation".to_string(),
9261                patch: structural_patch(1, 12),
9262            })
9263            .expect_err("dynamic write must enforce the targeted rule");
9264        assert_eq!(
9265            targeted_constraint_id(&dynamic_error),
9266            targeted_rule_id.get()
9267        );
9268
9269        let binding = session
9270            .issue_typed_entity_binding(&TYPED_DESCRIPTOR)
9271            .expect("targeted typed binding should issue");
9272        let typed_patch = binding
9273            .bind_write_ordinals(vec![
9274                (0, DynamicWriteCell::Value(InputValue::nat64(2))),
9275                (1, DynamicWriteCell::Value(profile_input(12))),
9276            ])
9277            .expect("targeted typed patch should bind");
9278        let typed_error = session
9279            .execute_trusted_typed_mutation(
9280                &binding,
9281                &DynamicTypedMutation::Insert { patch: typed_patch },
9282            )
9283            .expect_err("typed write must enforce the targeted rule");
9284        assert_eq!(targeted_constraint_id(&typed_error), targeted_rule_id.get());
9285
9286        #[cfg(feature = "sql")]
9287        {
9288            let sql_error = session
9289                .execute_trusted_sql_mutation("INSERT INTO TargetedMutation (id) VALUES (3)")
9290                .expect_err("SQL default resolution must enforce the targeted rule");
9291            let crate::db::QueryError::Execute(execute) = sql_error else {
9292                panic!("targeted SQL write should fail at shared execution admission");
9293            };
9294            assert_eq!(
9295                targeted_constraint_id(execute.as_internal()),
9296                targeted_rule_id.get()
9297            );
9298        }
9299
9300        session
9301            .execute_trusted_dynamic_mutation_batch(vec![
9302                DynamicMutation::Insert {
9303                    entity: "TargetedMutation".to_string(),
9304                    patch: structural_patch(4, 5),
9305                },
9306                DynamicMutation::Insert {
9307                    entity: "TargetedMutation".to_string(),
9308                    patch: structural_patch(5, 12),
9309                },
9310            ])
9311            .expect_err("one invalid targeted value must reject the whole batch");
9312        assert_eq!(
9313            DATA_STORE.with(|store| store.borrow().exact_entity_count(entity_tag)),
9314            Some(0),
9315            "no frontend or earlier valid batch row may escape targeted admission",
9316        );
9317
9318        let admitted = session
9319            .execute_trusted_dynamic_mutation_batch(vec![
9320                DynamicMutation::Insert {
9321                    entity: "TargetedMutation".to_string(),
9322                    patch: structural_patch(6, 5),
9323                },
9324                DynamicMutation::Insert {
9325                    entity: "TargetedMutation".to_string(),
9326                    patch: structural_patch(7, 10),
9327                },
9328            ])
9329            .expect("compliant targeted values should share one accepted batch");
9330        let admitted_rows = admitted
9331            .iter()
9332            .flat_map(|result| result.rows.iter())
9333            .collect::<Vec<_>>();
9334        let [first, second] = admitted_rows.as_slice() else {
9335            panic!("the mixed targeted batch should return two rows");
9336        };
9337        let first_timestamp = first
9338            .get(2)
9339            .expect("the first mixed row should contain its managed timestamp");
9340        assert!(matches!(
9341            first_timestamp.as_public(),
9342            crate::value::PublicValue::Timestamp(_)
9343        ));
9344        assert_eq!(
9345            second.get(2),
9346            Some(first_timestamp),
9347            "one accepted mixed batch must materialize one managed timestamp",
9348        );
9349        assert_eq!(
9350            DATA_STORE.with(|store| store.borrow().exact_entity_count(entity_tag)),
9351            Some(2),
9352        );
9353    }
9354}