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