Skip to main content

icydb_core/db/session/
write.rs

1//! Module: db::session::write
2//! Responsibility: session-owned typed write APIs for insert, replace, update,
3//! and structural mutation entrypoints over the shared save pipeline.
4//! Does not own: commit staging, mutation execution, or persistence encoding.
5//! Boundary: keeps public session write semantics above the executor save surface.
6
7use super::AcceptedSchemaCatalogContext;
8use crate::{
9    db::{
10        DbSession, DynamicMutation, DynamicMutationResult, DynamicStructuralPatch,
11        DynamicTypedBindingError, DynamicTypedEntityBinding, DynamicTypedFieldBindingRequest,
12        DynamicTypedFieldType, DynamicTypedMutation, DynamicTypedStructuralPatch, DynamicWriteCell,
13        commit::{CommitRowOp, database_incarnation_id},
14        data::{
15            AcceptedMutationIntentPatch, AcceptedPreKeyInsert, DecodedDataStoreKey, FieldSlot,
16            RawRow, StructuralRowContract, StructuralSlotReader,
17            canonical_row_from_raw_row_with_accepted_decode_contract,
18            resolve_existing_replace_structural_patch_with_accepted_contract,
19            resolve_insert_structural_patch_with_accepted_contract,
20            resolve_update_structural_patch_with_accepted_contract,
21        },
22        executor::{
23            AcceptedMutationConstraintScheduler, commit_structural_row_ops_with_window_for_path,
24            mutation_key_exists_error,
25        },
26        schema::{
27            AcceptedFieldKind, AcceptedIdentityAllocation, AcceptedRowLayoutRuntimeContract,
28            FieldId, FieldInsertGeneration, IdentityStatementCursor, lower_field_type,
29            output_value_from_runtime,
30        },
31        write_context::{AcceptedWriteContext, MutationMode},
32    },
33    error::{InternalError, MutationDiagnosticContext},
34    metrics::sink::{MetricsEvent, SaveMutationKind, record},
35    traits::CanisterKind,
36    types::{CurrentTimestamp, Timestamp},
37    value::{InputValue, Value},
38};
39use icydb_schema::{EntitySourceKey, FieldSourceKey, FieldType, TypeSourceKey};
40
41#[derive(Clone, Debug, Eq, PartialEq)]
42struct AcceptedIdentityInsertField {
43    field_id: FieldId,
44    field_slot: usize,
45    accepted_kind: AcceptedFieldKind,
46}
47
48/// Accepted row identity carried by a structural mutation after frontend
49/// lowering but before the canonical after-image exists.
50pub(in crate::db::session) enum AcceptedStructuralMutationTarget {
51    ResolveFromAfterImage,
52    Expected(Box<DecodedDataStoreKey>),
53}
54
55impl AcceptedStructuralMutationTarget {
56    pub(in crate::db::session) fn expected(key: DecodedDataStoreKey) -> Self {
57        Self::Expected(Box::new(key))
58    }
59}
60
61/// One accepted structural mutation intent ready for shared batch
62/// materialization.
63pub(in crate::db::session) enum AcceptedStructuralMutation {
64    Save {
65        mode: MutationMode,
66        target: AcceptedStructuralMutationTarget,
67        patch: AcceptedMutationIntentPatch,
68    },
69    Delete {
70        key: Box<DecodedDataStoreKey>,
71    },
72}
73
74impl AcceptedStructuralMutation {
75    pub(in crate::db::session) const fn save(
76        mode: MutationMode,
77        target: AcceptedStructuralMutationTarget,
78        patch: AcceptedMutationIntentPatch,
79    ) -> Self {
80        Self::Save {
81            mode,
82            target,
83            patch,
84        }
85    }
86
87    pub(in crate::db::session) fn delete(key: DecodedDataStoreKey) -> Self {
88        Self::Delete { key: Box::new(key) }
89    }
90}
91
92const MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS: usize = 4_096;
93const MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES: usize = 16 * 1024 * 1024;
94const MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES: usize = 1024 * 1024;
95
96fn add_structural_mutation_staged_bytes(
97    total: &mut usize,
98    lengths: impl IntoIterator<Item = usize>,
99) -> Result<(), InternalError> {
100    for length in lengths {
101        *total = total.checked_add(length).ok_or_else(|| {
102            InternalError::mutation_batch_staged_bytes_exceeded(
103                None,
104                MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES,
105            )
106        })?;
107        if *total > MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES {
108            return Err(InternalError::mutation_batch_staged_bytes_exceeded(
109                Some(*total),
110                MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES,
111            ));
112        }
113    }
114    Ok(())
115}
116
117fn validate_structural_mutation_result_bytes(encoded_bytes: usize) -> Result<(), InternalError> {
118    if encoded_bytes > MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES {
119        return Err(InternalError::mutation_batch_result_bytes_exceeded(
120            encoded_bytes,
121            MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES,
122        ));
123    }
124    Ok(())
125}
126
127/// One canonical row produced by structural mutation materialization.
128pub(in crate::db::session) struct AcceptedStructuralMutationRow {
129    values: Vec<Value>,
130    logical_changed: bool,
131}
132
133impl AcceptedStructuralMutationRow {
134    #[cfg(feature = "sql")]
135    pub(in crate::db::session) fn into_values(self) -> Vec<Value> {
136        self.values
137    }
138
139    pub(in crate::db::session) const fn logical_changed(&self) -> bool {
140        self.logical_changed
141    }
142}
143
144const fn dynamic_mutation_mode(request: &DynamicMutation) -> Option<MutationMode> {
145    match request {
146        DynamicMutation::Insert { .. } => Some(MutationMode::Insert),
147        DynamicMutation::Update { .. } => Some(MutationMode::Update),
148        DynamicMutation::Replace { .. } => Some(MutationMode::Replace),
149        DynamicMutation::Delete { .. } => None,
150    }
151}
152
153const fn dynamic_typed_mutation_mode(request: &DynamicTypedMutation) -> MutationMode {
154    match request {
155        DynamicTypedMutation::Insert { .. } => MutationMode::Insert,
156        DynamicTypedMutation::Update { .. } => MutationMode::Update,
157        DynamicTypedMutation::Replace { .. } => MutationMode::Replace,
158    }
159}
160
161const fn diagnostic_mutation_operation(
162    mode: MutationMode,
163) -> icydb_diagnostic_code::DiagnosticMutationOperation {
164    match mode {
165        MutationMode::Insert => icydb_diagnostic_code::DiagnosticMutationOperation::Insert,
166        MutationMode::Replace => icydb_diagnostic_code::DiagnosticMutationOperation::Replace,
167        MutationMode::Update => icydb_diagnostic_code::DiagnosticMutationOperation::Update,
168    }
169}
170
171const fn mutation_diagnostic_context(
172    entity_tag: crate::types::EntityTag,
173    mode: MutationMode,
174    batch_position: u32,
175) -> MutationDiagnosticContext {
176    MutationDiagnosticContext::new(
177        entity_tag.value(),
178        diagnostic_mutation_operation(mode),
179        batch_position,
180    )
181}
182
183const fn dynamic_write_context(operation_timestamp: Timestamp) -> AcceptedWriteContext {
184    AcceptedWriteContext::new(operation_timestamp)
185}
186
187fn insert_key_exists_after_generation(identity_generated: bool) -> InternalError {
188    if identity_generated {
189        InternalError::identity_state_corruption()
190    } else {
191        mutation_key_exists_error()
192    }
193}
194
195fn dynamic_key(
196    entity_tag: crate::types::EntityTag,
197    key: &InputValue,
198) -> Result<DecodedDataStoreKey, InternalError> {
199    let value = key
200        .clone()
201        .try_into_runtime_non_enum()
202        .ok_or_else(InternalError::executor_unsupported)?;
203    DecodedDataStoreKey::try_from_structural_key(entity_tag, &value)
204}
205
206fn lower_dynamic_patch(
207    entity_path: &str,
208    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
209    patch: &DynamicStructuralPatch,
210    mode: MutationMode,
211    mutation_context: MutationDiagnosticContext,
212) -> Result<AcceptedMutationIntentPatch, InternalError> {
213    let mut lowered = AcceptedMutationIntentPatch::new();
214    for (field_name, cell) in patch.fields() {
215        let slot = descriptor
216            .field_slot_index_by_name(field_name)
217            .ok_or_else(|| {
218                InternalError::mutation_structural_field_unknown(entity_path, field_name)
219            })?;
220        let field = descriptor
221            .field_for_slot_index(slot)
222            .ok_or_else(InternalError::executor_invariant)?;
223        if !matches!(cell, DynamicWriteCell::Omitted)
224            && (field.write_policy().insert_generation().is_some()
225                || field.write_policy().write_management().is_some())
226        {
227            return Err(InternalError::mutation_database_owned_field_explicit(
228                mutation_context,
229                field.field_id().get(),
230            ));
231        }
232        let slot = FieldSlot::from_validated_index(slot);
233        lowered = match cell {
234            DynamicWriteCell::Omitted => lowered,
235            DynamicWriteCell::Default => match mode {
236                MutationMode::Insert | MutationMode::Replace => {
237                    lowered.set_explicit_insert_default(slot)
238                }
239                MutationMode::Update => lowered.set_explicit_update_default(slot),
240            },
241            DynamicWriteCell::Null => lowered.set_authored(slot, InputValue::Null),
242            DynamicWriteCell::Value(value) => lowered.set_authored(slot, value.clone()),
243        };
244    }
245    Ok(lowered)
246}
247
248fn lower_dynamic_mutation_intent(
249    entity_tag: crate::types::EntityTag,
250    entity_path: &str,
251    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
252    request: &DynamicMutation,
253    batch_position: u32,
254) -> Result<(AcceptedStructuralMutation, Option<SaveMutationKind>), InternalError> {
255    match request {
256        DynamicMutation::Insert { patch, .. } => {
257            let mode = MutationMode::Insert;
258            Ok((
259                AcceptedStructuralMutation::save(
260                    mode,
261                    AcceptedStructuralMutationTarget::ResolveFromAfterImage,
262                    lower_dynamic_patch(
263                        entity_path,
264                        descriptor,
265                        patch,
266                        mode,
267                        mutation_diagnostic_context(entity_tag, mode, batch_position),
268                    )?,
269                ),
270                Some(SaveMutationKind::Insert),
271            ))
272        }
273        DynamicMutation::Update { key, patch, .. }
274        | DynamicMutation::Replace { key, patch, .. } => {
275            let mode =
276                dynamic_mutation_mode(request).ok_or_else(InternalError::executor_invariant)?;
277            let kind = match mode {
278                MutationMode::Insert => SaveMutationKind::Insert,
279                MutationMode::Replace => SaveMutationKind::Replace,
280                MutationMode::Update => SaveMutationKind::Update,
281            };
282            Ok((
283                AcceptedStructuralMutation::save(
284                    mode,
285                    AcceptedStructuralMutationTarget::expected(dynamic_key(entity_tag, key)?),
286                    lower_dynamic_patch(
287                        entity_path,
288                        descriptor,
289                        patch,
290                        mode,
291                        mutation_diagnostic_context(entity_tag, mode, batch_position),
292                    )?,
293                ),
294                Some(kind),
295            ))
296        }
297        DynamicMutation::Delete { key, .. } => Ok((
298            AcceptedStructuralMutation::delete(dynamic_key(entity_tag, key)?),
299            None,
300        )),
301    }
302}
303
304fn lower_typed_patch(
305    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
306    patch: &DynamicTypedStructuralPatch,
307    mode: MutationMode,
308    mutation_context: MutationDiagnosticContext,
309) -> Result<AcceptedMutationIntentPatch, InternalError> {
310    let mut lowered = AcceptedMutationIntentPatch::new();
311    for (field_id, slot, cell) in patch.fields() {
312        let slot_index = usize::from(*slot);
313        let field = descriptor
314            .field_for_slot_index(slot_index)
315            .ok_or_else(InternalError::store_invariant)?;
316        if field.field_id().get() != *field_id {
317            return Err(InternalError::store_invariant());
318        }
319        if !matches!(cell, DynamicWriteCell::Omitted)
320            && (field.write_policy().insert_generation().is_some()
321                || field.write_policy().write_management().is_some())
322        {
323            return Err(InternalError::mutation_database_owned_field_explicit(
324                mutation_context,
325                field.field_id().get(),
326            ));
327        }
328        let slot = FieldSlot::from_validated_index(slot_index);
329        lowered = match cell {
330            DynamicWriteCell::Omitted => lowered,
331            DynamicWriteCell::Default => match mode {
332                MutationMode::Insert | MutationMode::Replace => {
333                    lowered.set_explicit_insert_default(slot)
334                }
335                MutationMode::Update => lowered.set_explicit_update_default(slot),
336            },
337            DynamicWriteCell::Null => lowered.set_authored(slot, InputValue::Null),
338            DynamicWriteCell::Value(value) => lowered.set_authored(slot, value.clone()),
339        };
340    }
341    Ok(lowered)
342}
343
344fn preserve_dynamic_replacement_identity(
345    key: &DecodedDataStoreKey,
346    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
347    mut patch: AcceptedMutationIntentPatch,
348) -> Result<AcceptedMutationIntentPatch, InternalError> {
349    let primary_key_slots = descriptor.primary_key_slot_indices();
350    let runtime_key = key.primary_key_runtime_value();
351    let components = match runtime_key {
352        Value::List(values) if primary_key_slots.len() > 1 => values,
353        value if primary_key_slots.len() == 1 => vec![value],
354        _ => return Err(InternalError::executor_invariant()),
355    };
356    if components.len() != primary_key_slots.len() {
357        return Err(InternalError::executor_invariant());
358    }
359
360    for (slot, value) in primary_key_slots.iter().copied().zip(components) {
361        let _ = descriptor
362            .field_for_slot_index(slot)
363            .ok_or_else(InternalError::executor_invariant)?;
364        let has_explicit_intent = patch
365            .entries()
366            .iter()
367            .any(|entry| entry.slot().index() == slot);
368        if has_explicit_intent {
369            continue;
370        }
371        let value = InputValue::try_from_runtime_non_enum(&value)
372            .ok_or_else(InternalError::executor_invariant)?;
373        patch =
374            patch.set_preserved_replacement_identity(FieldSlot::from_validated_index(slot), value);
375    }
376
377    Ok(patch)
378}
379
380// Locate the sole accepted Identity owner that is eligible to resolve a
381// keyless insert. Accepted-schema integrity already freezes the exact shape;
382// this runtime check fails closed if a malformed contract reaches execution.
383fn accepted_identity_insert_field(
384    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
385) -> Result<Option<AcceptedIdentityInsertField>, InternalError> {
386    let mut identity = None;
387    for field in descriptor.fields() {
388        if field.write_policy().insert_generation() != Some(FieldInsertGeneration::Identity) {
389            continue;
390        }
391        let field_slot = usize::from(field.slot().get());
392        if identity
393            .replace(AcceptedIdentityInsertField {
394                field_id: field.field_id(),
395                field_slot,
396                accepted_kind: field.kind().clone(),
397            })
398            .is_some()
399            || descriptor.primary_key_slot_indices() != [field_slot]
400        {
401            return Err(InternalError::identity_corruption());
402        }
403    }
404    Ok(identity)
405}
406
407fn checked_pre_key_candidate_count(count: usize) -> Result<u32, InternalError> {
408    u32::try_from(count).map_err(|_| InternalError::identity_candidate_count_exhausted())
409}
410
411fn validate_identity_materialization(
412    entity_tag: crate::types::EntityTag,
413    identity_field: &AcceptedIdentityInsertField,
414    candidate: &AcceptedPreKeyInsert,
415    allocation: &AcceptedIdentityAllocation,
416    data_key: &DecodedDataStoreKey,
417    reader: &StructuralSlotReader<'_>,
418) -> Result<(), InternalError> {
419    let owner = allocation.owner();
420    let slot_value = reader.required_cached_value(identity_field.field_slot)?;
421    if candidate.entity_tag() != entity_tag
422        || candidate.input_ordinal() != allocation.input_ordinal()
423        || owner.entity_tag() != entity_tag
424        || owner.field_id() != identity_field.field_id
425        || allocation.field_slot() != identity_field.field_slot
426        || slot_value != allocation.value()
427        || data_key.primary_key_runtime_value() != *allocation.value()
428    {
429        return Err(InternalError::identity_corruption());
430    }
431    Ok(())
432}
433
434fn data_key_from_row(
435    entity_tag: crate::types::EntityTag,
436    contract: &StructuralRowContract,
437    row: &RawRow,
438) -> Result<DecodedDataStoreKey, InternalError> {
439    let reader =
440        StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(row, contract)?;
441    let values = contract
442        .primary_key_slot_indices()
443        .iter()
444        .map(|slot| reader.required_cached_value(*slot).cloned())
445        .collect::<Result<Vec<_>, _>>()?;
446    let value = match values.as_slice() {
447        [value] => value.clone(),
448        _ => Value::List(values),
449    };
450    DecodedDataStoreKey::try_from_structural_key(entity_tag, &value)
451}
452
453#[cfg(feature = "sql")]
454pub(in crate::db::session) fn structural_data_key_from_runtime_values(
455    entity_tag: crate::types::EntityTag,
456    values: Vec<Value>,
457) -> Result<DecodedDataStoreKey, InternalError> {
458    let value = match values.as_slice() {
459        [value] => value.clone(),
460        _ => Value::List(values),
461    };
462    DecodedDataStoreKey::try_from_structural_key(entity_tag, &value)
463}
464
465fn validated_existing_row(
466    store: crate::db::registry::StoreHandle,
467    data_key: &DecodedDataStoreKey,
468    contract: &StructuralRowContract,
469) -> Result<Option<RawRow>, InternalError> {
470    let raw_key = data_key.to_raw()?;
471    let row = store.with_data(|data| data.get(&raw_key));
472    if let Some(row) = row.as_ref() {
473        let reader =
474            StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(row, contract)?;
475        reader.validate_primary_key(data_key)?;
476    }
477    Ok(row)
478}
479
480fn prepare_dynamic_mutation_result(
481    catalog: &AcceptedSchemaCatalogContext,
482    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
483    rows: Vec<AcceptedStructuralMutationRow>,
484    enforce_mixed_batch_result_bound: bool,
485) -> Result<DynamicMutationResult, InternalError> {
486    let affected_rows = rows.iter().try_fold(0_u32, |total, row| {
487        total
488            .checked_add(u32::from(row.logical_changed()))
489            .ok_or_else(InternalError::executor_invariant)
490    })?;
491    let columns = descriptor
492        .fields()
493        .iter()
494        .map(|field| field.name().to_string())
495        .collect();
496    let rows = rows
497        .into_iter()
498        .map(|row| {
499            row.values
500                .iter()
501                .map(|value| {
502                    output_value_from_runtime(catalog.enum_catalog(), value)
503                        .map_err(|_| InternalError::store_invariant())
504                })
505                .collect::<Result<Vec<_>, _>>()
506        })
507        .collect::<Result<Vec<_>, _>>()?;
508    let result = DynamicMutationResult {
509        entity: catalog.snapshot().entity_name().to_string(),
510        columns,
511        rows,
512        affected_rows,
513    };
514    if enforce_mixed_batch_result_bound {
515        let encoded =
516            candid::encode_one(&result).map_err(|_| InternalError::executor_invariant())?;
517        validate_structural_mutation_result_bytes(encoded.len())?;
518    }
519    Ok(result)
520}
521
522fn dynamic_typed_field_type(
523    field_type: DynamicTypedFieldType,
524) -> Result<FieldType, DynamicTypedBindingError> {
525    match field_type {
526        DynamicTypedFieldType::Scalar(scalar) => Ok(FieldType::Scalar(scalar)),
527        DynamicTypedFieldType::List(item) => {
528            Ok(FieldType::List(Box::new(dynamic_typed_field_type(*item)?)))
529        }
530        DynamicTypedFieldType::Named(source_key) => TypeSourceKey::try_new(source_key)
531            .map(FieldType::Named)
532            .map_err(|_| DynamicTypedBindingError::FieldUnavailable),
533    }
534}
535
536fn typed_adapter_field_kind_matches(
537    accepted: &AcceptedFieldKind,
538    expected: &AcceptedFieldKind,
539) -> bool {
540    if accepted == expected {
541        return true;
542    }
543    match (accepted, expected) {
544        (AcceptedFieldKind::Relation { key_kind, .. }, expected) => {
545            typed_adapter_field_kind_matches(key_kind, expected)
546        }
547        (AcceptedFieldKind::List(accepted), AcceptedFieldKind::List(expected)) => {
548            typed_adapter_field_kind_matches(accepted, expected)
549        }
550        _ => false,
551    }
552}
553
554impl<C: CanisterKind> DbSession<C> {
555    /// Issue one opaque accepted binding for immutable generated source keys.
556    pub fn issue_typed_entity_binding(
557        &self,
558        entity_source_key: &str,
559        field_requests: &[DynamicTypedFieldBindingRequest],
560    ) -> Result<DynamicTypedEntityBinding, DynamicTypedBindingError> {
561        let entity_source = EntitySourceKey::try_new(entity_source_key)
562            .map_err(|_| DynamicTypedBindingError::FieldUnavailable)?;
563        let field_requests = field_requests
564            .iter()
565            .map(|request| {
566                Ok((
567                    FieldSourceKey::try_new(request.source_key.clone())
568                        .map_err(|_| DynamicTypedBindingError::FieldUnavailable)?,
569                    dynamic_typed_field_type(request.field_type.clone())?,
570                    request.nullable,
571                ))
572            })
573            .collect::<Result<Vec<_>, DynamicTypedBindingError>>()?;
574        let catalog = self
575            .find_accepted_schema_catalog_context_for_entity_source_key(entity_source.as_str())?
576            .ok_or(DynamicTypedBindingError::FieldUnavailable)?;
577        let identity = catalog.identity();
578        if identity.entity_path() != entity_source.as_str() {
579            return Err(InternalError::store_invariant().into());
580        }
581        let store = self.db.recovered_store(identity.store_path())?;
582        let bundle = store
583            .with_schema(crate::db::schema::SchemaStore::current_accepted_schema_bundle)?
584            .ok_or_else(InternalError::store_invariant)?;
585        let entity_tag = identity.entity_tag();
586        if bundle.source_bindings().entity(&entity_source) != Some(entity_tag)
587            || bundle.revision() != catalog.revision()
588        {
589            return Err(InternalError::store_invariant().into());
590        }
591        let snapshot = bundle
592            .entity_snapshots()
593            .get(&entity_tag)
594            .ok_or_else(InternalError::store_invariant)?;
595        let row_contract = catalog.inspection_plan().row_contract();
596        let mut fields = Vec::with_capacity(field_requests.len());
597        for (source, field_type, nullable) in &field_requests {
598            let field_id = bundle
599                .source_bindings()
600                .field(entity_tag, source)
601                .ok_or(DynamicTypedBindingError::FieldUnavailable)?;
602            let field = snapshot
603                .fields()
604                .iter()
605                .find(|field| field.id() == field_id)
606                .ok_or_else(InternalError::store_invariant)?;
607            let runtime_field =
608                row_contract.required_accepted_field_contract(usize::from(field.slot().get()))?;
609            if runtime_field.field_id() != field_id {
610                return Err(InternalError::store_invariant().into());
611            }
612            let expected_kind = lower_field_type(field_type, bundle.source_bindings())
613                .map_err(|_| DynamicTypedBindingError::IncompatibleField)?;
614            if field.nullable() != *nullable
615                || !typed_adapter_field_kind_matches(field.kind(), &expected_kind)
616            {
617                return Err(DynamicTypedBindingError::IncompatibleField);
618            }
619            fields.push((
620                source.as_str().to_string(),
621                field_id.get(),
622                field.slot().get(),
623                field.name().to_string(),
624            ));
625        }
626        let adapter_names = bundle.typed_adapter_names()?;
627
628        DynamicTypedEntityBinding::new(
629            database_incarnation_id()?.to_bytes(),
630            entity_source.as_str().to_string(),
631            snapshot.entity_name().to_string(),
632            entity_tag.value(),
633            catalog.revision().get(),
634            catalog.fingerprint(),
635            row_contract.current_layout_version().get(),
636            fields,
637            adapter_names.named_types,
638            adapter_names.enum_variants,
639            adapter_names.composite_fields,
640        )
641        .map_err(Into::into)
642    }
643
644    pub(in crate::db::session) fn current_typed_entity_binding_catalog(
645        &self,
646        binding: &DynamicTypedEntityBinding,
647    ) -> Result<Option<AcceptedSchemaCatalogContext>, InternalError> {
648        if database_incarnation_id()?.to_bytes() != binding.database_incarnation {
649            return Ok(None);
650        }
651        let Some(catalog) = self.find_accepted_schema_catalog_context_for_entity_source_key(
652            binding.entity_source.as_str(),
653        )?
654        else {
655            return Ok(None);
656        };
657        let row_contract = catalog.inspection_plan().row_contract();
658        let identity = catalog.identity();
659        if identity.entity_path() != binding.entity_source.as_str()
660            || identity.entity_tag().value() != binding.entity_tag
661            || catalog.revision().get() != binding.accepted_revision
662            || catalog.fingerprint() != binding.accepted_fingerprint
663            || row_contract.current_layout_version().get() != binding.entity_generation
664        {
665            return Ok(None);
666        }
667        let entity_source = EntitySourceKey::try_new(binding.entity_source.clone())
668            .map_err(|_| InternalError::store_invariant())?;
669        let store = self.db.recovered_store(identity.store_path())?;
670        let bundle = store
671            .with_schema(crate::db::schema::SchemaStore::current_accepted_schema_bundle)?
672            .ok_or_else(InternalError::store_invariant)?;
673        if bundle.revision() != catalog.revision()
674            || bundle.source_bindings().entity(&entity_source) != Some(identity.entity_tag())
675        {
676            return Ok(None);
677        }
678        let snapshot = bundle
679            .entity_snapshots()
680            .get(&identity.entity_tag())
681            .ok_or_else(InternalError::store_invariant)?;
682        for (source_key, expected_field_id, expected_slot) in binding.field_identity_bindings() {
683            let source = FieldSourceKey::try_new(source_key)
684                .map_err(|_| InternalError::store_invariant())?;
685            let Some(field_id) = bundle
686                .source_bindings()
687                .field(identity.entity_tag(), &source)
688            else {
689                return Ok(None);
690            };
691            let Some(field) = snapshot
692                .fields()
693                .iter()
694                .find(|field| field.id() == field_id)
695            else {
696                return Err(InternalError::store_invariant());
697            };
698            if field_id.get() != expected_field_id || field.slot().get() != expected_slot {
699                return Ok(None);
700            }
701        }
702        Ok(Some(catalog))
703    }
704
705    /// Verify that an opaque typed binding still names the exact accepted authority.
706    pub fn typed_entity_binding_is_current(
707        &self,
708        binding: &DynamicTypedEntityBinding,
709    ) -> Result<bool, InternalError> {
710        self.current_typed_entity_binding_catalog(binding)
711            .map(|catalog| catalog.is_some())
712    }
713
714    /// Materialize one accepted delete batch, run bounded frontend validation,
715    /// then commit it atomically.
716    #[cfg(feature = "sql")]
717    pub(in crate::db::session) fn execute_accepted_structural_delete_batch(
718        &self,
719        catalog: &AcceptedSchemaCatalogContext,
720        descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
721        keys: Vec<DecodedDataStoreKey>,
722        precommit_validation: impl FnOnce(&[Vec<Value>]) -> Result<(), InternalError>,
723    ) -> Result<Vec<Vec<Value>>, InternalError> {
724        let mutations = keys
725            .into_iter()
726            .map(AcceptedStructuralMutation::delete)
727            .collect();
728        self.execute_accepted_structural_mutation_batch_inner(
729            catalog,
730            descriptor,
731            mutations,
732            Timestamp::now(),
733            false,
734            |rows| {
735                let rows = rows
736                    .into_iter()
737                    .map(AcceptedStructuralMutationRow::into_values)
738                    .collect::<Vec<_>>();
739                precommit_validation(rows.as_slice())?;
740                Ok(rows)
741            },
742        )
743    }
744
745    /// Materialize one accepted structural batch, let its caller prepare and
746    /// validate the final after-images, then commit atomically.
747    ///
748    /// The caller freezes one operation timestamp and supplies frontend-lowered
749    /// intent only. Accepted defaults, generated values, managed timestamps,
750    /// constraints, relations, row encoding, and commit preparation remain
751    /// owned by this database boundary.
752    pub(in crate::db::session) fn execute_accepted_structural_save_batch<T>(
753        &self,
754        catalog: &AcceptedSchemaCatalogContext,
755        descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
756        mutations: Vec<AcceptedStructuralMutation>,
757        operation_timestamp: Timestamp,
758        precommit_preparation: impl FnOnce(
759            Vec<AcceptedStructuralMutationRow>,
760        ) -> Result<T, InternalError>,
761    ) -> Result<T, InternalError> {
762        self.execute_accepted_structural_mutation_batch_inner(
763            catalog,
764            descriptor,
765            mutations,
766            operation_timestamp,
767            false,
768            precommit_preparation,
769        )
770    }
771
772    /// Commit the largest durable prefix of one accepted resumable update page.
773    #[cfg(feature = "sql")]
774    pub(in crate::db::session) fn execute_accepted_structural_update_prefix(
775        &self,
776        catalog: &AcceptedSchemaCatalogContext,
777        descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
778        mutations: Vec<AcceptedStructuralMutation>,
779        operation_timestamp: Timestamp,
780    ) -> Result<usize, InternalError> {
781        self.execute_accepted_structural_mutation_batch_inner(
782            catalog,
783            descriptor,
784            mutations,
785            operation_timestamp,
786            true,
787            |rows| Ok(rows.len()),
788        )
789    }
790
791    #[expect(
792        clippy::too_many_lines,
793        reason = "one phased owner keeps accepted authority, mutation context, precommit preparation, output capture, and commit staging inseparable"
794    )]
795    fn execute_accepted_structural_mutation_batch_inner<T>(
796        &self,
797        catalog: &AcceptedSchemaCatalogContext,
798        descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
799        mutations: Vec<AcceptedStructuralMutation>,
800        operation_timestamp: Timestamp,
801        largest_journaled_prefix: bool,
802        precommit_preparation: impl FnOnce(
803            Vec<AcceptedStructuralMutationRow>,
804        ) -> Result<T, InternalError>,
805    ) -> Result<T, InternalError> {
806        let identity = catalog.identity();
807        let entity_path = identity.entity_path();
808        let store_path = identity.store_path();
809        let row_decode_contract =
810            descriptor.row_decode_contract(catalog.value_catalog_handle().clone());
811        let row_contract = StructuralRowContract::from_accepted_decode_contract(
812            entity_path,
813            row_decode_contract.clone(),
814        );
815        let store = self.db.recovered_store(store_path)?;
816        let write_context = dynamic_write_context(operation_timestamp);
817        let identity_field = accepted_identity_insert_field(descriptor)?;
818        let identity_incarnation = identity_field
819            .as_ref()
820            .map(|_| database_incarnation_id())
821            .transpose()?;
822        let mutation_count = mutations.len();
823        if mutation_count > MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS {
824            return Err(InternalError::mutation_batch_too_many_items(
825                mutation_count,
826                MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
827            ));
828        }
829        let identity_candidate_count = mutations
830            .iter()
831            .filter(|mutation| {
832                matches!(
833                    mutation,
834                    AcceptedStructuralMutation::Save {
835                        mode: MutationMode::Insert,
836                        target: AcceptedStructuralMutationTarget::ResolveFromAfterImage,
837                        ..
838                    }
839                )
840            })
841            .count();
842        let _ = checked_pre_key_candidate_count(identity_candidate_count)?;
843        let mut identity_cursor: Option<IdentityStatementCursor> = None;
844        let mut identity_insert_ordinal = 0_u32;
845        let mut scheduler = AcceptedMutationConstraintScheduler::new(
846            entity_path,
847            identity.entity_tag(),
848            row_decode_contract.clone(),
849            catalog.fingerprint(),
850            catalog.fingerprint_method_version(),
851            catalog.accepted_row_constraints(),
852            mutation_count,
853        );
854        let mut output = Vec::with_capacity(mutation_count);
855        let mut staged_bytes = 0_usize;
856
857        for (input_index, mutation) in mutations.into_iter().enumerate() {
858            let batch_input_ordinal = u32::try_from(input_index).map_err(|_| {
859                InternalError::mutation_batch_too_many_items(
860                    mutation_count,
861                    MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
862                )
863            })?;
864            let AcceptedStructuralMutation::Save {
865                mode,
866                target,
867                patch: authored_patch,
868            } = mutation
869            else {
870                let AcceptedStructuralMutation::Delete { key } = mutation else {
871                    return Err(InternalError::executor_invariant());
872                };
873                let before = validated_existing_row(store, &key, &row_contract)?
874                    .ok_or_else(|| InternalError::store_not_found(&key))?;
875                let raw_key = key.to_raw()?;
876                let canonical_before = canonical_row_from_raw_row_with_accepted_decode_contract(
877                    entity_path,
878                    row_decode_contract.clone(),
879                    &before,
880                )?;
881                add_structural_mutation_staged_bytes(
882                    &mut staged_bytes,
883                    [
884                        raw_key.as_bytes().len(),
885                        canonical_before.as_raw_row().as_bytes().len(),
886                    ],
887                )?;
888                scheduler.schedule_delete(
889                    CommitRowOp::new(
890                        entity_path,
891                        raw_key,
892                        Some(canonical_before.as_raw_row().as_bytes().to_vec()),
893                        None,
894                        catalog.fingerprint(),
895                    ),
896                    batch_input_ordinal,
897                )?;
898                let reader = StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(
899                    canonical_before.as_raw_row(),
900                    &row_contract,
901                )?;
902                let mut values = Vec::with_capacity(descriptor.fields().len());
903                for field in descriptor.fields() {
904                    values.push(
905                        reader
906                            .required_cached_value(usize::from(field.slot().get()))?
907                            .clone(),
908                    );
909                }
910                output.push(AcceptedStructuralMutationRow {
911                    values,
912                    logical_changed: true,
913                });
914                continue;
915            };
916            let mutation_context =
917                mutation_diagnostic_context(identity.entity_tag(), mode, batch_input_ordinal);
918            let (expected_key, pre_key_insert, mut keyed_patch) = match target {
919                AcceptedStructuralMutationTarget::ResolveFromAfterImage => {
920                    let candidate_ordinal =
921                        if identity_field.is_some() && matches!(mode, MutationMode::Insert) {
922                            identity_insert_ordinal
923                        } else {
924                            batch_input_ordinal
925                        };
926                    (
927                        None,
928                        Some(AcceptedPreKeyInsert::new(
929                            identity.entity_tag(),
930                            authored_patch,
931                            candidate_ordinal,
932                        )),
933                        None,
934                    )
935                }
936                AcceptedStructuralMutationTarget::Expected(key) => {
937                    (Some(*key), None, Some(authored_patch))
938                }
939            };
940            if matches!(mode, MutationMode::Replace)
941                && let Some(key) = expected_key.as_ref()
942            {
943                let patch = keyed_patch
944                    .take()
945                    .ok_or_else(InternalError::executor_invariant)?;
946                keyed_patch = Some(preserve_dynamic_replacement_identity(
947                    key, descriptor, patch,
948                )?);
949            }
950            let patch = pre_key_insert
951                .as_ref()
952                .map(AcceptedPreKeyInsert::fields)
953                .or(keyed_patch.as_ref())
954                .ok_or_else(InternalError::executor_invariant)?;
955            let before = expected_key
956                .as_ref()
957                .map(|key| validated_existing_row(store, key, &row_contract))
958                .transpose()?
959                .flatten();
960            match mode {
961                MutationMode::Insert if before.is_some() => {
962                    return Err(mutation_key_exists_error());
963                }
964                MutationMode::Update if before.is_none() => {
965                    let key = expected_key
966                        .as_ref()
967                        .ok_or_else(InternalError::executor_invariant)?;
968                    return Err(InternalError::store_not_found(key));
969                }
970                MutationMode::Insert | MutationMode::Replace | MutationMode::Update => {}
971            }
972
973            let identity_allocation = if let Some(identity_field) = identity_field.as_ref()
974                && matches!(mode, MutationMode::Insert)
975                && before.is_none()
976            {
977                let candidate = pre_key_insert.as_ref().ok_or_else(|| {
978                    InternalError::mutation_database_owned_field_explicit(
979                        mutation_context,
980                        identity_field.field_id.get(),
981                    )
982                })?;
983                if identity_cursor.is_none() {
984                    let incarnation = identity_incarnation
985                        .ok_or_else(InternalError::identity_state_corruption)?;
986                    identity_cursor = Some(store.with_schema(|schema_store| {
987                        schema_store.identity_statement_cursor(
988                            incarnation,
989                            identity.entity_tag(),
990                            identity_field.field_id,
991                            &identity_field.accepted_kind,
992                        )
993                    })?);
994                }
995                let allocation = identity_cursor
996                    .as_mut()
997                    .ok_or_else(InternalError::identity_state_corruption)?
998                    .allocate(identity_field.field_slot, candidate.input_ordinal())?;
999                identity_insert_ordinal = identity_insert_ordinal
1000                    .checked_add(1)
1001                    .ok_or_else(InternalError::identity_candidate_count_exhausted)?;
1002                Some(allocation)
1003            } else if let Some(identity_field) = identity_field.as_ref()
1004                && matches!(mode, MutationMode::Replace)
1005                && before.is_none()
1006            {
1007                return Err(InternalError::mutation_database_owned_field_explicit(
1008                    mutation_context,
1009                    identity_field.field_id.get(),
1010                ));
1011            } else {
1012                None
1013            };
1014
1015            let resolved = match (mode, before.as_ref()) {
1016                (MutationMode::Insert | MutationMode::Replace, None) => {
1017                    resolve_insert_structural_patch_with_accepted_contract(
1018                        entity_path,
1019                        row_decode_contract.clone(),
1020                        catalog.fingerprint(),
1021                        catalog.accepted_row_constraints(),
1022                        patch,
1023                        write_context,
1024                        mutation_context,
1025                        identity_allocation.as_ref(),
1026                    )?
1027                }
1028                (MutationMode::Update, Some(before)) => {
1029                    resolve_update_structural_patch_with_accepted_contract(
1030                        entity_path,
1031                        row_decode_contract.clone(),
1032                        catalog.fingerprint(),
1033                        catalog.accepted_row_constraints(),
1034                        before,
1035                        patch,
1036                        write_context,
1037                        mutation_context,
1038                    )?
1039                }
1040                (MutationMode::Replace, Some(before)) => {
1041                    resolve_existing_replace_structural_patch_with_accepted_contract(
1042                        entity_path,
1043                        row_decode_contract.clone(),
1044                        catalog.fingerprint(),
1045                        catalog.accepted_row_constraints(),
1046                        before,
1047                        patch,
1048                        write_context,
1049                        mutation_context,
1050                    )?
1051                }
1052                (MutationMode::Insert, Some(_)) | (MutationMode::Update, None) => {
1053                    return Err(InternalError::executor_invariant());
1054                }
1055            };
1056            let (after, provenance) = resolved.into_parts();
1057            let reader = StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(
1058                after.as_raw_row(),
1059                &row_contract,
1060            )?;
1061            let data_key = match expected_key {
1062                Some(key) => {
1063                    reader.validate_primary_key(&key)?;
1064                    key
1065                }
1066                None => {
1067                    data_key_from_row(identity.entity_tag(), &row_contract, after.as_raw_row())?
1068                }
1069            };
1070            if let Some(allocation) = identity_allocation.as_ref() {
1071                validate_identity_materialization(
1072                    identity.entity_tag(),
1073                    identity_field
1074                        .as_ref()
1075                        .ok_or_else(InternalError::identity_corruption)?,
1076                    pre_key_insert
1077                        .as_ref()
1078                        .ok_or_else(InternalError::identity_corruption)?,
1079                    allocation,
1080                    &data_key,
1081                    &reader,
1082                )?;
1083            }
1084            if matches!(mode, MutationMode::Insert)
1085                && validated_existing_row(store, &data_key, &row_contract)?.is_some()
1086            {
1087                return Err(insert_key_exists_after_generation(
1088                    identity_allocation.is_some(),
1089                ));
1090            }
1091            let raw_key = data_key.to_raw()?;
1092            let canonical_before = before
1093                .as_ref()
1094                .map(|before| {
1095                    canonical_row_from_raw_row_with_accepted_decode_contract(
1096                        entity_path,
1097                        row_decode_contract.clone(),
1098                        before,
1099                    )
1100                })
1101                .transpose()?;
1102            let logical_changed = canonical_before.as_ref().is_none_or(|before| {
1103                before.as_raw_row().as_bytes() != after.as_raw_row().as_bytes()
1104            });
1105            let physical_changed = before
1106                .as_ref()
1107                .is_none_or(|before| before.as_bytes() != after.as_raw_row().as_bytes());
1108            add_structural_mutation_staged_bytes(
1109                &mut staged_bytes,
1110                [
1111                    raw_key.as_bytes().len(),
1112                    canonical_before
1113                        .as_ref()
1114                        .map_or(0, |before| before.as_raw_row().as_bytes().len()),
1115                    after.as_raw_row().as_bytes().len(),
1116                ],
1117            )?;
1118            let row_op = physical_changed.then(|| {
1119                CommitRowOp::new(
1120                    entity_path,
1121                    raw_key.clone(),
1122                    canonical_before
1123                        .as_ref()
1124                        .map(|before| before.as_raw_row().as_bytes().to_vec()),
1125                    Some(after.as_raw_row().as_bytes().to_vec()),
1126                    catalog.fingerprint(),
1127                )
1128            });
1129            scheduler.schedule_save_after_image(
1130                mode,
1131                &data_key,
1132                after.as_raw_row(),
1133                provenance.as_slice(),
1134                row_op,
1135                batch_input_ordinal,
1136            )?;
1137            if physical_changed {
1138                #[cfg(feature = "sql")]
1139                if largest_journaled_prefix
1140                    && !crate::db::commit::journaled_row_ops_fit_commit_window(scheduler.rows())
1141                {
1142                    scheduler.pop_last_save_row()?;
1143                    if output.is_empty() {
1144                        return Err(InternalError::query_sql_write_boundary(
1145                            icydb_diagnostic_code::SqlWriteBoundaryCode::ResumableUpdateSingleRowResourceExceeded,
1146                        ));
1147                    }
1148                    break;
1149                }
1150            }
1151
1152            let mut values = Vec::with_capacity(descriptor.fields().len());
1153            for field in descriptor.fields() {
1154                values.push(
1155                    reader
1156                        .required_cached_value(usize::from(field.slot().get()))?
1157                        .clone(),
1158                );
1159            }
1160            output.push(AcceptedStructuralMutationRow {
1161                values,
1162                logical_changed,
1163            });
1164        }
1165
1166        #[cfg(not(feature = "sql"))]
1167        let _ = largest_journaled_prefix;
1168
1169        let batch = scheduler.finish();
1170        let prepared = precommit_preparation(output)?;
1171        let identity_ranges = identity_cursor
1172            .map(IdentityStatementCursor::into_range_advance)
1173            .transpose()?
1174            .into_iter()
1175            .flatten()
1176            .collect::<Vec<_>>();
1177        if batch.is_empty() && !identity_ranges.is_empty() {
1178            return Err(InternalError::identity_corruption());
1179        }
1180        if !batch.is_empty() {
1181            commit_structural_row_ops_with_window_for_path(
1182                &self.db,
1183                entity_path,
1184                batch,
1185                identity_ranges,
1186                "accepted_structural_batch_apply",
1187            )?;
1188        }
1189        Ok(prepared)
1190    }
1191
1192    fn execute_one_accepted_save_mutation(
1193        &self,
1194        catalog: &AcceptedSchemaCatalogContext,
1195        descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
1196        mode: MutationMode,
1197        target: AcceptedStructuralMutationTarget,
1198        patch: AcceptedMutationIntentPatch,
1199    ) -> Result<DynamicMutationResult, InternalError> {
1200        let identity = catalog.identity();
1201        let entity_path = identity.entity_path();
1202        let result = self.execute_accepted_structural_save_batch(
1203            catalog,
1204            descriptor,
1205            vec![AcceptedStructuralMutation::save(mode, target, patch)],
1206            Timestamp::now(),
1207            |rows| prepare_dynamic_mutation_result(catalog, descriptor, rows, false),
1208        )?;
1209        record(MetricsEvent::SaveMutation {
1210            entity_path: entity_path.into(),
1211            kind: match mode {
1212                MutationMode::Insert => SaveMutationKind::Insert,
1213                MutationMode::Replace => SaveMutationKind::Replace,
1214                MutationMode::Update => SaveMutationKind::Update,
1215            },
1216            rows_touched: u64::from(result.affected_rows),
1217        });
1218        Ok(result)
1219    }
1220
1221    /// Execute one trusted entity-name-driven structural mutation.
1222    ///
1223    /// This lane resolves public values, defaults, generation, management,
1224    /// constraints, relations, and commit preparation from accepted schema.
1225    /// It never materializes a generated entity or invokes application
1226    /// validators/normalizers.
1227    pub fn execute_trusted_dynamic_mutation(
1228        &self,
1229        request: &DynamicMutation,
1230    ) -> Result<DynamicMutationResult, InternalError> {
1231        self.execute_trusted_dynamic_mutation_batch_with_result_policy(vec![request.clone()], false)
1232    }
1233
1234    /// Execute one bounded same-entity structural mutation batch atomically.
1235    ///
1236    /// Every item binds to the same accepted catalog identity, shares one
1237    /// operation timestamp, and is projected to its public result before the
1238    /// commit marker can be published.
1239    pub fn execute_trusted_dynamic_mutation_batch(
1240        &self,
1241        requests: Vec<DynamicMutation>,
1242    ) -> Result<DynamicMutationResult, InternalError> {
1243        self.execute_trusted_dynamic_mutation_batch_with_result_policy(requests, true)
1244    }
1245
1246    fn execute_trusted_dynamic_mutation_batch_with_result_policy(
1247        &self,
1248        requests: Vec<DynamicMutation>,
1249        enforce_mixed_batch_result_bound: bool,
1250    ) -> Result<DynamicMutationResult, InternalError> {
1251        if requests.is_empty() {
1252            return Err(InternalError::mutation_batch_empty());
1253        }
1254        if requests.len() > MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS {
1255            return Err(InternalError::mutation_batch_too_many_items(
1256                requests.len(),
1257                MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
1258            ));
1259        }
1260        let first = requests
1261            .first()
1262            .ok_or_else(InternalError::mutation_batch_empty)?;
1263        if first.entity().is_empty() {
1264            return Err(InternalError::executor_unsupported());
1265        }
1266        let catalog = self.accepted_schema_catalog_context_for_entity_name(Some(first.entity()))?;
1267        let accepted_identity = catalog.identity();
1268        let descriptor =
1269            AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())?;
1270        let mut mutations = Vec::with_capacity(requests.len());
1271        let mut save_kinds = Vec::with_capacity(requests.len());
1272
1273        for (batch_position, request) in requests.iter().enumerate() {
1274            let batch_position = u32::try_from(batch_position).map_err(|_| {
1275                InternalError::mutation_batch_too_many_items(
1276                    requests.len(),
1277                    MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
1278                )
1279            })?;
1280            if request.entity().is_empty() {
1281                return Err(InternalError::executor_unsupported());
1282            }
1283            let item_catalog =
1284                self.accepted_schema_catalog_context_for_entity_name(Some(request.entity()))?;
1285            if item_catalog.identity() != accepted_identity {
1286                return Err(InternalError::mutation_batch_entity_mismatch(
1287                    batch_position,
1288                    accepted_identity.entity_tag().value(),
1289                    item_catalog.identity().entity_tag().value(),
1290                ));
1291            }
1292            let (mutation, save_kind) = lower_dynamic_mutation_intent(
1293                accepted_identity.entity_tag(),
1294                accepted_identity.entity_path(),
1295                &descriptor,
1296                request,
1297                batch_position,
1298            )?;
1299            mutations.push(mutation);
1300            save_kinds.push(save_kind);
1301        }
1302
1303        let entity_path = accepted_identity.entity_path_handle();
1304        let (result, metrics) = self.execute_accepted_structural_mutation_batch_inner(
1305            &catalog,
1306            &descriptor,
1307            mutations,
1308            Timestamp::now(),
1309            false,
1310            |rows| {
1311                if rows.len() != save_kinds.len() {
1312                    return Err(InternalError::executor_invariant());
1313                }
1314                let metrics = rows
1315                    .iter()
1316                    .zip(save_kinds)
1317                    .filter_map(|(row, kind)| kind.map(|kind| (kind, row.logical_changed())))
1318                    .collect::<Vec<_>>();
1319                let result = prepare_dynamic_mutation_result(
1320                    &catalog,
1321                    &descriptor,
1322                    rows,
1323                    enforce_mixed_batch_result_bound,
1324                )?;
1325                Ok((result, metrics))
1326            },
1327        )?;
1328        for (kind, logical_changed) in metrics {
1329            record(MetricsEvent::SaveMutation {
1330                entity_path: entity_path.clone(),
1331                kind,
1332                rows_touched: u64::from(logical_changed),
1333            });
1334        }
1335        Ok(result)
1336    }
1337
1338    /// Execute one generated typed write through immutable accepted entity and
1339    /// field identities. `None` means the opaque binding is stale.
1340    #[doc(hidden)]
1341    pub fn execute_trusted_typed_mutation(
1342        &self,
1343        binding: &DynamicTypedEntityBinding,
1344        request: &DynamicTypedMutation,
1345    ) -> Result<Option<DynamicMutationResult>, InternalError> {
1346        let Some(catalog) = self.current_typed_entity_binding_catalog(binding)? else {
1347            return Ok(None);
1348        };
1349        let identity = catalog.identity();
1350        let descriptor =
1351            AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())?;
1352        let mode = dynamic_typed_mutation_mode(request);
1353        let (target, patch) = match request {
1354            DynamicTypedMutation::Insert { patch } => (
1355                AcceptedStructuralMutationTarget::ResolveFromAfterImage,
1356                patch,
1357            ),
1358            DynamicTypedMutation::Update { key, patch }
1359            | DynamicTypedMutation::Replace { key, patch } => (
1360                AcceptedStructuralMutationTarget::expected(dynamic_key(
1361                    identity.entity_tag(),
1362                    key,
1363                )?),
1364                patch,
1365            ),
1366        };
1367        if !patch.is_bound_to(binding) {
1368            return Ok(None);
1369        }
1370        let patch = lower_typed_patch(
1371            &descriptor,
1372            patch,
1373            mode,
1374            mutation_diagnostic_context(identity.entity_tag(), mode, 0),
1375        )?;
1376        self.execute_one_accepted_save_mutation(&catalog, &descriptor, mode, target, patch)
1377            .map(Some)
1378    }
1379
1380    /// Execute one trusted atomic insert batch from entity-name-driven patches.
1381    ///
1382    /// Every patch is lowered against the same accepted snapshot and shares
1383    /// one operation timestamp before the canonical structural batch owner
1384    /// stages any durable effect.
1385    pub fn execute_trusted_dynamic_insert_batch(
1386        &self,
1387        entity: &str,
1388        patches: Vec<DynamicStructuralPatch>,
1389    ) -> Result<DynamicMutationResult, InternalError> {
1390        let mutations = patches
1391            .into_iter()
1392            .map(|patch| DynamicMutation::Insert {
1393                entity: entity.to_string(),
1394                patch,
1395            })
1396            .collect();
1397        self.execute_trusted_dynamic_mutation_batch_with_result_policy(mutations, false)
1398    }
1399}
1400
1401#[cfg(test)]
1402mod typed_adapter_tests {
1403    use super::{
1404        AcceptedFieldKind, DbSession, DynamicTypedBindingError, DynamicTypedFieldBindingRequest,
1405        DynamicTypedFieldType, DynamicTypedMutation, DynamicWriteCell, dynamic_typed_field_type,
1406        typed_adapter_field_kind_matches,
1407    };
1408    use crate::{
1409        db::{
1410            data::DataStore,
1411            index::IndexStore,
1412            registry::{StoreAllocationIdentities, StoreRegistry, StoreRuntimeStorageCapabilities},
1413            schema::{
1414                AcceptedSchemaRevision, FieldId, FieldStorageDecode, LeafCodec,
1415                PersistedFieldSnapshot, PersistedSchemaSnapshot, ScalarCodec, SchemaFieldSlot,
1416                SchemaInsertDefault, SchemaRowLayout, SchemaStore, SchemaVersion,
1417                accepted_schema_candidate_with_field_bindings_for_tests,
1418            },
1419        },
1420        traits::{CanisterKind, Path},
1421        types::EntityTag,
1422        value::InputValue,
1423    };
1424    use icydb_schema::{EntitySourceKey, FieldSourceKey, ScalarType};
1425    use std::{cell::RefCell, collections::BTreeMap};
1426
1427    const STORE_PATH: &str = "session::write::typed_adapter_tests::Store";
1428    const ENTITY_SOURCE: &str = "session::write::typed_adapter_tests::Entity";
1429    const OTHER_ENTITY_SOURCE: &str = "session::write::typed_adapter_tests::OtherEntity";
1430    const ID_SOURCE: &str = "session::write::typed_adapter_tests::Entity::id";
1431    const VALUE_SOURCE: &str = "session::write::typed_adapter_tests::Entity::value";
1432    const REPLACEMENT_SOURCE: &str =
1433        "session::write::typed_adapter_tests::Entity::replacement_value";
1434    const OTHER_ID_SOURCE: &str = "session::write::typed_adapter_tests::OtherEntity::id";
1435
1436    struct TestCanister;
1437
1438    impl Path for TestCanister {
1439        const PATH: &'static str = "session::write::typed_adapter_tests::Canister";
1440    }
1441
1442    impl CanisterKind for TestCanister {
1443        const COMMIT_MEMORY_ID: u8 = 41;
1444        const COMMIT_STABLE_KEY: &'static str = "icydb.typed_adapter_tests.commit.v1";
1445        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 42;
1446        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
1447            "icydb.typed_adapter_tests.integrity.progress.v1";
1448    }
1449
1450    thread_local! {
1451        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
1452        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
1453        static SCHEMA_STORE: RefCell<SchemaStore> =
1454            const { RefCell::new(SchemaStore::init_heap()) };
1455        static STORE_REGISTRY: StoreRegistry = {
1456            let mut registry = StoreRegistry::new();
1457            registry.register_store(
1458                STORE_PATH,
1459                &DATA_STORE,
1460                &INDEX_STORE,
1461                &SCHEMA_STORE,
1462                StoreAllocationIdentities::absent(),
1463                StoreRuntimeStorageCapabilities::heap(),
1464            ).expect("typed adapter test store should register");
1465            registry
1466        };
1467    }
1468
1469    fn nat64_field(id: u32, name: &str, slot: u16) -> PersistedFieldSnapshot {
1470        PersistedFieldSnapshot::new_initial(
1471            FieldId::new(id),
1472            name.to_string(),
1473            SchemaFieldSlot::new(slot),
1474            AcceptedFieldKind::Nat64,
1475            Vec::new(),
1476            false,
1477            SchemaInsertDefault::None,
1478            FieldStorageDecode::ByKind,
1479            LeafCodec::Scalar(ScalarCodec::Nat64),
1480        )
1481    }
1482
1483    fn snapshot(
1484        entity_source: &str,
1485        entity_name: &str,
1486        fields: Vec<PersistedFieldSnapshot>,
1487    ) -> PersistedSchemaSnapshot {
1488        let layout = SchemaRowLayout::initial(
1489            fields
1490                .iter()
1491                .map(|field| (field.id(), field.slot()))
1492                .collect(),
1493        );
1494        PersistedSchemaSnapshot::new(
1495            SchemaVersion::initial(),
1496            entity_source.to_string(),
1497            entity_name.to_string(),
1498            FieldId::new(1),
1499            layout,
1500            fields,
1501        )
1502    }
1503
1504    fn field_source(source: &str) -> FieldSourceKey {
1505        FieldSourceKey::try_new(source).expect("typed field source should admit")
1506    }
1507
1508    fn entity_source(source: &str) -> EntitySourceKey {
1509        EntitySourceKey::try_new(source).expect("typed entity source should admit")
1510    }
1511
1512    fn publish(
1513        session: &DbSession<TestCanister>,
1514        expected: AcceptedSchemaRevision,
1515        revision: AcceptedSchemaRevision,
1516        snapshots: BTreeMap<EntityTag, PersistedSchemaSnapshot>,
1517        fields: BTreeMap<(EntityTag, FieldSourceKey), FieldId>,
1518    ) {
1519        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
1520            STORE_PATH, revision, snapshots, fields,
1521        );
1522        let store = session
1523            .db
1524            .store_handle(STORE_PATH)
1525            .expect("typed adapter test store should resolve");
1526        crate::db::commit::publish_accepted_schema_candidate(
1527            STORE_PATH, store, expected, &candidate,
1528        )
1529        .expect("typed binding candidate should publish");
1530    }
1531
1532    fn request(source: &str) -> DynamicTypedFieldBindingRequest {
1533        DynamicTypedFieldBindingRequest::new(
1534            source.to_string(),
1535            DynamicTypedFieldType::Scalar(ScalarType::Nat64),
1536            false,
1537        )
1538    }
1539
1540    fn assert_query_diagnostic(
1541        error: crate::db::QueryError,
1542        code: icydb_diagnostic_code::DiagnosticCode,
1543        origin: icydb_diagnostic_code::ErrorOrigin,
1544        detail: icydb_diagnostic_code::DiagnosticDetail,
1545    ) {
1546        let diagnostic = error.diagnostic();
1547        assert_eq!(diagnostic.code(), code);
1548        assert_eq!(diagnostic.origin(), origin);
1549        assert_eq!(diagnostic.detail(), Some(&detail));
1550    }
1551
1552    #[test]
1553    fn typed_adapter_kind_matching_is_exact_but_accepts_relation_key_wrappers() {
1554        let relation = AcceptedFieldKind::Relation {
1555            target_path: "test::Target".to_string(),
1556            target_entity_name: "Target".to_string(),
1557            target_entity_tag: EntityTag::new(7),
1558            target_store_path: "test::Store".to_string(),
1559            key_kind: Box::new(AcceptedFieldKind::Nat64),
1560        };
1561
1562        assert!(typed_adapter_field_kind_matches(
1563            &relation,
1564            &AcceptedFieldKind::Nat64,
1565        ));
1566        assert!(typed_adapter_field_kind_matches(
1567            &AcceptedFieldKind::List(Box::new(relation)),
1568            &AcceptedFieldKind::List(Box::new(AcceptedFieldKind::Nat64)),
1569        ));
1570        assert!(!typed_adapter_field_kind_matches(
1571            &AcceptedFieldKind::Nat64,
1572            &AcceptedFieldKind::Nat32,
1573        ));
1574    }
1575
1576    #[test]
1577    fn typed_adapter_field_contract_rejects_invalid_named_source_identity() {
1578        assert!(matches!(
1579            dynamic_typed_field_type(DynamicTypedFieldType::Named(String::new())),
1580            Err(DynamicTypedBindingError::FieldUnavailable),
1581        ));
1582        assert!(matches!(
1583            dynamic_typed_field_type(DynamicTypedFieldType::Scalar(ScalarType::Nat16)),
1584            Ok(icydb_schema::FieldType::Scalar(ScalarType::Nat16)),
1585        ));
1586    }
1587
1588    // Keep the full rename, stale-binding, and old-name-reuse lifecycle in one
1589    // regression so each issued binding is checked against the next revision.
1590    #[expect(clippy::too_many_lines)]
1591    #[test]
1592    fn typed_binding_uses_accepted_ids_and_slots_across_renames_and_name_reuse() {
1593        let entity_tag = EntityTag::new(91);
1594        let other_entity_tag = EntityTag::new(92);
1595        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
1596        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
1597        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
1598
1599        let session = DbSession::<TestCanister>::new(
1600            &STORE_REGISTRY,
1601            &crate::db::RequestExecutionRoot::__new_runtime_root(),
1602        );
1603        session
1604            .db
1605            .ensure_recovered_state()
1606            .expect("typed adapter test database should initialize");
1607        publish(
1608            &session,
1609            AcceptedSchemaRevision::NONE,
1610            AcceptedSchemaRevision::INITIAL,
1611            BTreeMap::from([(
1612                entity_tag,
1613                snapshot(
1614                    ENTITY_SOURCE,
1615                    "Entity",
1616                    vec![nat64_field(1, "id", 0), nat64_field(2, "value", 1)],
1617                ),
1618            )]),
1619            BTreeMap::from([
1620                ((entity_tag, field_source(ID_SOURCE)), FieldId::new(1)),
1621                ((entity_tag, field_source(VALUE_SOURCE)), FieldId::new(2)),
1622            ]),
1623        );
1624
1625        let initial_catalog = session
1626            .find_accepted_schema_catalog_context_for_entity_source_key(ENTITY_SOURCE)
1627            .expect("initial source catalog lookup should inspect")
1628            .expect("initial source catalog should exist");
1629        assert_eq!(initial_catalog.identity().entity_tag(), entity_tag);
1630        let initial = session
1631            .issue_typed_entity_binding(
1632                entity_source(ENTITY_SOURCE).as_str(),
1633                &[request(ID_SOURCE), request(VALUE_SOURCE)],
1634            )
1635            .expect("initial typed binding should issue");
1636        assert_eq!(initial.field_slot(ID_SOURCE), Some(0));
1637        assert_eq!(initial.field_slot(VALUE_SOURCE), Some(1));
1638        assert_eq!(initial.output_field_slot("value"), Some(1));
1639        let initial_patch = initial
1640            .bind_write_fields(vec![(
1641                VALUE_SOURCE.to_string(),
1642                DynamicWriteCell::Value(InputValue::Nat64(7)),
1643            )])
1644            .expect("source-bound patch should lower");
1645        assert_eq!(
1646            initial_patch.fields(),
1647            &[(2, 1, DynamicWriteCell::Value(InputValue::Nat64(7)))]
1648        );
1649
1650        publish(
1651            &session,
1652            AcceptedSchemaRevision::INITIAL,
1653            AcceptedSchemaRevision::new(2),
1654            BTreeMap::from([
1655                (
1656                    entity_tag,
1657                    snapshot(
1658                        ENTITY_SOURCE,
1659                        "RenamedEntity",
1660                        vec![
1661                            nat64_field(1, "id", 0),
1662                            nat64_field(2, "renamed_value", 1),
1663                            nat64_field(3, "value", 2),
1664                        ],
1665                    ),
1666                ),
1667                (
1668                    other_entity_tag,
1669                    snapshot(OTHER_ENTITY_SOURCE, "Entity", vec![nat64_field(1, "id", 0)]),
1670                ),
1671            ]),
1672            BTreeMap::from([
1673                ((entity_tag, field_source(ID_SOURCE)), FieldId::new(1)),
1674                ((entity_tag, field_source(VALUE_SOURCE)), FieldId::new(2)),
1675                (
1676                    (entity_tag, field_source(REPLACEMENT_SOURCE)),
1677                    FieldId::new(3),
1678                ),
1679                (
1680                    (other_entity_tag, field_source(OTHER_ID_SOURCE)),
1681                    FieldId::new(1),
1682                ),
1683            ]),
1684        );
1685
1686        let stale_authority = session
1687            .ensure_accepted_schema_authority_is_current_for_store_path(
1688                STORE_PATH,
1689                initial_catalog.value_catalog_handle().authority(),
1690            )
1691            .expect_err("the initial accepted authority must be stale after revision two");
1692        assert_eq!(
1693            stale_authority.diagnostic_facts(),
1694            vec![
1695                (
1696                    icydb_diagnostic_code::DiagnosticFactTag::ExpectedRevision,
1697                    AcceptedSchemaRevision::INITIAL.get(),
1698                ),
1699                (
1700                    icydb_diagnostic_code::DiagnosticFactTag::CurrentRevision,
1701                    AcceptedSchemaRevision::new(2).get(),
1702                ),
1703            ],
1704        );
1705
1706        assert!(
1707            !session
1708                .typed_entity_binding_is_current(&initial)
1709                .expect("renamed binding currentness should inspect")
1710        );
1711        let renamed = session
1712            .issue_typed_entity_binding(ENTITY_SOURCE, &[request(ID_SOURCE), request(VALUE_SOURCE)])
1713            .expect("renamed source-bound adapter should rebind");
1714        assert_eq!(renamed.entity(), "RenamedEntity");
1715        assert_eq!(renamed.field_slot(VALUE_SOURCE), Some(1));
1716        assert_eq!(renamed.output_field_slot("renamed_value"), Some(1));
1717        assert_eq!(renamed.output_field_slot("value"), None);
1718
1719        publish(
1720            &session,
1721            AcceptedSchemaRevision::new(2),
1722            AcceptedSchemaRevision::new(3),
1723            BTreeMap::from([
1724                (
1725                    entity_tag,
1726                    snapshot(
1727                        ENTITY_SOURCE,
1728                        "RenamedEntity",
1729                        vec![nat64_field(1, "id", 0), nat64_field(2, "value", 1)],
1730                    ),
1731                ),
1732                (
1733                    other_entity_tag,
1734                    snapshot(OTHER_ENTITY_SOURCE, "Entity", vec![nat64_field(1, "id", 0)]),
1735                ),
1736            ]),
1737            BTreeMap::from([
1738                ((entity_tag, field_source(ID_SOURCE)), FieldId::new(1)),
1739                (
1740                    (entity_tag, field_source(REPLACEMENT_SOURCE)),
1741                    FieldId::new(2),
1742                ),
1743                (
1744                    (other_entity_tag, field_source(OTHER_ID_SOURCE)),
1745                    FieldId::new(1),
1746                ),
1747            ]),
1748        );
1749
1750        assert!(matches!(
1751            session.issue_typed_entity_binding(
1752                ENTITY_SOURCE,
1753                &[request(ID_SOURCE), request(VALUE_SOURCE)],
1754            ),
1755            Err(DynamicTypedBindingError::FieldUnavailable),
1756        ));
1757        assert!(
1758            !session
1759                .typed_entity_binding_is_current(&renamed)
1760                .expect("removed source binding should become stale")
1761        );
1762
1763        let replacement = session
1764            .issue_typed_entity_binding(
1765                ENTITY_SOURCE,
1766                &[request(ID_SOURCE), request(REPLACEMENT_SOURCE)],
1767            )
1768            .expect("explicit replacement source should bind");
1769        assert!(
1770            session
1771                .execute_trusted_typed_mutation(
1772                    &replacement,
1773                    &DynamicTypedMutation::Insert {
1774                        patch: initial_patch
1775                    },
1776                )
1777                .expect("cross-binding patch should fail closed")
1778                .is_none()
1779        );
1780        let patch = replacement
1781            .bind_write_fields(vec![
1782                (
1783                    ID_SOURCE.to_string(),
1784                    DynamicWriteCell::Value(InputValue::Nat64(1)),
1785                ),
1786                (
1787                    REPLACEMENT_SOURCE.to_string(),
1788                    DynamicWriteCell::Value(InputValue::Nat64(9)),
1789                ),
1790            ])
1791            .expect("replacement source write should bind by accepted IDs and slots");
1792        let result = session
1793            .execute_trusted_typed_mutation(&replacement, &DynamicTypedMutation::Insert { patch })
1794            .expect("typed insert should use the accepted mutation pipeline")
1795            .expect("replacement binding should remain current");
1796        assert_eq!(result.entity, "RenamedEntity");
1797        assert_eq!(result.columns, vec!["id".to_string(), "value".to_string()]);
1798        assert_eq!(
1799            result.rows,
1800            vec![vec![
1801                crate::value::OutputValue::Nat64(1),
1802                crate::value::OutputValue::Nat64(9)
1803            ]]
1804        );
1805        assert_eq!(result.affected_rows, 1);
1806
1807        let second_patch = replacement
1808            .bind_write_fields(vec![
1809                (
1810                    ID_SOURCE.to_string(),
1811                    DynamicWriteCell::Value(InputValue::Nat64(2)),
1812                ),
1813                (
1814                    REPLACEMENT_SOURCE.to_string(),
1815                    DynamicWriteCell::Value(InputValue::Nat64(10)),
1816                ),
1817            ])
1818            .expect("second source-bound patch should lower");
1819        session
1820            .execute_trusted_typed_mutation(
1821                &replacement,
1822                &DynamicTypedMutation::Insert {
1823                    patch: second_patch,
1824                },
1825            )
1826            .expect("second typed insert should use the accepted mutation pipeline")
1827            .expect("replacement binding should remain current");
1828
1829        {
1830            let query = crate::db::DynamicQuery::new("RenamedEntity")
1831                .select(["id", "value"])
1832                .order_by(crate::db::asc("id"))
1833                .limit(1);
1834            let result = session
1835                .execute_trusted_live_page(&query, None)
1836                .expect("SQL-free dynamic execution should use accepted authority");
1837            assert_eq!(result.entity, "RenamedEntity");
1838            assert_eq!(result.columns, vec!["id".to_string(), "value".to_string()]);
1839            assert_eq!(
1840                result.rows,
1841                vec![vec![
1842                    crate::value::OutputValue::Nat64(1),
1843                    crate::value::OutputValue::Nat64(9)
1844                ]]
1845            );
1846            assert_eq!(result.row_count, 1);
1847            assert_query_diagnostic(
1848                session
1849                    .execute_trusted_live_page(&query.cursor("00"), None)
1850                    .expect_err("scalar execution must reject grouped cursor state"),
1851                icydb_diagnostic_code::DiagnosticCode::QueryIntent,
1852                icydb_diagnostic_code::ErrorOrigin::Query,
1853                icydb_diagnostic_code::DiagnosticDetail::QueryKind {
1854                    kind: icydb_diagnostic_code::QueryErrorKind::Intent,
1855                },
1856            );
1857            assert_query_diagnostic(
1858                session
1859                    .execute_public_dynamic_grouped_query(
1860                        &crate::db::DynamicQuery::new("RenamedEntity").grouped_limits(1, 1024),
1861                    )
1862                    .expect_err("grouped execution must reject scalar query state"),
1863                icydb_diagnostic_code::DiagnosticCode::QueryIntent,
1864                icydb_diagnostic_code::ErrorOrigin::Query,
1865                icydb_diagnostic_code::DiagnosticDetail::QueryKind {
1866                    kind: icydb_diagnostic_code::QueryErrorKind::Intent,
1867                },
1868            );
1869
1870            let grouped_query = crate::db::DynamicQuery::new("RenamedEntity")
1871                .filter(crate::db::FieldRef::new("id").eq(1_u64))
1872                .group_by("value")
1873                .aggregate(crate::db::count())
1874                .grouped_limits(1, 16 * 1024)
1875                .limit(1);
1876            let grouped = session
1877                .execute_public_dynamic_grouped_query(&grouped_query)
1878                .expect("SQL-free grouped execution should use accepted authority");
1879            let typed_grouped = session
1880                .execute_public_dynamic_grouped_query_for_typed_binding(
1881                    &replacement,
1882                    &grouped_query,
1883                )
1884                .expect("typed grouped execution should inspect accepted authority")
1885                .expect("replacement binding should remain current");
1886            assert_eq!(typed_grouped, grouped);
1887            assert!(
1888                session
1889                    .execute_public_dynamic_grouped_query_for_typed_binding(
1890                        &renamed,
1891                        &grouped_query,
1892                    )
1893                    .expect("stale grouped binding should inspect accepted authority")
1894                    .is_none(),
1895                "stale typed grouped bindings must fail closed before execution"
1896            );
1897            assert_eq!(grouped.entity, "RenamedEntity");
1898            assert_eq!(grouped.row_count, 1);
1899            assert_eq!(grouped.rows.len(), 1);
1900            assert_eq!(
1901                grouped.rows[0].group_key(),
1902                &[crate::value::OutputValue::Nat64(9)]
1903            );
1904            assert_eq!(
1905                grouped.rows[0].aggregate_values(),
1906                &[crate::value::OutputValue::Nat64(1)]
1907            );
1908            assert_eq!(grouped.next_cursor, None);
1909
1910            let grouped_state_error = session
1911                .execute_trusted_dynamic_grouped_query(&grouped_query.clone().grouped_limits(1, 1))
1912                .expect_err("grouped retained state must respect its explicit byte ceiling");
1913            assert!(matches!(
1914                grouped_state_error.diagnostic().detail(),
1915                Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
1916                    boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
1917                })
1918            ));
1919            assert_eq!(
1920                grouped_state_error.diagnostic_facts()[0],
1921                (
1922                    icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
1923                    icydb_diagnostic_code::DiagnosticExecutionBudgetResource::GroupDistinctStateBytes.raw(),
1924                ),
1925            );
1926
1927            assert_query_diagnostic(
1928                session
1929                    .execute_public_dynamic_grouped_query(&grouped_query.clone().select(["value"]))
1930                    .expect_err("grouped output must reject scalar selection"),
1931                icydb_diagnostic_code::DiagnosticCode::QueryIntent,
1932                icydb_diagnostic_code::ErrorOrigin::Query,
1933                icydb_diagnostic_code::DiagnosticDetail::QueryKind {
1934                    kind: icydb_diagnostic_code::QueryErrorKind::Intent,
1935                },
1936            );
1937            assert_query_diagnostic(
1938                session
1939                    .execute_public_dynamic_grouped_query(
1940                        &crate::db::DynamicQuery::new("RenamedEntity")
1941                            .group_by("value")
1942                            .aggregate(crate::db::count()),
1943                    )
1944                    .expect_err("public grouped execution must require explicit limits"),
1945                icydb_diagnostic_code::DiagnosticCode::QueryReadAdmission,
1946                icydb_diagnostic_code::ErrorOrigin::Query,
1947                icydb_diagnostic_code::DiagnosticDetail::QueryReadAdmission {
1948                    reason:
1949                        icydb_diagnostic_code::QueryReadAdmissionCode::GroupedQueryRequiresLimits,
1950                },
1951            );
1952            assert_query_diagnostic(
1953                session
1954                    .execute_trusted_dynamic_grouped_query(
1955                        &crate::db::DynamicQuery::new("RenamedEntity")
1956                            .group_by("value")
1957                            .aggregate(crate::db::count())
1958                            .grouped_limits(0, 1024),
1959                    )
1960                    .expect_err("trusted grouped execution must reject zero limits"),
1961                icydb_diagnostic_code::DiagnosticCode::QueryReadAdmission,
1962                icydb_diagnostic_code::ErrorOrigin::Query,
1963                icydb_diagnostic_code::DiagnosticDetail::QueryReadAdmission {
1964                    reason:
1965                        icydb_diagnostic_code::QueryReadAdmissionCode::GroupedQueryRequiresLimits,
1966                },
1967            );
1968            assert_query_diagnostic(
1969                session
1970                    .execute_public_dynamic_grouped_query(&grouped_query.grouped_limits(101, 1024))
1971                    .expect_err("public grouped execution must enforce its group budget"),
1972                icydb_diagnostic_code::DiagnosticCode::QueryReadAdmission,
1973                icydb_diagnostic_code::ErrorOrigin::Query,
1974                icydb_diagnostic_code::DiagnosticDetail::QueryReadAdmission {
1975                    reason:
1976                        icydb_diagnostic_code::QueryReadAdmissionCode::GroupedQueryExceedsBudget,
1977                },
1978            );
1979
1980            let paged_query = crate::db::DynamicQuery::new("RenamedEntity")
1981                .group_by("value")
1982                .aggregate(crate::db::count())
1983                .grouped_limits(2, 16 * 1024)
1984                .limit(1);
1985            assert_query_diagnostic(
1986                session
1987                    .execute_public_dynamic_grouped_query(&paged_query)
1988                    .expect_err("public grouped execution must reject an unbounded full scan"),
1989                icydb_diagnostic_code::DiagnosticCode::QueryReadAdmission,
1990                icydb_diagnostic_code::ErrorOrigin::Query,
1991                icydb_diagnostic_code::DiagnosticDetail::QueryReadAdmission {
1992                    reason:
1993                        icydb_diagnostic_code::QueryReadAdmissionCode::UnboundedFullScanRejected,
1994                },
1995            );
1996            let first_page = session
1997                .execute_trusted_dynamic_grouped_query(&paged_query)
1998                .expect("SQL-free grouped first page should execute");
1999            assert_eq!(first_page.row_count, 1);
2000            assert_eq!(
2001                first_page.rows[0].group_key(),
2002                &[crate::value::OutputValue::Nat64(9)]
2003            );
2004            let cursor = first_page
2005                .next_cursor
2006                .expect("first grouped page should return a continuation cursor");
2007            assert_query_diagnostic(
2008                session
2009                    .execute_trusted_dynamic_grouped_query(
2010                        &paged_query.clone().cursor(format!("{cursor}0")),
2011                    )
2012                    .expect_err("tampered grouped cursor must fail closed"),
2013                icydb_diagnostic_code::DiagnosticCode::QueryInvalidContinuationCursor,
2014                icydb_diagnostic_code::ErrorOrigin::Cursor,
2015                icydb_diagnostic_code::DiagnosticDetail::QueryKind {
2016                    kind: icydb_diagnostic_code::QueryErrorKind::InvalidContinuationCursor,
2017                },
2018            );
2019            let second_page = session
2020                .execute_trusted_dynamic_grouped_query(&paged_query.cursor(cursor))
2021                .expect("SQL-free grouped continuation should execute");
2022            assert_eq!(second_page.row_count, 1);
2023            assert_eq!(
2024                second_page.rows[0].group_key(),
2025                &[crate::value::OutputValue::Nat64(10)]
2026            );
2027            assert_eq!(second_page.next_cursor, None);
2028        }
2029    }
2030}
2031
2032#[cfg(test)]
2033mod mixed_relation_batch_tests {
2034    use super::{DbSession, DynamicMutation, DynamicStructuralPatch, DynamicWriteCell};
2035    use crate::{
2036        db::{
2037            DynamicQuery, asc,
2038            data::DataStore,
2039            desc,
2040            index::IndexStore,
2041            query::expr::FilterExpr,
2042            registry::{StoreAllocationIdentities, StoreRegistry, StoreRuntimeStorageCapabilities},
2043            schema::{
2044                AcceptedConstraintCatalog, AcceptedFieldKind, AcceptedSchemaRevision, FieldId,
2045                FieldStorageDecode, LeafCodec, PersistedFieldSnapshot,
2046                PersistedIndexFieldPathSnapshot, PersistedIndexKeySnapshot, PersistedIndexSnapshot,
2047                PersistedRelationEdgeSnapshot, PersistedSchemaSnapshot, RelationId, ScalarCodec,
2048                SchemaFieldSlot, SchemaIndexId, SchemaInsertDefault, SchemaRowLayout, SchemaStore,
2049                SchemaVersion, accepted_schema_candidate_with_field_bindings_for_tests,
2050            },
2051        },
2052        error::ErrorClass,
2053        traits::{CanisterKind, Path},
2054        types::EntityTag,
2055        value::{InputValue, OutputValue},
2056    };
2057    use icydb_schema::FieldSourceKey;
2058    use std::{cell::RefCell, collections::BTreeMap};
2059
2060    const STORE_PATH: &str = "session::write::mixed_relation_batch_tests::Store";
2061    const ENTITY_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node";
2062    const ID_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node::id";
2063    const PARENT_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node::parent_id";
2064    const CODE_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node::code";
2065    const ENTITY_NAME: &str = "MixedRelationNode";
2066    const ENTITY_TAG: EntityTag = EntityTag::new(94);
2067    const OTHER_ENTITY_SOURCE: &str = "session::write::mixed_relation_batch_tests::Other";
2068    const OTHER_ID_SOURCE: &str = "session::write::mixed_relation_batch_tests::Other::id";
2069    const OTHER_VALUE_SOURCE: &str = "session::write::mixed_relation_batch_tests::Other::value";
2070    const OTHER_ENTITY_NAME: &str = "MixedRelationOther";
2071    const OTHER_ENTITY_TAG: EntityTag = EntityTag::new(95);
2072
2073    struct TestCanister;
2074
2075    impl Path for TestCanister {
2076        const PATH: &'static str = "session::write::mixed_relation_batch_tests::Canister";
2077    }
2078
2079    impl CanisterKind for TestCanister {
2080        const COMMIT_MEMORY_ID: u8 = 47;
2081        const COMMIT_STABLE_KEY: &'static str = "icydb.mixed_relation_batch_tests.commit.v1";
2082        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 48;
2083        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
2084            "icydb.mixed_relation_batch_tests.integrity.progress.v1";
2085    }
2086
2087    thread_local! {
2088        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
2089        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
2090        static SCHEMA_STORE: RefCell<SchemaStore> =
2091            const { RefCell::new(SchemaStore::init_heap()) };
2092        static STORE_REGISTRY: StoreRegistry = {
2093            let mut registry = StoreRegistry::new();
2094            registry.register_store(
2095                STORE_PATH,
2096                &DATA_STORE,
2097                &INDEX_STORE,
2098                &SCHEMA_STORE,
2099                StoreAllocationIdentities::absent(),
2100                StoreRuntimeStorageCapabilities::heap(),
2101            ).expect("mixed relation test store should register");
2102            registry
2103        };
2104    }
2105
2106    fn source_key(source: &str) -> FieldSourceKey {
2107        FieldSourceKey::try_new(source).expect("mixed relation field source should admit")
2108    }
2109
2110    fn relation_snapshot() -> PersistedSchemaSnapshot {
2111        let fields = vec![
2112            PersistedFieldSnapshot::new_initial(
2113                FieldId::new(1),
2114                "id".to_string(),
2115                SchemaFieldSlot::new(0),
2116                AcceptedFieldKind::Nat64,
2117                Vec::new(),
2118                false,
2119                SchemaInsertDefault::None,
2120                FieldStorageDecode::ByKind,
2121                LeafCodec::Scalar(ScalarCodec::Nat64),
2122            ),
2123            PersistedFieldSnapshot::new_initial(
2124                FieldId::new(2),
2125                "parent_id".to_string(),
2126                SchemaFieldSlot::new(1),
2127                AcceptedFieldKind::Nat64,
2128                Vec::new(),
2129                true,
2130                SchemaInsertDefault::None,
2131                FieldStorageDecode::ByKind,
2132                LeafCodec::Scalar(ScalarCodec::Nat64),
2133            ),
2134            PersistedFieldSnapshot::new_initial(
2135                FieldId::new(3),
2136                "code".to_string(),
2137                SchemaFieldSlot::new(2),
2138                AcceptedFieldKind::Nat64,
2139                Vec::new(),
2140                false,
2141                SchemaInsertDefault::None,
2142                FieldStorageDecode::ByKind,
2143                LeafCodec::Scalar(ScalarCodec::Nat64),
2144            ),
2145        ];
2146        let relation = PersistedRelationEdgeSnapshot::new(
2147            RelationId::new(1).expect("mixed relation identity should be non-zero"),
2148            "parent".to_string(),
2149            ENTITY_SOURCE.to_string(),
2150            vec![FieldId::new(2)],
2151        );
2152        let snapshot = PersistedSchemaSnapshot::new_with_indexes(
2153            SchemaVersion::initial(),
2154            ENTITY_SOURCE.to_string(),
2155            ENTITY_NAME.to_string(),
2156            FieldId::new(1),
2157            SchemaRowLayout::initial(
2158                fields
2159                    .iter()
2160                    .map(|field| (field.id(), field.slot()))
2161                    .collect(),
2162            ),
2163            fields,
2164            vec![PersistedIndexSnapshot::new(
2165                SchemaIndexId::new(1).expect("mixed unique index identity should be non-zero"),
2166                1,
2167                "by_code".to_string(),
2168                STORE_PATH.to_string(),
2169                true,
2170                PersistedIndexKeySnapshot::FieldPath(vec![PersistedIndexFieldPathSnapshot::new(
2171                    FieldId::new(3),
2172                    SchemaFieldSlot::new(2),
2173                    vec!["code".to_string()],
2174                    AcceptedFieldKind::Nat64,
2175                    false,
2176                )]),
2177                None,
2178            )],
2179        )
2180        .with_relations(vec![relation]);
2181        let constraints = AcceptedConstraintCatalog::initial(
2182            snapshot.fields(),
2183            snapshot.indexes(),
2184            snapshot.relations(),
2185        )
2186        .expect("mixed relation constraints should close");
2187        snapshot.with_constraint_catalog(constraints)
2188    }
2189
2190    fn other_snapshot() -> PersistedSchemaSnapshot {
2191        let fields = vec![
2192            PersistedFieldSnapshot::new_initial(
2193                FieldId::new(1),
2194                "id".to_string(),
2195                SchemaFieldSlot::new(0),
2196                AcceptedFieldKind::Nat64,
2197                Vec::new(),
2198                false,
2199                SchemaInsertDefault::None,
2200                FieldStorageDecode::ByKind,
2201                LeafCodec::Scalar(ScalarCodec::Nat64),
2202            ),
2203            PersistedFieldSnapshot::new_initial(
2204                FieldId::new(2),
2205                "value".to_string(),
2206                SchemaFieldSlot::new(1),
2207                AcceptedFieldKind::Nat64,
2208                Vec::new(),
2209                false,
2210                SchemaInsertDefault::None,
2211                FieldStorageDecode::ByKind,
2212                LeafCodec::Scalar(ScalarCodec::Nat64),
2213            ),
2214        ];
2215        PersistedSchemaSnapshot::new(
2216            SchemaVersion::initial(),
2217            OTHER_ENTITY_SOURCE.to_string(),
2218            OTHER_ENTITY_NAME.to_string(),
2219            FieldId::new(1),
2220            SchemaRowLayout::initial(
2221                fields
2222                    .iter()
2223                    .map(|field| (field.id(), field.slot()))
2224                    .collect(),
2225            ),
2226            fields,
2227        )
2228    }
2229
2230    fn initialize() -> DbSession<TestCanister> {
2231        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
2232        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
2233        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
2234        let session = DbSession::<TestCanister>::new(
2235            &STORE_REGISTRY,
2236            &crate::db::RequestExecutionRoot::__new_runtime_root(),
2237        );
2238        session
2239            .db
2240            .ensure_recovered_state()
2241            .expect("mixed relation database should initialize");
2242        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
2243            STORE_PATH,
2244            AcceptedSchemaRevision::INITIAL,
2245            BTreeMap::from([
2246                (ENTITY_TAG, relation_snapshot()),
2247                (OTHER_ENTITY_TAG, other_snapshot()),
2248            ]),
2249            BTreeMap::from([
2250                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
2251                ((ENTITY_TAG, source_key(PARENT_SOURCE)), FieldId::new(2)),
2252                ((ENTITY_TAG, source_key(CODE_SOURCE)), FieldId::new(3)),
2253                (
2254                    (OTHER_ENTITY_TAG, source_key(OTHER_ID_SOURCE)),
2255                    FieldId::new(1),
2256                ),
2257                (
2258                    (OTHER_ENTITY_TAG, source_key(OTHER_VALUE_SOURCE)),
2259                    FieldId::new(2),
2260                ),
2261            ]),
2262        );
2263        let store = session
2264            .db
2265            .store_handle(STORE_PATH)
2266            .expect("mixed relation store should resolve");
2267        crate::db::commit::publish_accepted_schema_candidate(
2268            STORE_PATH,
2269            store,
2270            AcceptedSchemaRevision::NONE,
2271            &candidate,
2272        )
2273        .expect("mixed relation candidate should publish");
2274        session
2275    }
2276
2277    fn patch(id: Option<u64>, parent: Option<u64>, code: Option<u64>) -> DynamicStructuralPatch {
2278        let mut fields = Vec::new();
2279        if let Some(id) = id {
2280            fields.push((
2281                "id".to_string(),
2282                DynamicWriteCell::Value(InputValue::Nat64(id)),
2283            ));
2284        }
2285        fields.push((
2286            "parent_id".to_string(),
2287            parent.map_or(DynamicWriteCell::Null, |parent| {
2288                DynamicWriteCell::Value(InputValue::Nat64(parent))
2289            }),
2290        ));
2291        if let Some(code) = code {
2292            fields.push((
2293                "code".to_string(),
2294                DynamicWriteCell::Value(InputValue::Nat64(code)),
2295            ));
2296        }
2297        DynamicStructuralPatch::new(fields)
2298    }
2299
2300    fn insert(id: u64, parent: Option<u64>) -> DynamicMutation {
2301        insert_with_code(id, parent, id)
2302    }
2303
2304    fn insert_with_code(id: u64, parent: Option<u64>, code: u64) -> DynamicMutation {
2305        DynamicMutation::Insert {
2306            entity: ENTITY_NAME.to_string(),
2307            patch: patch(Some(id), parent, Some(code)),
2308        }
2309    }
2310
2311    fn update_parent(id: u64, parent: Option<u64>) -> DynamicMutation {
2312        DynamicMutation::Update {
2313            entity: ENTITY_NAME.to_string(),
2314            key: InputValue::Nat64(id),
2315            patch: patch(None, parent, None),
2316        }
2317    }
2318
2319    fn update_code(id: u64, code: u64) -> DynamicMutation {
2320        DynamicMutation::Update {
2321            entity: ENTITY_NAME.to_string(),
2322            key: InputValue::Nat64(id),
2323            patch: DynamicStructuralPatch::new(vec![(
2324                "code".to_string(),
2325                DynamicWriteCell::Value(InputValue::Nat64(code)),
2326            )]),
2327        }
2328    }
2329
2330    fn delete(id: u64) -> DynamicMutation {
2331        DynamicMutation::Delete {
2332            entity: ENTITY_NAME.to_string(),
2333            key: InputValue::Nat64(id),
2334        }
2335    }
2336
2337    fn expected_row(id: u64, parent: Option<u64>) -> Vec<OutputValue> {
2338        expected_row_with_code(id, parent, id)
2339    }
2340
2341    fn expected_row_with_code(id: u64, parent: Option<u64>, code: u64) -> Vec<OutputValue> {
2342        vec![
2343            OutputValue::Nat64(id),
2344            parent.map_or(OutputValue::Null, OutputValue::Nat64),
2345            OutputValue::Nat64(code),
2346        ]
2347    }
2348
2349    fn other_patch(id: Option<u64>, value: u64) -> DynamicStructuralPatch {
2350        let mut fields = Vec::new();
2351        if let Some(id) = id {
2352            fields.push((
2353                "id".to_string(),
2354                DynamicWriteCell::Value(InputValue::Nat64(id)),
2355            ));
2356        }
2357        fields.push((
2358            "value".to_string(),
2359            DynamicWriteCell::Value(InputValue::Nat64(value)),
2360        ));
2361        DynamicStructuralPatch::new(fields)
2362    }
2363
2364    fn assert_relation_violation(error: &crate::error::InternalError) {
2365        assert!(error.diagnostic_facts().contains(&(
2366            icydb_diagnostic_code::DiagnosticFactTag::ConstraintKind,
2367            icydb_diagnostic_code::DiagnosticConstraintKind::Relation.raw(),
2368        )));
2369    }
2370
2371    #[test]
2372    fn live_pages_resume_mixed_projection_from_authenticated_hidden_order_values() {
2373        let session = initialize();
2374        session
2375            .execute_trusted_dynamic_mutation_batch(vec![
2376                insert_with_code(1, None, 10),
2377                insert_with_code(2, Some(1), 20),
2378                insert_with_code(3, None, 30),
2379            ])
2380            .expect("live-page rows should insert");
2381        let query = DynamicQuery::new(ENTITY_NAME)
2382            .select(["id"])
2383            .order_by(desc("code"));
2384
2385        let first = session
2386            .execute_public_live_page(&query, None)
2387            .expect("initial live page should execute");
2388        assert_eq!(
2389            first.rows,
2390            vec![vec![OutputValue::Nat64(3)], vec![OutputValue::Nat64(2)]]
2391        );
2392        let cursor = first
2393            .continuation
2394            .as_deref()
2395            .expect("unreturned matching row should produce continuation");
2396        let second = session
2397            .execute_public_live_page(&query, Some(cursor))
2398            .expect("authenticated live continuation should resume");
2399        assert_eq!(second.rows, vec![vec![OutputValue::Nat64(1)]]);
2400        assert_eq!(second.continuation, None);
2401
2402        let total_limit = session
2403            .execute_public_live_page(&query.clone().limit(2), None)
2404            .expect("total live-page limit should execute");
2405        assert_eq!(
2406            total_limit.rows,
2407            vec![vec![OutputValue::Nat64(3)], vec![OutputValue::Nat64(2)]],
2408        );
2409        assert_eq!(
2410            total_limit.continuation, None,
2411            "query LIMIT is a total traversal window rather than a page size",
2412        );
2413
2414        let three_row_window = query.clone().limit(3);
2415        let limited_first = session
2416            .execute_public_live_page(&three_row_window, None)
2417            .expect("first total-window page should execute");
2418        let limited_cursor = limited_first
2419            .continuation
2420            .as_deref()
2421            .expect("a partially consumed total window should continue");
2422        let limited_second = session
2423            .execute_public_live_page(&three_row_window, Some(limited_cursor))
2424            .expect("remaining total window should preserve the plan signature");
2425        assert_eq!(limited_second.rows, vec![vec![OutputValue::Nat64(1)]]);
2426        assert_eq!(limited_second.continuation, None);
2427
2428        let mixed_order = DynamicQuery::new(ENTITY_NAME)
2429            .select(["id"])
2430            .order_by(desc("parent_id"))
2431            .order_by(asc("id"));
2432        let mixed_first = session
2433            .execute_trusted_live_page(&mixed_order, None)
2434            .expect("mixed-direction nullable order should execute");
2435        assert_eq!(
2436            mixed_first.rows,
2437            vec![vec![OutputValue::Nat64(2)], vec![OutputValue::Nat64(1)]],
2438        );
2439        let mixed_cursor = mixed_first
2440            .continuation
2441            .as_deref()
2442            .expect("duplicate null order values should retain continuation");
2443        let mixed_second = session
2444            .execute_trusted_live_page(&mixed_order, Some(mixed_cursor))
2445            .expect("mixed-direction nullable order should resume");
2446        assert_eq!(mixed_second.rows, vec![vec![OutputValue::Nat64(3)]]);
2447        assert_eq!(mixed_second.continuation, None);
2448
2449        let mismatched_window = session
2450            .execute_public_live_page(&query.clone().limit(3), Some(cursor))
2451            .expect_err("a changed total limit must invalidate the continuation");
2452        assert_eq!(
2453            mismatched_window.diagnostic_code(),
2454            icydb_diagnostic_code::DiagnosticCode::QueryInvalidContinuationCursor,
2455        );
2456
2457        let mut tampered = cursor.as_bytes().to_vec();
2458        let last = tampered.len().saturating_sub(1);
2459        tampered[last] = if tampered[last] == b'0' { b'1' } else { b'0' };
2460        let tampered = String::from_utf8(tampered).expect("hex cursor should remain UTF-8");
2461        let error = session
2462            .execute_public_live_page(&query, Some(tampered.as_str()))
2463            .expect_err("tampered cursor must fail closed");
2464        assert_eq!(
2465            error.diagnostic_code(),
2466            icydb_diagnostic_code::DiagnosticCode::QueryInvalidContinuationCursor,
2467        );
2468    }
2469
2470    #[test]
2471    fn live_pages_resume_across_changed_output_work_envelopes() {
2472        let session = initialize();
2473        session
2474            .execute_trusted_dynamic_mutation_batch(vec![
2475                insert(1, None),
2476                insert(2, None),
2477                insert(3, None),
2478            ])
2479            .expect("output-envelope rows should insert");
2480        let query = DynamicQuery::new(ENTITY_NAME)
2481            .select(["id"])
2482            .order_by(desc("code"));
2483        let first = session
2484            .execute_trusted_live_page_with_result_bytes_limit_for_tests(&query, None, 32)
2485            .expect("small output envelope should publish the first bounded page");
2486        assert_eq!(first.rows, vec![vec![OutputValue::Nat64(3)]]);
2487        let continuation = first
2488            .continuation
2489            .expect("small output envelope should leave authenticated progress");
2490
2491        let second = session
2492            .execute_trusted_live_page_with_result_bytes_limit_for_tests(
2493                &query,
2494                Some(continuation.as_str()),
2495                64,
2496            )
2497            .unwrap_or_else(|error| {
2498                panic!(
2499                    "larger output envelope should resume the same query: {error:?}, facts={:?}",
2500                    error.diagnostic_facts(),
2501                )
2502            });
2503        assert_eq!(
2504            second.rows,
2505            vec![vec![OutputValue::Nat64(2)], vec![OutputValue::Nat64(1)]]
2506        );
2507        let second_continuation = second
2508            .continuation
2509            .as_deref()
2510            .expect("an exact-full page still needs to prove physical exhaustion");
2511        assert_ne!(first.work.envelope_identity, second.work.envelope_identity);
2512
2513        let terminal = session
2514            .execute_trusted_live_page_with_result_bytes_limit_for_tests(
2515                &query,
2516                Some(second_continuation),
2517                48,
2518            )
2519            .expect("a third finite envelope should prove exhaustion without replaying rows");
2520        assert!(terminal.rows.is_empty());
2521        assert_eq!(terminal.continuation, None);
2522        assert_ne!(
2523            second.work.envelope_identity,
2524            terminal.work.envelope_identity
2525        );
2526
2527        assert_eq!(
2528            [first.rows, second.rows, terminal.rows].concat(),
2529            vec![
2530                vec![OutputValue::Nat64(3)],
2531                vec![OutputValue::Nat64(2)],
2532                vec![OutputValue::Nat64(1)],
2533            ]
2534        );
2535    }
2536
2537    #[test]
2538    fn distinct_live_pages_resume_adjacent_groups_and_global_replay_end_to_end() {
2539        let session = initialize();
2540        session
2541            .execute_trusted_dynamic_mutation_batch(vec![
2542                insert(1, None),
2543                insert(2, None),
2544                insert(3, Some(1)),
2545                insert(4, Some(2)),
2546                insert(5, Some(1)),
2547                insert(6, Some(3)),
2548                insert(7, Some(2)),
2549            ])
2550            .expect("DISTINCT continuation rows should insert atomically");
2551
2552        let adjacent = DynamicQuery::new(ENTITY_NAME)
2553            .select(["parent_id"])
2554            .order_by(asc("parent_id"))
2555            .order_by(asc("id"))
2556            .distinct_for_internal_execution();
2557        let global = DynamicQuery::new(ENTITY_NAME)
2558            .select(["parent_id"])
2559            .order_by(asc("id"))
2560            .distinct_for_internal_execution();
2561
2562        let traverse = |query: &DynamicQuery, strategy: &str| {
2563            let mut continuation = None;
2564            let mut rows = Vec::new();
2565            let mut cursors = std::collections::BTreeSet::new();
2566            let mut pages = 0_u32;
2567            let mut entries_visited = 0_u64;
2568            loop {
2569                let page = session
2570                    .execute_trusted_live_page(query, continuation.as_deref())
2571                    .unwrap_or_else(|error| {
2572                        panic!("{strategy} DISTINCT page should execute: {error:?}")
2573                    });
2574                pages = pages.saturating_add(1);
2575                entries_visited = entries_visited.saturating_add(page.work.entries_visited);
2576                assert_eq!(page.row_count as usize, page.rows.len());
2577                assert_eq!(page.work.result_rows, page.row_count);
2578                rows.extend(page.rows);
2579                let Some(cursor) = page.continuation else {
2580                    break;
2581                };
2582                assert!(
2583                    cursors.insert(cursor.clone()),
2584                    "{strategy} DISTINCT continuation must advance monotonically",
2585                );
2586                continuation = Some(cursor);
2587                assert!(pages < 8, "{strategy} DISTINCT traversal must terminate");
2588            }
2589
2590            (rows, pages, entries_visited)
2591        };
2592
2593        let expected = vec![
2594            vec![OutputValue::Null],
2595            vec![OutputValue::Nat64(1)],
2596            vec![OutputValue::Nat64(2)],
2597            vec![OutputValue::Nat64(3)],
2598        ];
2599        let (adjacent_rows, adjacent_pages, adjacent_entries) = traverse(&adjacent, "adjacent");
2600        let (global_rows, global_pages, global_entries) = traverse(&global, "global");
2601
2602        assert_eq!(adjacent_rows, expected);
2603        assert_eq!(global_rows, expected);
2604        assert_eq!(adjacent_pages, 2);
2605        assert_eq!(global_pages, 2);
2606        assert!(adjacent_entries > 0);
2607        assert!(global_entries > 0);
2608    }
2609
2610    #[test]
2611    fn selective_live_pages_publish_monotonic_empty_physical_progress() {
2612        let session = initialize();
2613        session
2614            .execute_trusted_dynamic_mutation_batch(
2615                (1..=9)
2616                    .map(|id| {
2617                        let parent = match id {
2618                            1 => Some(2),
2619                            9 => Some(1),
2620                            _ => None,
2621                        };
2622                        insert(id, parent)
2623                    })
2624                    .collect(),
2625            )
2626            .expect("selective live-page rows should insert");
2627        let query = DynamicQuery::new(ENTITY_NAME)
2628            .select(["id"])
2629            .filter(FilterExpr::eq("parent_id", 1_u64))
2630            .order_by(asc("id"))
2631            .limit(1);
2632
2633        let first = session
2634            .execute_trusted_live_page(&query, None)
2635            .expect("first selective page should stop with physical progress");
2636        assert!(first.rows.is_empty());
2637        assert_eq!(first.work.entries_visited, 4);
2638        let first_cursor = first
2639            .continuation
2640            .expect("filtered physical progress must return a continuation");
2641
2642        let second = session
2643            .execute_trusted_live_page(&query, Some(first_cursor.as_str()))
2644            .expect("second selective page should resume after the first physical frontier");
2645        assert!(second.rows.is_empty());
2646        assert_eq!(second.work.entries_visited, 4);
2647        let second_cursor = second
2648            .continuation
2649            .expect("second filtered frontier must remain resumable");
2650        assert_ne!(second_cursor, first_cursor);
2651
2652        let third = session
2653            .execute_trusted_live_page(&query, Some(second_cursor.as_str()))
2654            .expect("final selective page should return the late match");
2655        assert_eq!(third.rows, vec![vec![OutputValue::Nat64(9)]]);
2656        assert_eq!(third.work.entries_visited, 1);
2657        assert_eq!(third.continuation, None);
2658
2659        let descending = DynamicQuery::new(ENTITY_NAME)
2660            .select(["id"])
2661            .filter(FilterExpr::eq("parent_id", 2_u64))
2662            .order_by(desc("id"))
2663            .limit(1);
2664        let descending_first = session
2665            .execute_trusted_live_page(&descending, None)
2666            .expect("descending selective page should stop with physical progress");
2667        assert!(descending_first.rows.is_empty());
2668        let descending_first_cursor = descending_first
2669            .continuation
2670            .expect("descending filtered progress must return a continuation");
2671        let descending_second = session
2672            .execute_trusted_live_page(&descending, Some(descending_first_cursor.as_str()))
2673            .expect("descending progress should resume after its physical frontier");
2674        assert!(descending_second.rows.is_empty());
2675        let descending_second_cursor = descending_second
2676            .continuation
2677            .expect("descending second frontier must remain resumable");
2678        assert_ne!(descending_second_cursor, descending_first_cursor);
2679        let descending_third = session
2680            .execute_trusted_live_page(&descending, Some(descending_second_cursor.as_str()))
2681            .expect("descending final page should return the late match");
2682        assert_eq!(descending_third.rows, vec![vec![OutputValue::Nat64(1)]]);
2683        assert_eq!(descending_third.continuation, None);
2684    }
2685
2686    #[test]
2687    fn accepted_relation_edges_drive_catalog_and_describe_introspection() {
2688        let session = initialize();
2689        let entities = session
2690            .show_entities()
2691            .expect("accepted entity catalog should resolve");
2692        let source = entities
2693            .iter()
2694            .find(|entity| entity.entity_name() == ENTITY_NAME)
2695            .expect("relation source should be listed");
2696        assert_eq!(source.relations(), 1);
2697
2698        let description = session
2699            .try_describe_entity_by_name(ENTITY_NAME)
2700            .expect("accepted relation source should describe");
2701        let [relation] = description.relations() else {
2702            panic!("accepted relation edge should produce one relation row");
2703        };
2704        assert_eq!(relation.field(), "parent_id");
2705        assert_eq!(relation.target_path(), ENTITY_SOURCE);
2706        assert_eq!(relation.target_entity_name(), ENTITY_NAME);
2707        assert_eq!(relation.target_store_path(), STORE_PATH);
2708        assert_eq!(
2709            relation.cardinality(),
2710            crate::db::EntityRelationCardinality::Single,
2711        );
2712    }
2713
2714    #[test]
2715    fn mixed_relation_validation_uses_the_complete_final_row_overlay() {
2716        let session = initialize();
2717        session
2718            .execute_trusted_dynamic_mutation_batch(vec![insert(1, None), insert(2, Some(1))])
2719            .expect("the initial relation should commit");
2720
2721        let blocked = session
2722            .execute_trusted_dynamic_mutation(&delete(1))
2723            .expect_err("an unaffected committed source must block target deletion");
2724        assert_relation_violation(&blocked);
2725
2726        let deleted = session
2727            .execute_trusted_dynamic_mutation_batch(vec![delete(2), delete(1)])
2728            .expect("a source and its target should delete atomically");
2729        assert_eq!(
2730            deleted.rows,
2731            vec![expected_row(2, Some(1)), expected_row(1, None)],
2732        );
2733
2734        session
2735            .execute_trusted_dynamic_mutation_batch(vec![insert(3, None), insert(4, Some(3))])
2736            .expect("the update-away fixture should commit");
2737        let updated_away = session
2738            .execute_trusted_dynamic_mutation_batch(vec![update_parent(4, None), delete(3)])
2739            .expect("an updated final source may release a deleted target");
2740        assert_eq!(
2741            updated_away.rows,
2742            vec![expected_row(4, None), expected_row(3, None)],
2743        );
2744
2745        session
2746            .execute_trusted_dynamic_mutation_batch(vec![insert(5, None), insert(6, Some(5))])
2747            .expect("the retained-reference fixture should commit");
2748        let retained = session
2749            .execute_trusted_dynamic_mutation_batch(vec![update_parent(6, Some(5)), delete(5)])
2750            .expect_err("a final updated source must still block target deletion");
2751        assert_relation_violation(&retained);
2752
2753        session
2754            .execute_trusted_dynamic_mutation(&insert(7, None))
2755            .expect("the inserted-reference fixture target should commit");
2756        let inserted_reference = session
2757            .execute_trusted_dynamic_mutation_batch(vec![insert(8, Some(7)), delete(7)])
2758            .expect_err("a final inserted source must not reference a deleted target");
2759        assert_relation_violation(&inserted_reference);
2760
2761        let inserted_target = session
2762            .execute_trusted_dynamic_mutation_batch(vec![insert(10, Some(9)), insert(9, None)])
2763            .expect("an inserted relation should see its batch-final target");
2764        assert_eq!(
2765            inserted_target.rows,
2766            vec![expected_row(10, Some(9)), expected_row(9, None)],
2767        );
2768
2769        session
2770            .execute_trusted_dynamic_mutation(&insert(11, None))
2771            .expect("the updated-reference fixture source should commit");
2772        let updated_target = session
2773            .execute_trusted_dynamic_mutation_batch(vec![
2774                update_parent(11, Some(12)),
2775                insert(12, None),
2776            ])
2777            .expect("an updated relation should see its batch-final target");
2778        assert_eq!(
2779            updated_target.rows,
2780            vec![expected_row(11, Some(12)), expected_row(12, None)],
2781        );
2782    }
2783
2784    #[test]
2785    fn mixed_batch_rejects_cross_entity_missing_and_collision_then_honors_replace() {
2786        let session = initialize();
2787        session
2788            .execute_trusted_dynamic_mutation(&insert(1, None))
2789            .expect("the primary mixed fixture row should commit");
2790        session
2791            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
2792                entity: OTHER_ENTITY_NAME.to_string(),
2793                patch: other_patch(Some(1), 10),
2794            })
2795            .expect("the secondary mixed fixture row should commit");
2796
2797        let mixed_entity = session
2798            .execute_trusted_dynamic_mutation_batch(vec![
2799                update_code(1, 11),
2800                DynamicMutation::Update {
2801                    entity: OTHER_ENTITY_NAME.to_string(),
2802                    key: InputValue::Nat64(1),
2803                    patch: other_patch(None, 11),
2804                },
2805            ])
2806            .expect_err("one atomic batch must not cross accepted entities");
2807        assert_eq!(mixed_entity.class(), ErrorClass::Conflict);
2808        assert_eq!(
2809            mixed_entity.diagnostic_facts(),
2810            vec![
2811                (icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 1,),
2812                (
2813                    icydb_diagnostic_code::DiagnosticFactTag::ExpectedEntityTag,
2814                    ENTITY_TAG.value(),
2815                ),
2816                (
2817                    icydb_diagnostic_code::DiagnosticFactTag::ActualEntityTag,
2818                    OTHER_ENTITY_TAG.value(),
2819                ),
2820            ],
2821        );
2822
2823        let missing = session
2824            .execute_trusted_dynamic_mutation_batch(vec![update_code(1, 12), delete(99)])
2825            .expect_err("a late missing delete must reject the earlier staged update");
2826        assert_eq!(missing.class(), ErrorClass::NotFound);
2827
2828        session
2829            .execute_trusted_dynamic_mutation(&insert(2, None))
2830            .expect("the collision fixture should commit");
2831        let collision = session
2832            .execute_trusted_dynamic_mutation_batch(vec![update_code(1, 13), insert(2, None)])
2833            .expect_err("an insert collision must reject the earlier staged update");
2834        assert_eq!(collision.class(), ErrorClass::Conflict);
2835        let failures_unchanged = session
2836            .execute_trusted_dynamic_mutation(&update_code(1, 1))
2837            .expect("failed batches must preserve the original unique value");
2838        assert_eq!(failures_unchanged.affected_rows, 0);
2839
2840        let replaced = session
2841            .execute_trusted_dynamic_mutation_batch(vec![
2842                update_code(1, 14),
2843                DynamicMutation::Replace {
2844                    entity: ENTITY_NAME.to_string(),
2845                    key: InputValue::Nat64(99),
2846                    patch: patch(None, None, Some(99)),
2847                },
2848            ])
2849            .expect("ordinary caller-key replace should insert its absent final row");
2850        assert_eq!(
2851            replaced.rows,
2852            vec![
2853                expected_row_with_code(1, None, 14),
2854                expected_row_with_code(99, None, 99),
2855            ],
2856        );
2857
2858        let unchanged = session
2859            .execute_trusted_dynamic_mutation(&update_code(1, 14))
2860            .expect("the successful mixed replace must publish its preceding update");
2861        assert_eq!(unchanged.affected_rows, 0);
2862        let other_unchanged = session
2863            .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
2864                entity: OTHER_ENTITY_NAME.to_string(),
2865                key: InputValue::Nat64(1),
2866                patch: other_patch(None, 10),
2867            })
2868            .expect("cross-entity rejection must preserve the secondary row");
2869        assert_eq!(other_unchanged.affected_rows, 0);
2870    }
2871
2872    #[test]
2873    fn mixed_batch_unique_swap_and_delete_release_use_the_final_overlay() {
2874        let session = initialize();
2875        session
2876            .execute_trusted_dynamic_mutation_batch(vec![
2877                insert_with_code(1, None, 10),
2878                insert_with_code(2, None, 20),
2879            ])
2880            .expect("the unique-overlay fixture should commit");
2881
2882        let swapped = session
2883            .execute_trusted_dynamic_mutation_batch(vec![update_code(1, 20), update_code(2, 10)])
2884            .expect("two final rows should atomically swap unique memberships");
2885        assert_eq!(
2886            swapped.rows,
2887            vec![
2888                expected_row_with_code(1, None, 20),
2889                expected_row_with_code(2, None, 10),
2890            ],
2891        );
2892
2893        let released = session
2894            .execute_trusted_dynamic_mutation_batch(vec![delete(1), insert_with_code(3, None, 20)])
2895            .expect("a delete should release unique membership to a final inserted row");
2896        assert_eq!(
2897            released.rows,
2898            vec![
2899                expected_row_with_code(1, None, 20),
2900                expected_row_with_code(3, None, 20),
2901            ],
2902        );
2903    }
2904}
2905
2906#[cfg(test)]
2907mod identity_pre_key_tests {
2908    #[cfg(all(feature = "sql", feature = "diagnostics"))]
2909    use super::DynamicTypedEntityBinding;
2910    use super::{
2911        AcceptedMutationIntentPatch, AcceptedRowLayoutRuntimeContract, AcceptedStructuralMutation,
2912        AcceptedStructuralMutationTarget, DbSession, DynamicMutation, DynamicStructuralPatch,
2913        DynamicTypedFieldBindingRequest, DynamicTypedFieldType, DynamicTypedMutation,
2914        DynamicWriteCell, FieldSlot, MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
2915        MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES, MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES,
2916        add_structural_mutation_staged_bytes, checked_pre_key_candidate_count,
2917        insert_key_exists_after_generation, validate_structural_mutation_result_bytes,
2918    };
2919    #[cfg(all(feature = "sql", feature = "diagnostics"))]
2920    use crate::db::data::DecodedDataStoreKey;
2921    #[cfg(all(feature = "sql", feature = "diagnostics"))]
2922    use crate::db::executor::budget::{
2923        HardExecutionBudget, HardExecutionContext, HardExecutionFailureHeadroom,
2924        with_query_execution_budget_for_tests,
2925    };
2926    #[cfg(all(feature = "sql", feature = "diagnostics"))]
2927    use crate::db::{
2928        CompareProofAndAdvanceError, DynamicQuery, ExhaustiveReadError, RawDataStoreKey,
2929        ReadSetRevisionError, ResumableJobAdvance, ResumableJobAdvanceRequest,
2930        ResumableJobAdvanceStatus, ResumableJobError, ResumableJobId, ResumableJobIdempotencyKey,
2931        ResumableJobStatus, asc,
2932    };
2933    use crate::{
2934        db::{
2935            commit::{database_incarnation_id, forget_recovered_domain_for_tests},
2936            data::DataStore,
2937            executor::{MutationCommitInterruption, interrupt_next_mutation_commit_for_tests},
2938            index::IndexStore,
2939            integrity::{
2940                PhysicalUnitCheckpoint, QuickIntegrityStatus, RowInspectionLimits,
2941                execute_quick_integrity, execute_row_integrity_page,
2942            },
2943            journal::JournalTailStore,
2944            registry::{
2945                StoreAllocationIdentities, StoreAllocationIdentity, StoreRegistry,
2946                StoreRuntimeStorageCapabilities,
2947            },
2948            schema::{
2949                AcceptedFieldKind, AcceptedSchemaRevision, FieldId, FieldInsertGeneration,
2950                FieldStorageDecode, LeafCodec, PersistedFieldSnapshot,
2951                PersistedIndexFieldPathSnapshot, PersistedIndexKeySnapshot, PersistedIndexSnapshot,
2952                PersistedSchemaSnapshot, ScalarCodec, SchemaFieldSlot, SchemaFieldWritePolicy,
2953                SchemaIndexId, SchemaInsertDefault, SchemaRowLayout, SchemaStore, SchemaVersion,
2954                accepted_schema_candidate_with_field_bindings_for_tests,
2955            },
2956            write_context::MutationMode,
2957        },
2958        error::{ErrorClass, ErrorOrigin, InternalError},
2959        testing::test_memory,
2960        traits::{CanisterKind, Path},
2961        types::{EntityTag, Timestamp},
2962        value::{InputValue, OutputValue, Value},
2963    };
2964    use icydb_schema::{FieldSourceKey, ScalarType};
2965    #[cfg(all(feature = "sql", feature = "diagnostics"))]
2966    use std::cell::Cell;
2967    use std::{cell::RefCell, collections::BTreeMap, time::Instant};
2968
2969    const STORE_PATH: &str = "session::write::identity_pre_key_tests::Store";
2970    const ENTITY_SOURCE: &str = "session::write::identity_pre_key_tests::Entity";
2971    const ID_SOURCE: &str = "session::write::identity_pre_key_tests::Entity::id";
2972    const PAYLOAD_SOURCE: &str = "session::write::identity_pre_key_tests::Entity::payload";
2973    const ENTITY_NAME: &str = "IdentityRow";
2974    const ENTITY_TAG: EntityTag = EntityTag::new(93);
2975    const JOURNALED_STORE_PATH: &str = "session::write::identity_pre_key_tests::JournaledStore";
2976    const UNRELATED_STORE_PATH: &str = "session::write::identity_pre_key_tests::UnrelatedStore";
2977
2978    struct TestCanister;
2979
2980    impl Path for TestCanister {
2981        const PATH: &'static str = "session::write::identity_pre_key_tests::Canister";
2982    }
2983
2984    impl CanisterKind for TestCanister {
2985        const COMMIT_MEMORY_ID: u8 = 45;
2986        const COMMIT_STABLE_KEY: &'static str = "icydb.identity_pre_key_tests.commit.v1";
2987        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 46;
2988        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
2989            "icydb.identity_pre_key_tests.integrity.progress.v1";
2990    }
2991
2992    thread_local! {
2993        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
2994        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
2995        static SCHEMA_STORE: RefCell<SchemaStore> =
2996            const { RefCell::new(SchemaStore::init_heap()) };
2997        static UNRELATED_DATA_STORE: RefCell<DataStore> =
2998            const { RefCell::new(DataStore::init_heap()) };
2999        static UNRELATED_INDEX_STORE: RefCell<IndexStore> =
3000            const { RefCell::new(IndexStore::init_heap()) };
3001        static UNRELATED_SCHEMA_STORE: RefCell<SchemaStore> =
3002            const { RefCell::new(SchemaStore::init_heap()) };
3003        static STORE_REGISTRY: StoreRegistry = {
3004            let mut registry = StoreRegistry::new();
3005            registry.register_store(
3006                STORE_PATH,
3007                &DATA_STORE,
3008                &INDEX_STORE,
3009                &SCHEMA_STORE,
3010                StoreAllocationIdentities::absent(),
3011                StoreRuntimeStorageCapabilities::heap(),
3012            ).expect("identity pre-key test store should register");
3013            registry.register_store(
3014                UNRELATED_STORE_PATH,
3015                &UNRELATED_DATA_STORE,
3016                &UNRELATED_INDEX_STORE,
3017                &UNRELATED_SCHEMA_STORE,
3018                StoreAllocationIdentities::absent(),
3019                StoreRuntimeStorageCapabilities::heap(),
3020            ).expect("unrelated identity test store should register");
3021            registry
3022        };
3023        static JOURNALED_DATA_STORE: RefCell<DataStore> =
3024            RefCell::new(DataStore::init_journaled(test_memory(186)));
3025        static JOURNALED_INDEX_STORE: RefCell<IndexStore> =
3026            RefCell::new(IndexStore::init_journaled(test_memory(187)));
3027        static JOURNALED_SCHEMA_STORE: RefCell<SchemaStore> =
3028            RefCell::new(SchemaStore::init_journaled(test_memory(188)));
3029        static JOURNALED_TAIL_STORE: RefCell<JournalTailStore> =
3030            RefCell::new(JournalTailStore::init(test_memory(189)));
3031        static JOURNALED_STORE_REGISTRY: StoreRegistry = {
3032            let mut registry = StoreRegistry::new();
3033            registry.register_journaled_store(
3034                JOURNALED_STORE_PATH,
3035                &JOURNALED_DATA_STORE,
3036                &JOURNALED_INDEX_STORE,
3037                &JOURNALED_SCHEMA_STORE,
3038                &JOURNALED_TAIL_STORE,
3039                StoreAllocationIdentities::new_journaled(
3040                    StoreAllocationIdentity::new(186, "icydb.test.identity-range.data.v1"),
3041                    StoreAllocationIdentity::new(187, "icydb.test.identity-range.index.v1"),
3042                    StoreAllocationIdentity::new(188, "icydb.test.identity-range.schema.v1"),
3043                    StoreAllocationIdentity::new(189, "icydb.test.identity-range.journal.v1"),
3044                ),
3045                StoreRuntimeStorageCapabilities::journaled(),
3046            ).expect("identity range journaled store should register");
3047            registry
3048        };
3049    }
3050
3051    struct JournaledTestCanister;
3052
3053    impl Path for JournaledTestCanister {
3054        const PATH: &'static str = "session::write::identity_pre_key_tests::JournaledCanister";
3055    }
3056
3057    impl CanisterKind for JournaledTestCanister {
3058        const COMMIT_MEMORY_ID: u8 = 190;
3059        const COMMIT_STABLE_KEY: &'static str = "icydb.identity_range_tests.commit.v1";
3060        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 191;
3061        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
3062            "icydb.identity_range_tests.integrity.progress.v1";
3063    }
3064
3065    fn source_key(source: &str) -> FieldSourceKey {
3066        FieldSourceKey::try_new(source).expect("identity test field source should admit")
3067    }
3068
3069    fn identity_snapshot(store_path: &str) -> PersistedSchemaSnapshot {
3070        let fields = vec![
3071            PersistedFieldSnapshot::new_initial_with_write_policy(
3072                FieldId::new(1),
3073                "id".to_string(),
3074                SchemaFieldSlot::new(0),
3075                AcceptedFieldKind::Nat64,
3076                Vec::new(),
3077                false,
3078                SchemaInsertDefault::None,
3079                SchemaFieldWritePolicy::from_model_policies(
3080                    Some(FieldInsertGeneration::Identity),
3081                    None,
3082                ),
3083                FieldStorageDecode::ByKind,
3084                LeafCodec::Scalar(ScalarCodec::Nat64),
3085            ),
3086            PersistedFieldSnapshot::new_initial(
3087                FieldId::new(2),
3088                "payload".to_string(),
3089                SchemaFieldSlot::new(1),
3090                AcceptedFieldKind::Nat64,
3091                Vec::new(),
3092                false,
3093                SchemaInsertDefault::None,
3094                FieldStorageDecode::ByKind,
3095                LeafCodec::Scalar(ScalarCodec::Nat64),
3096            ),
3097        ];
3098        PersistedSchemaSnapshot::new_with_indexes(
3099            SchemaVersion::initial(),
3100            ENTITY_SOURCE.to_string(),
3101            ENTITY_NAME.to_string(),
3102            FieldId::new(1),
3103            SchemaRowLayout::initial(
3104                fields
3105                    .iter()
3106                    .map(|field| (field.id(), field.slot()))
3107                    .collect(),
3108            ),
3109            fields,
3110            vec![PersistedIndexSnapshot::new(
3111                SchemaIndexId::new(1).expect("identity test index ID should admit"),
3112                1,
3113                "by_payload".to_string(),
3114                store_path.to_string(),
3115                false,
3116                PersistedIndexKeySnapshot::FieldPath(vec![PersistedIndexFieldPathSnapshot::new(
3117                    FieldId::new(2),
3118                    SchemaFieldSlot::new(1),
3119                    vec!["payload".to_string()],
3120                    AcceptedFieldKind::Nat64,
3121                    false,
3122                )]),
3123                None,
3124            )],
3125        )
3126    }
3127
3128    fn initialize() -> DbSession<TestCanister> {
3129        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
3130        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
3131        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
3132        UNRELATED_DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
3133        UNRELATED_INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
3134        UNRELATED_SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
3135        let session = DbSession::<TestCanister>::new(
3136            &STORE_REGISTRY,
3137            &crate::db::RequestExecutionRoot::__new_runtime_root(),
3138        );
3139        session
3140            .db
3141            .ensure_recovered_state()
3142            .expect("identity pre-key test database should initialize");
3143        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
3144            STORE_PATH,
3145            AcceptedSchemaRevision::INITIAL,
3146            BTreeMap::from([(ENTITY_TAG, identity_snapshot(STORE_PATH))]),
3147            BTreeMap::from([
3148                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
3149                ((ENTITY_TAG, source_key(PAYLOAD_SOURCE)), FieldId::new(2)),
3150            ]),
3151        );
3152        let store = session
3153            .db
3154            .store_handle(STORE_PATH)
3155            .expect("identity pre-key test store should resolve");
3156        crate::db::commit::publish_accepted_schema_candidate(
3157            STORE_PATH,
3158            store,
3159            AcceptedSchemaRevision::NONE,
3160            &candidate,
3161        )
3162        .expect("identity candidate should publish with explicit zero state");
3163        session
3164    }
3165
3166    fn initialize_journaled_with_root() -> (
3167        DbSession<JournaledTestCanister>,
3168        crate::db::RequestExecutionRoot,
3169    ) {
3170        let root = crate::db::RequestExecutionRoot::__new_runtime_root();
3171        let session = DbSession::<JournaledTestCanister>::new(&JOURNALED_STORE_REGISTRY, &root);
3172        session
3173            .db
3174            .ensure_recovered_state()
3175            .expect("journaled identity database should initialize");
3176        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
3177            JOURNALED_STORE_PATH,
3178            AcceptedSchemaRevision::INITIAL,
3179            BTreeMap::from([(ENTITY_TAG, identity_snapshot(JOURNALED_STORE_PATH))]),
3180            BTreeMap::from([
3181                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
3182                ((ENTITY_TAG, source_key(PAYLOAD_SOURCE)), FieldId::new(2)),
3183            ]),
3184        );
3185        let store = session
3186            .db
3187            .store_handle(JOURNALED_STORE_PATH)
3188            .expect("journaled identity store should resolve");
3189        crate::db::commit::publish_accepted_schema_candidate(
3190            JOURNALED_STORE_PATH,
3191            store,
3192            AcceptedSchemaRevision::NONE,
3193            &candidate,
3194        )
3195        .expect("journaled identity candidate should publish");
3196        (session, root)
3197    }
3198
3199    fn initialize_journaled() -> DbSession<JournaledTestCanister> {
3200        initialize_journaled_with_root().0
3201    }
3202
3203    fn payload_patch(value: u64) -> AcceptedMutationIntentPatch {
3204        AcceptedMutationIntentPatch::new()
3205            .set_authored(FieldSlot::from_validated_index(1), InputValue::Nat64(value))
3206    }
3207
3208    fn dynamic_payload_patch(value: u64) -> DynamicStructuralPatch {
3209        DynamicStructuralPatch::new(vec![(
3210            "payload".to_string(),
3211            DynamicWriteCell::Value(InputValue::Nat64(value)),
3212        )])
3213    }
3214
3215    fn expected_dynamic_row(id: u64, payload: u64) -> Vec<OutputValue> {
3216        vec![OutputValue::Nat64(id), OutputValue::Nat64(payload)]
3217    }
3218
3219    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3220    fn exact_key_binding<C: CanisterKind>(session: &DbSession<C>) -> DynamicTypedEntityBinding {
3221        session
3222            .issue_typed_entity_binding(
3223                ENTITY_SOURCE,
3224                &[
3225                    DynamicTypedFieldBindingRequest::new(
3226                        ID_SOURCE.to_string(),
3227                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
3228                        false,
3229                    ),
3230                    DynamicTypedFieldBindingRequest::new(
3231                        PAYLOAD_SOURCE.to_string(),
3232                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
3233                        false,
3234                    ),
3235                ],
3236            )
3237            .expect("exact-key test binding should issue")
3238    }
3239
3240    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3241    fn insert_exact_key_fixture<C: CanisterKind>(session: &DbSession<C>, payload: u64) -> u64 {
3242        let output = session
3243            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
3244                entity: ENTITY_NAME.to_string(),
3245                patch: dynamic_payload_patch(payload),
3246            })
3247            .expect("exact-key fixture insert should commit");
3248        match output.rows.as_slice() {
3249            [row] => match row.as_slice() {
3250                [OutputValue::Nat64(id), OutputValue::Nat64(actual_payload)]
3251                    if *actual_payload == payload =>
3252                {
3253                    *id
3254                }
3255                _ => panic!("exact-key fixture should return its identity and payload"),
3256            },
3257            _ => panic!("exact-key fixture insert should return one row"),
3258        }
3259    }
3260
3261    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3262    fn identity_row_stored_bytes<C: CanisterKind>(
3263        session: &DbSession<C>,
3264        store_path: &'static str,
3265        key: u64,
3266    ) -> u64 {
3267        let data_key = DecodedDataStoreKey::try_from_structural_key(ENTITY_TAG, &Value::Nat64(key))
3268            .expect("identity row key should encode");
3269        let raw_key = data_key.to_raw().expect("identity raw key should encode");
3270        let store = session
3271            .db
3272            .recovered_store(store_path)
3273            .expect("identity store should resolve");
3274        store.with_data(|data_store| {
3275            u64::try_from(
3276                data_store
3277                    .get(&raw_key)
3278                    .expect("inserted identity row should exist")
3279                    .len(),
3280            )
3281            .expect("bounded row length should fit u64")
3282        })
3283    }
3284
3285    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3286    fn with_stored_bytes_limit<T>(
3287        limit: u64,
3288        shape_fingerprint_prefix: u64,
3289        operation: impl FnOnce() -> Result<T, crate::db::query::intent::QueryError>,
3290    ) -> Result<T, crate::db::query::intent::QueryError> {
3291        let budget = HardExecutionBudget::uniform_for_tests(
3292            u64::MAX,
3293            HardExecutionFailureHeadroom::new(500, 256),
3294        )
3295        .with_limit_for_tests(
3296            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::StoredBytesRead,
3297            limit,
3298        );
3299        let context = HardExecutionContext::new(
3300            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
3301            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
3302            shape_fingerprint_prefix,
3303        );
3304
3305        with_query_execution_budget_for_tests(budget, context, operation)
3306    }
3307
3308    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3309    fn assert_exact_key_batch<C: CanisterKind>(session: &DbSession<C>) {
3310        let first = insert_exact_key_fixture(session, 41);
3311        let second = insert_exact_key_fixture(session, 42);
3312        let missing = u64::MAX;
3313        let binding = exact_key_binding(session);
3314        let gets_before = DataStore::current_get_call_count();
3315        let result = session
3316            .execute_public_exact_key_batch_for_typed_binding(
3317                &binding,
3318                &[second, missing, first, second],
3319            )
3320            .expect("exact-key batch should execute")
3321            .expect("exact-key binding should remain current");
3322
3323        assert_eq!(result.positions, vec![0, 1, 2, 0]);
3324        assert_eq!(
3325            result.distinct_rows,
3326            vec![
3327                Some(expected_dynamic_row(second, 42)),
3328                None,
3329                Some(expected_dynamic_row(first, 41)),
3330            ],
3331        );
3332        assert_eq!(
3333            DataStore::current_get_call_count().saturating_sub(gets_before),
3334            3,
3335            "four input positions with one duplicate must perform three physical reads",
3336        );
3337    }
3338
3339    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3340    #[test]
3341    fn exact_key_batches_preserve_semantics_across_heap_and_journaled_stores() {
3342        assert_exact_key_batch(&initialize());
3343        assert_exact_key_batch(&initialize_journaled());
3344    }
3345
3346    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3347    fn assert_primary_range_materialization_fetches_once<C: CanisterKind>(
3348        session: &DbSession<C>,
3349        store_path: &'static str,
3350    ) {
3351        let key = insert_exact_key_fixture(session, 41);
3352        let stored_bytes = identity_row_stored_bytes(session, store_path, key);
3353
3354        let scalar = DynamicQuery::new(ENTITY_NAME)
3355            .select(["id", "payload"])
3356            .order_by(asc("id"))
3357            .limit(1);
3358        let gets_before = DataStore::current_get_call_count();
3359        let scalar_page = with_stored_bytes_limit(stored_bytes, 0x7072_696d_6172_792d, || {
3360            session.execute_trusted_live_page(&scalar, None)
3361        })
3362        .expect("one scalar primary-range row should fit one payload-read allowance");
3363        assert_eq!(scalar_page.row_count, 1);
3364        assert_eq!(
3365            DataStore::current_get_call_count().saturating_sub(gets_before),
3366            1,
3367            "scalar primary traversal should fetch its emitted row exactly once",
3368        );
3369
3370        let grouped = DynamicQuery::new(ENTITY_NAME)
3371            .group_by("payload")
3372            .aggregate(crate::db::count())
3373            .grouped_limits(10, 16 * 1_024)
3374            .limit(1);
3375        let gets_before = DataStore::current_get_call_count();
3376        let grouped_page = with_stored_bytes_limit(stored_bytes, 0x6772_6f75_7065_642d, || {
3377            session.execute_trusted_dynamic_grouped_query(&grouped)
3378        })
3379        .expect("one grouped primary-range row should fit one payload-read allowance");
3380        assert_eq!(grouped_page.row_count, 1);
3381        assert_eq!(
3382            DataStore::current_get_call_count().saturating_sub(gets_before),
3383            1,
3384            "grouped primary traversal should fetch its source row exactly once",
3385        );
3386    }
3387
3388    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3389    #[test]
3390    fn row_materialization_fetches_each_required_payload_at_most_once() {
3391        assert_primary_range_materialization_fetches_once(&initialize(), STORE_PATH);
3392        assert_primary_range_materialization_fetches_once(
3393            &initialize_journaled(),
3394            JOURNALED_STORE_PATH,
3395        );
3396    }
3397
3398    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3399    #[test]
3400    fn ordered_grouped_pages_close_a_group_spanning_physical_refills_before_resume() {
3401        let session = initialize();
3402        let mut patches = Vec::new();
3403        for _ in 0..70 {
3404            patches.push(dynamic_payload_patch(10));
3405        }
3406        for _ in 0..3 {
3407            patches.push(dynamic_payload_patch(20));
3408        }
3409        patches.push(dynamic_payload_patch(30));
3410        let inserted = session
3411            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, patches)
3412            .expect("ordered grouped continuation rows should insert");
3413        assert_eq!(inserted.rows.len(), 74);
3414
3415        let query = DynamicQuery::new(ENTITY_NAME)
3416            .group_by("payload")
3417            .aggregate(crate::db::count())
3418            .aggregate(crate::db::sum("id"))
3419            .order_by(asc("payload"))
3420            .grouped_limits(4, 16 * 1_024)
3421            .limit(1);
3422        let expected = [
3423            (10_u64, 70_u64, crate::types::Decimal::new(2_485, 0)),
3424            (20, 3, crate::types::Decimal::new(216, 0)),
3425            (30, 1, crate::types::Decimal::new(74, 0)),
3426        ];
3427        let mut continuation: Option<String> = None;
3428        let mut seen_cursors = std::collections::BTreeSet::new();
3429
3430        for (page_index, (group_key, row_count, id_sum)) in expected.into_iter().enumerate() {
3431            let request = continuation.as_ref().map_or_else(
3432                || query.clone(),
3433                |cursor| query.clone().cursor(cursor.clone()),
3434            );
3435            let entries_before = IndexStore::current_entry_read_count();
3436            let rows_before = DataStore::current_get_call_count();
3437            let page = session
3438                .execute_trusted_dynamic_grouped_query(&request)
3439                .unwrap_or_else(|error| {
3440                    panic!("ordered grouped page {page_index} should execute: {error:?}")
3441                });
3442            let entries_read =
3443                IndexStore::current_entry_read_count().saturating_sub(entries_before);
3444            let rows_read = DataStore::current_get_call_count().saturating_sub(rows_before);
3445
3446            assert_eq!(page.row_count, 1);
3447            let [row] = page.rows.as_slice() else {
3448                panic!("ordered grouped page must contain exactly one closed group")
3449            };
3450            assert_eq!(row.group_key(), &[OutputValue::Nat64(group_key)]);
3451            assert_eq!(
3452                row.aggregate_values(),
3453                &[OutputValue::Nat64(row_count), OutputValue::Decimal(id_sum),],
3454            );
3455            if page_index == 0 {
3456                assert!(
3457                    entries_read.saturating_add(rows_read) >= 70,
3458                    "the first closed group must span the maintained 64-entry physical refill",
3459                );
3460            }
3461
3462            continuation = page.next_cursor;
3463            if page_index + 1 < expected.len() {
3464                let cursor = continuation
3465                    .as_ref()
3466                    .expect("another closed group should retain continuation");
3467                assert!(
3468                    seen_cursors.insert(cursor.clone()),
3469                    "ordered grouped continuation must advance monotonically",
3470                );
3471            } else {
3472                assert_eq!(continuation, None);
3473            }
3474        }
3475    }
3476
3477    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3478    #[test]
3479    fn exhaustive_pages_require_and_recompare_the_complete_source_proof() {
3480        let session = initialize();
3481        let first = insert_exact_key_fixture(&session, 41);
3482        let second = insert_exact_key_fixture(&session, 42);
3483        let third = insert_exact_key_fixture(&session, 43);
3484        let query = DynamicQuery::new(ENTITY_NAME)
3485            .select(["id", "payload"])
3486            .order_by(asc("id"));
3487
3488        let page = session
3489            .execute_trusted_exhaustive_page(&query, None, None)
3490            .expect("initial exhaustive page should capture its source proof");
3491        assert_eq!(
3492            page.rows,
3493            vec![
3494                expected_dynamic_row(first, 41),
3495                expected_dynamic_row(second, 42),
3496            ],
3497        );
3498        let continuation = page
3499            .continuation
3500            .as_deref()
3501            .expect("unreturned row should retain exhaustive continuation");
3502        assert!(matches!(
3503            session.execute_trusted_exhaustive_page(&query, Some(continuation), None),
3504            Err(ExhaustiveReadError::Revision(
3505                ReadSetRevisionError::ResumeProofRequired
3506            )),
3507        ));
3508        let resumed = session
3509            .execute_trusted_exhaustive_page(&query, Some(continuation), Some(&page.proof))
3510            .expect("unchanged proof should resume exhaustive traversal");
3511        assert_eq!(resumed.rows, vec![expected_dynamic_row(third, 43)]);
3512        assert_eq!(resumed.continuation, None);
3513
3514        let stale_page = session
3515            .execute_trusted_exhaustive_page(&query, None, None)
3516            .expect("fresh exhaustive page should capture current revision");
3517        let stale_continuation = stale_page
3518            .continuation
3519            .as_deref()
3520            .expect("fresh three-row traversal should retain continuation");
3521        let _ = insert_exact_key_fixture(&session, 44);
3522        assert!(matches!(
3523            session.execute_trusted_exhaustive_page(
3524                &query,
3525                Some(stale_continuation),
3526                Some(&stale_page.proof),
3527            ),
3528            Err(ExhaustiveReadError::Revision(
3529                ReadSetRevisionError::StoreDataChanged { .. }
3530            )),
3531        ));
3532    }
3533
3534    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3535    #[test]
3536    fn heap_sources_cannot_back_durable_resumable_jobs() {
3537        let session = initialize();
3538        let proof = session
3539            .capture_read_set_revision_proof(&[ENTITY_NAME])
3540            .expect("heap source proof should capture for one-call exhaustive reads");
3541        let job_id = ResumableJobId::try_from_bytes([70; 32])
3542            .expect("nonzero heap test job identity should admit");
3543
3544        assert!(matches!(
3545            session.start_resumable_job(job_id, proof, Vec::new()),
3546            Err(ResumableJobError::SourceProof(
3547                ReadSetRevisionError::DurableStoreRequired { .. }
3548            )),
3549        ));
3550    }
3551
3552    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3553    #[test]
3554    fn proof_and_progress_controls_charge_one_shared_request_scope() {
3555        let (session, root) = initialize_journaled_with_root();
3556        let resource = icydb_diagnostic_code::DiagnosticExecutionBudgetResource::QueryExecutions;
3557        let before = root.observed(resource);
3558        let proof = session
3559            .capture_read_set_revision_proof(&[ENTITY_NAME])
3560            .expect("proof capture should use the retained request scope");
3561        let job_id = ResumableJobId::try_from_bytes([75; 32])
3562            .expect("nonzero accounting job identity should admit");
3563        session
3564            .start_resumable_job(job_id, proof, Vec::new())
3565            .expect("job start should use the same retained request scope");
3566        let _ = session
3567            .resumable_job_state(job_id)
3568            .expect("job load should use the same retained request scope");
3569
3570        assert_eq!(root.observed(resource).saturating_sub(before), 3);
3571    }
3572
3573    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3574    #[test]
3575    fn source_proofs_ignore_unrelated_stores_but_bind_access_state_changes() {
3576        let session = initialize();
3577        let proof = session
3578            .capture_read_set_revision_proof(&[ENTITY_NAME])
3579            .expect("source proof should cover only the entity's physical store");
3580        let shared_store_proof = session
3581            .capture_read_set_revision_proof(&[ENTITY_NAME, ENTITY_NAME])
3582            .expect("entities sharing one physical source should deduplicate");
3583        assert_eq!(shared_store_proof, proof);
3584        assert_eq!(shared_store_proof.stores().len(), 1);
3585        let unrelated = session
3586            .db
3587            .store_handle(UNRELATED_STORE_PATH)
3588            .expect("unrelated registered store should resolve");
3589        unrelated.with_data_mut(|store| {
3590            let _ = store.remove(&RawDataStoreKey::from_persisted_bytes(vec![1]));
3591        });
3592        session
3593            .verify_read_set_revision_proof(&proof)
3594            .expect("a nonparticipating store mutation must not invalidate the proof");
3595
3596        let source = session
3597            .db
3598            .store_handle(STORE_PATH)
3599            .expect("participating source store should resolve");
3600        source
3601            .mark_index_building()
3602            .expect("source access-state transition should advance its revision");
3603        assert!(matches!(
3604            session.verify_read_set_revision_proof(&proof),
3605            Err(ExhaustiveReadError::Revision(
3606                ReadSetRevisionError::StoreAccessChanged { .. }
3607            )),
3608        ));
3609    }
3610
3611    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3612    #[expect(
3613        clippy::too_many_lines,
3614        reason = "one lifecycle test proves successful replay plus pre-page and post-page source invalidation without sharing progress state across tests"
3615    )]
3616    #[test]
3617    fn journaled_job_advance_is_idempotent_and_revision_checked_on_both_sides() {
3618        let session = initialize_journaled();
3619        let proof = session
3620            .capture_read_set_revision_proof(&[ENTITY_NAME])
3621            .expect("journaled source proof should capture");
3622        let job_id =
3623            ResumableJobId::try_from_bytes([71; 32]).expect("nonzero job identity should admit");
3624        session
3625            .start_resumable_job(job_id, proof, vec![0])
3626            .expect("journaled job should start outside its protected source revision");
3627        let request = ResumableJobAdvanceRequest::new(
3628            job_id,
3629            0,
3630            ResumableJobIdempotencyKey::new("page-0")
3631                .expect("bounded idempotency key should admit"),
3632        );
3633        let calls = Cell::new(0_u8);
3634        let receipt = session
3635            .compare_proof_and_advance(&request, |state| {
3636                calls.set(calls.get() + 1);
3637                assert_eq!(state.application_state, vec![0]);
3638                Ok::<_, ()>(
3639                    ResumableJobAdvance::new(Some("cursor-1".to_string()), vec![1], vec![9])
3640                        .expect("bounded application advance should admit"),
3641                )
3642            })
3643            .expect("unchanged source should advance exactly once");
3644        assert_eq!(calls.get(), 1);
3645        assert_eq!(receipt.status, ResumableJobAdvanceStatus::Advanced);
3646        assert_eq!(receipt.committed_sequence, 1);
3647
3648        let replay = session
3649            .compare_proof_and_advance::<()>(&request, |_| {
3650                panic!("lost-response replay must not execute application work")
3651            })
3652            .expect("same request identity should return its persisted receipt");
3653        assert_eq!(replay, receipt);
3654        let retained = session
3655            .resumable_job_state(job_id)
3656            .expect("advanced state should remain durable");
3657        assert_eq!(retained.sequence, 1);
3658        assert_eq!(retained.application_state, vec![1]);
3659
3660        let _ = insert_exact_key_fixture(&session, 51);
3661        let pre_change_request = ResumableJobAdvanceRequest::new(
3662            job_id,
3663            1,
3664            ResumableJobIdempotencyKey::new("page-1")
3665                .expect("bounded idempotency key should admit"),
3666        );
3667        let pre_change_calls = Cell::new(0_u8);
3668        let invalidated = session
3669            .compare_proof_and_advance::<()>(&pre_change_request, |_| {
3670                pre_change_calls.set(pre_change_calls.get() + 1);
3671                unreachable!("pre-page proof failure must reject before application work")
3672            })
3673            .expect("source drift should persist one replayable invalidation receipt");
3674        assert_eq!(pre_change_calls.get(), 0);
3675        assert_eq!(invalidated.status, ResumableJobAdvanceStatus::Invalidated);
3676        let invalidated_state = session
3677            .resumable_job_state(job_id)
3678            .expect("invalidated job should remain inspectable");
3679        assert_eq!(invalidated_state.status, ResumableJobStatus::Invalidated);
3680        assert_eq!(invalidated_state.continuation, None);
3681        assert_eq!(invalidated_state.application_state, vec![1]);
3682        assert_eq!(
3683            session
3684                .compare_proof_and_advance::<()>(&pre_change_request, |_| {
3685                    panic!("invalidation replay must not execute application work")
3686                })
3687                .expect("lost invalidation reply should replay exactly"),
3688            invalidated,
3689        );
3690
3691        let post_proof = session
3692            .capture_read_set_revision_proof(&[ENTITY_NAME])
3693            .expect("post-change journaled proof should capture");
3694        let post_job_id = ResumableJobId::try_from_bytes([72; 32])
3695            .expect("nonzero post-change job identity should admit");
3696        session
3697            .start_resumable_job(post_job_id, post_proof, vec![7])
3698            .expect("post-change journaled job should start");
3699        let post_request = ResumableJobAdvanceRequest::new(
3700            post_job_id,
3701            0,
3702            ResumableJobIdempotencyKey::new("post-page-0")
3703                .expect("bounded idempotency key should admit"),
3704        );
3705        let post_receipt = session
3706            .compare_proof_and_advance::<()>(&post_request, |_| {
3707                let _ = insert_exact_key_fixture(&session, 52);
3708                Ok(ResumableJobAdvance::new(None, vec![8], vec![10])
3709                    .expect("bounded post-change candidate should admit"))
3710            })
3711            .expect("post-page drift should discard the candidate and persist invalidation");
3712        assert_eq!(post_receipt.status, ResumableJobAdvanceStatus::Invalidated);
3713        let post_state = session
3714            .resumable_job_state(post_job_id)
3715            .expect("post-page invalidation should remain inspectable");
3716        assert_eq!(post_state.status, ResumableJobStatus::Invalidated);
3717        assert_eq!(post_state.application_state, vec![7]);
3718        session
3719            .acknowledge_resumable_job(post_job_id, post_state.sequence)
3720            .expect("terminal job acknowledgement should remove retained progress");
3721        session
3722            .acknowledge_resumable_job(post_job_id, post_state.sequence)
3723            .expect("lost acknowledgement reply should be safely replayable");
3724        assert_eq!(
3725            session.resumable_job_state(post_job_id),
3726            Err(ResumableJobError::NotFound),
3727        );
3728
3729        let completed_job_id = ResumableJobId::try_from_bytes([74; 32])
3730            .expect("nonzero completed job identity should admit");
3731        let completed_proof = session
3732            .capture_read_set_revision_proof(&[ENTITY_NAME])
3733            .expect("completed-job source proof should capture");
3734        session
3735            .start_resumable_job(completed_job_id, completed_proof, Vec::new())
3736            .expect("completed-job fixture should start");
3737        let completed_request = ResumableJobAdvanceRequest::new(
3738            completed_job_id,
3739            0,
3740            ResumableJobIdempotencyKey::new("complete")
3741                .expect("bounded completion key should admit"),
3742        );
3743        let completed_receipt = session
3744            .compare_proof_and_advance::<()>(&completed_request, |_| {
3745                Ok(ResumableJobAdvance::new(None, vec![99], vec![100])
3746                    .expect("bounded terminal advance should admit"))
3747            })
3748            .expect("null continuation should commit terminal completion");
3749        let completed_state = session
3750            .resumable_job_state(completed_job_id)
3751            .expect("completed state should remain replayable before acknowledgement");
3752        assert_eq!(completed_state.status, ResumableJobStatus::Completed);
3753        assert_eq!(
3754            session
3755                .compare_proof_and_advance::<()>(&completed_request, |_| {
3756                    panic!("completed request replay must not execute application work")
3757                })
3758                .expect("completed request should replay until acknowledgement"),
3759            completed_receipt,
3760        );
3761        let after_completion = ResumableJobAdvanceRequest::new(
3762            completed_job_id,
3763            1,
3764            ResumableJobIdempotencyKey::new("after-complete")
3765                .expect("bounded post-completion key should admit"),
3766        );
3767        assert!(matches!(
3768            session.compare_proof_and_advance::<()>(&after_completion, |_| {
3769                panic!("completed jobs cannot execute another page")
3770            }),
3771            Err(CompareProofAndAdvanceError::Protocol(
3772                ResumableJobError::Completed
3773            )),
3774        ));
3775        session
3776            .acknowledge_resumable_job(completed_job_id, completed_state.sequence)
3777            .expect("completed job should acknowledge and free capacity");
3778        session
3779            .acknowledge_resumable_job(completed_job_id, completed_state.sequence)
3780            .expect("completion acknowledgement should be idempotent");
3781
3782        let stale_job_id = ResumableJobId::try_from_bytes([73; 32])
3783            .expect("nonzero stale-sequence job identity should admit");
3784        let stale_proof = session
3785            .capture_read_set_revision_proof(&[ENTITY_NAME])
3786            .expect("stale-sequence source proof should capture");
3787        session
3788            .start_resumable_job(stale_job_id, stale_proof, Vec::new())
3789            .expect("stale-sequence job should start");
3790        let stale_request = ResumableJobAdvanceRequest::new(
3791            stale_job_id,
3792            4,
3793            ResumableJobIdempotencyKey::new("stale").expect("bounded idempotency key should admit"),
3794        );
3795        assert!(matches!(
3796            session.compare_proof_and_advance::<()>(&stale_request, |_| {
3797                panic!("stale sequence must reject before application work")
3798            }),
3799            Err(CompareProofAndAdvanceError::Protocol(
3800                ResumableJobError::StaleSequence {
3801                    expected: 4,
3802                    actual: 0,
3803                }
3804            )),
3805        ));
3806        assert_eq!(
3807            session.acknowledge_resumable_job(stale_job_id, 0),
3808            Err(ResumableJobError::NotTerminal),
3809        );
3810    }
3811
3812    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3813    #[test]
3814    fn exact_key_batch_uses_typed_hard_execution_budget() {
3815        let session = initialize();
3816        let binding = exact_key_binding(&session);
3817        let budget =
3818            HardExecutionBudget::uniform_for_tests(0, HardExecutionFailureHeadroom::new(500, 256));
3819        let error = session
3820            .execute_exact_key_batch_with_hard_budget_for_tests(&binding, &[u64::MAX], &budget)
3821            .expect_err("zero query budget should reject the exact-key route");
3822
3823        assert!(matches!(
3824            error.diagnostic().detail(),
3825            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
3826                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
3827            })
3828        ));
3829        let facts = error.diagnostic_facts();
3830        assert_eq!(
3831            &facts[..5],
3832            &[
3833                (
3834                    icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
3835                    icydb_diagnostic_code::DiagnosticExecutionBudgetResource::QueryExecutions.raw(),
3836                ),
3837                (icydb_diagnostic_code::DiagnosticFactTag::Limit, 0),
3838                (icydb_diagnostic_code::DiagnosticFactTag::Actual, 1),
3839                (
3840                    icydb_diagnostic_code::DiagnosticFactTag::ExecutionBudgetScope,
3841                    icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution.raw(),
3842                ),
3843                (
3844                    icydb_diagnostic_code::DiagnosticFactTag::ExecutionLane,
3845                    icydb_diagnostic_code::DiagnosticExecutionLane::PublicRead.raw(),
3846                ),
3847            ],
3848        );
3849        assert_eq!(
3850            facts[5].0,
3851            icydb_diagnostic_code::DiagnosticFactTag::QueryShapeFingerprintPrefix,
3852        );
3853        assert_ne!(facts[5].1, 0);
3854    }
3855
3856    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3857    fn assert_planned_query_exhausts(
3858        session: &DbSession<TestCanister>,
3859        query: &crate::db::DynamicQuery,
3860        resource: icydb_diagnostic_code::DiagnosticExecutionBudgetResource,
3861    ) {
3862        let budget = HardExecutionBudget::uniform_for_tests(
3863            u64::MAX,
3864            HardExecutionFailureHeadroom::new(500, 256),
3865        )
3866        .with_limit_for_tests(resource, 0);
3867        let context = HardExecutionContext::new(
3868            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
3869            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
3870            0x7068_7973_6963_616c,
3871        );
3872        let error = with_query_execution_budget_for_tests(budget, context, || {
3873            session.execute_trusted_live_page(query, None)
3874        })
3875        .expect_err("the injected zero resource allowance should reject planned execution");
3876
3877        assert!(matches!(
3878            error.diagnostic().detail(),
3879            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
3880                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
3881            })
3882        ));
3883        assert_eq!(
3884            error.diagnostic_facts()[0],
3885            (
3886                icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
3887                resource.raw(),
3888            ),
3889        );
3890    }
3891
3892    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3893    fn assert_grouped_query_exhausts(
3894        session: &DbSession<TestCanister>,
3895        query: &crate::db::DynamicQuery,
3896        resource: icydb_diagnostic_code::DiagnosticExecutionBudgetResource,
3897    ) {
3898        let budget = HardExecutionBudget::uniform_for_tests(
3899            u64::MAX,
3900            HardExecutionFailureHeadroom::new(500, 256),
3901        )
3902        .with_limit_for_tests(resource, 0);
3903        let context = HardExecutionContext::new(
3904            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
3905            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
3906            0x6772_6f75_7065_642d,
3907        );
3908        let error = with_query_execution_budget_for_tests(budget, context, || {
3909            session.execute_trusted_dynamic_grouped_query(query)
3910        })
3911        .expect_err("the injected zero resource allowance should reject grouped execution");
3912
3913        assert!(matches!(
3914            error.diagnostic().detail(),
3915            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
3916                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
3917            })
3918        ));
3919        assert_eq!(
3920            error.diagnostic_facts()[0],
3921            (
3922                icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
3923                resource.raw(),
3924            ),
3925        );
3926    }
3927
3928    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3929    fn assert_sql_query_exhausts(
3930        session: &DbSession<TestCanister>,
3931        sql: &str,
3932        resource: icydb_diagnostic_code::DiagnosticExecutionBudgetResource,
3933    ) {
3934        let budget = HardExecutionBudget::uniform_for_tests(
3935            u64::MAX,
3936            HardExecutionFailureHeadroom::new(500, 256),
3937        )
3938        .with_limit_for_tests(resource, 0);
3939        let context = HardExecutionContext::new(
3940            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
3941            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
3942            0x7371_6c2d_736f_7274,
3943        );
3944        let error = with_query_execution_budget_for_tests(budget, context, || {
3945            session.execute_trusted_sql_query(sql)
3946        })
3947        .expect_err("the injected zero resource allowance should reject SQL execution");
3948
3949        assert!(matches!(
3950            error.diagnostic().detail(),
3951            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
3952                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
3953            })
3954        ));
3955        assert_eq!(
3956            error.diagnostic_facts()[0],
3957            (
3958                icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
3959                resource.raw(),
3960            ),
3961        );
3962    }
3963
3964    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3965    #[test]
3966    fn planned_read_routes_share_physical_resource_accounting() {
3967        let session = initialize();
3968        let first = insert_exact_key_fixture(&session, 41);
3969        insert_exact_key_fixture(&session, 42);
3970
3971        let fallback = crate::db::DynamicQuery::new(ENTITY_NAME)
3972            .filter(crate::db::FieldRef::new("id").eq(first))
3973            .select(["id", "payload"])
3974            .order_by(crate::db::asc("id"))
3975            .limit(1);
3976        assert_eq!(
3977            session
3978                .execute_trusted_live_page(&fallback, None)
3979                .expect("bounded fallback execution should preserve its result")
3980                .row_count,
3981            1,
3982        );
3983        assert_planned_query_exhausts(
3984            &session,
3985            &fallback,
3986            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::RowsVisited,
3987        );
3988
3989        let covering = crate::db::DynamicQuery::new(ENTITY_NAME)
3990            .filter(crate::db::FieldRef::new("payload").eq(41_u64))
3991            .select(["payload"])
3992            .order_by(crate::db::asc("payload"))
3993            .limit(1);
3994        assert_eq!(
3995            session
3996                .execute_trusted_live_page(&covering, None)
3997                .expect("bounded covering execution should preserve its result")
3998                .row_count,
3999            1,
4000        );
4001        assert_planned_query_exhausts(
4002            &session,
4003            &covering,
4004            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::KeyIndexEntriesVisited,
4005        );
4006
4007        let residual = crate::db::DynamicQuery::new(ENTITY_NAME)
4008            .filter(crate::db::FieldRef::new("payload").eq_field("id"))
4009            .select(["id"])
4010            .order_by(crate::db::asc("id"))
4011            .limit(1);
4012        assert_eq!(
4013            session
4014                .execute_trusted_live_page(&residual, None)
4015                .expect("bounded residual execution should preserve its result")
4016                .row_count,
4017            0,
4018        );
4019        assert_planned_query_exhausts(
4020            &session,
4021            &residual,
4022            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::PredicateExpressionSteps,
4023        );
4024
4025        assert_planned_query_exhausts(
4026            &session,
4027            &fallback,
4028            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::ResultBytes,
4029        );
4030
4031        let grouped = crate::db::DynamicQuery::new(ENTITY_NAME)
4032            .group_by("payload")
4033            .aggregate(crate::db::count())
4034            .order_by(crate::db::asc("payload"))
4035            .grouped_limits(10, 16 * 1_024)
4036            .limit(1);
4037        let grouped_result = session
4038            .execute_trusted_dynamic_grouped_query(&grouped)
4039            .expect("bounded grouped execution should preserve its result");
4040        assert_eq!(grouped_result.row_count, 1);
4041        assert!(grouped_result.next_cursor.is_some());
4042        assert_grouped_query_exhausts(
4043            &session,
4044            &grouped,
4045            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::GroupDistinctEntries,
4046        );
4047        assert_grouped_query_exhausts(
4048            &session,
4049            &grouped,
4050            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::CursorSteps,
4051        );
4052
4053        assert_sql_query_exhausts(
4054            &session,
4055            "SELECT payload, COUNT(*) AS row_count FROM IdentityRow \
4056             GROUP BY payload ORDER BY row_count DESC, payload ASC LIMIT 1",
4057            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::SortEntries,
4058        );
4059    }
4060
4061    fn assert_dynamic_payload<C: CanisterKind>(
4062        session: &DbSession<C>,
4063        key: u64,
4064        expected_payload: u64,
4065    ) {
4066        let unchanged = session
4067            .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
4068                entity: ENTITY_NAME.to_string(),
4069                key: InputValue::Nat64(key),
4070                patch: dynamic_payload_patch(expected_payload),
4071            })
4072            .expect("the expected row should remain readable through a no-op update");
4073        assert_eq!(unchanged.affected_rows, 0);
4074        assert_eq!(
4075            unchanged.rows,
4076            vec![expected_dynamic_row(key, expected_payload)],
4077        );
4078    }
4079
4080    fn batch(values: &[u64]) -> Vec<AcceptedStructuralMutation> {
4081        values
4082            .iter()
4083            .map(|value| {
4084                AcceptedStructuralMutation::save(
4085                    MutationMode::Insert,
4086                    AcceptedStructuralMutationTarget::ResolveFromAfterImage,
4087                    payload_patch(*value),
4088                )
4089            })
4090            .collect()
4091    }
4092
4093    fn assert_identity_boundary(error: &InternalError) {
4094        assert_eq!(error.class(), ErrorClass::Unsupported);
4095        assert_eq!(error.origin(), ErrorOrigin::Identity);
4096    }
4097
4098    #[test]
4099    fn generated_candidate_collision_is_identity_corruption_before_generic_uniqueness() {
4100        let generated = insert_key_exists_after_generation(true);
4101        assert_eq!(generated.class(), ErrorClass::Corruption);
4102        assert_eq!(generated.origin(), ErrorOrigin::Identity);
4103
4104        let ordinary = insert_key_exists_after_generation(false);
4105        assert_ne!(ordinary.origin(), ErrorOrigin::Identity);
4106    }
4107
4108    #[cfg(target_pointer_width = "64")]
4109    #[test]
4110    fn pre_key_candidate_count_rejects_values_beyond_the_persisted_u32_bound() {
4111        let error = checked_pre_key_candidate_count(
4112            usize::try_from(u64::from(u32::MAX) + 1).expect("64-bit usize should hold u32 + 1"),
4113        )
4114        .expect_err("candidate counts beyond u32 must reject");
4115        assert_identity_boundary(&error);
4116    }
4117
4118    #[test]
4119    #[expect(
4120        clippy::too_many_lines,
4121        reason = "one holding lifecycle proves split, merge, transfer, late-failure neutrality, result order, and Identity state"
4122    )]
4123    fn mixed_structural_batch_preserves_holding_conservation_and_failure_atomicity() {
4124        let session = initialize();
4125        let seeded = session
4126            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, vec![dynamic_payload_patch(100)])
4127            .expect("seed rows should commit");
4128        assert_eq!(seeded.affected_rows, 1);
4129
4130        let split = session
4131            .execute_trusted_dynamic_mutation_batch(vec![
4132                DynamicMutation::Update {
4133                    entity: ENTITY_NAME.to_string(),
4134                    key: InputValue::Nat64(1),
4135                    patch: dynamic_payload_patch(60),
4136                },
4137                DynamicMutation::Insert {
4138                    entity: ENTITY_NAME.to_string(),
4139                    patch: dynamic_payload_patch(40),
4140                },
4141            ])
4142            .expect("one holding should split atomically");
4143        assert_eq!(split.affected_rows, 2);
4144        assert_eq!(
4145            split.rows,
4146            vec![expected_dynamic_row(1, 60), expected_dynamic_row(2, 40),],
4147            "split after-images must retain input order and exact quantity",
4148        );
4149
4150        let rejected_split = session
4151            .execute_trusted_dynamic_mutation_batch(vec![
4152                DynamicMutation::Update {
4153                    entity: ENTITY_NAME.to_string(),
4154                    key: InputValue::Nat64(1),
4155                    patch: dynamic_payload_patch(50),
4156                },
4157                DynamicMutation::Insert {
4158                    entity: ENTITY_NAME.to_string(),
4159                    patch: DynamicStructuralPatch::new(Vec::new()),
4160                },
4161            ])
4162            .expect_err("an invalid split output must reject the staged source update");
4163        assert_eq!(rejected_split.class(), ErrorClass::Unsupported);
4164        assert_eq!(rejected_split.origin(), ErrorOrigin::Executor);
4165        assert_eq!(
4166            rejected_split.diagnostic_facts(),
4167            vec![
4168                (
4169                    icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
4170                    ENTITY_TAG.value(),
4171                ),
4172                (icydb_diagnostic_code::DiagnosticFactTag::FieldId, 2),
4173                (
4174                    icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
4175                    icydb_diagnostic_code::DiagnosticMutationOperation::Insert.raw(),
4176                ),
4177                (icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 1,),
4178            ],
4179        );
4180        assert_dynamic_payload(&session, 1, 60);
4181        assert_dynamic_payload(&session, 2, 40);
4182
4183        let transfer = session
4184            .execute_trusted_dynamic_mutation_batch(vec![
4185                DynamicMutation::Update {
4186                    entity: ENTITY_NAME.to_string(),
4187                    key: InputValue::Nat64(1),
4188                    patch: dynamic_payload_patch(70),
4189                },
4190                DynamicMutation::Update {
4191                    entity: ENTITY_NAME.to_string(),
4192                    key: InputValue::Nat64(2),
4193                    patch: dynamic_payload_patch(30),
4194                },
4195            ])
4196            .expect("distinct transfer patches should share one atomic batch");
4197        assert_eq!(
4198            transfer.rows,
4199            vec![expected_dynamic_row(1, 70), expected_dynamic_row(2, 30),],
4200            "the transfer must preserve the exact total quantity",
4201        );
4202
4203        let merge = session
4204            .execute_trusted_dynamic_mutation_batch(vec![
4205                DynamicMutation::Delete {
4206                    entity: ENTITY_NAME.to_string(),
4207                    key: InputValue::Nat64(2),
4208                },
4209                DynamicMutation::Update {
4210                    entity: ENTITY_NAME.to_string(),
4211                    key: InputValue::Nat64(1),
4212                    patch: dynamic_payload_patch(100),
4213                },
4214            ])
4215            .expect("two holdings should merge atomically");
4216        assert_eq!(
4217            merge.rows,
4218            vec![expected_dynamic_row(2, 30), expected_dynamic_row(1, 100),],
4219            "delete before-images and update after-images must retain input order",
4220        );
4221
4222        let resplit = session
4223            .execute_trusted_dynamic_mutation_batch(vec![
4224                DynamicMutation::Update {
4225                    entity: ENTITY_NAME.to_string(),
4226                    key: InputValue::Nat64(1),
4227                    patch: dynamic_payload_patch(60),
4228                },
4229                DynamicMutation::Insert {
4230                    entity: ENTITY_NAME.to_string(),
4231                    patch: dynamic_payload_patch(40),
4232                },
4233            ])
4234            .expect("the merged holding should split again");
4235        assert_eq!(
4236            resplit.rows,
4237            vec![expected_dynamic_row(1, 60), expected_dynamic_row(3, 40),],
4238        );
4239
4240        let rejected_merge = session
4241            .execute_trusted_dynamic_mutation_batch(vec![
4242                DynamicMutation::Delete {
4243                    entity: ENTITY_NAME.to_string(),
4244                    key: InputValue::Nat64(3),
4245                },
4246                DynamicMutation::Update {
4247                    entity: ENTITY_NAME.to_string(),
4248                    key: InputValue::Nat64(99),
4249                    patch: dynamic_payload_patch(100),
4250                },
4251            ])
4252            .expect_err("a late missing merge target must preserve the earlier staged delete");
4253        assert_eq!(rejected_merge.class(), ErrorClass::NotFound);
4254        assert_dynamic_payload(&session, 1, 60);
4255        assert_dynamic_payload(&session, 3, 40);
4256
4257        SCHEMA_STORE.with(|store| {
4258            let cursor = store
4259                .borrow()
4260                .identity_statement_cursor(
4261                    database_incarnation_id().expect("database incarnation should remain readable"),
4262                    ENTITY_TAG,
4263                    FieldId::new(1),
4264                    &AcceptedFieldKind::Nat64,
4265                )
4266                .expect("mixed Identity state should remain readable");
4267            assert_eq!(cursor.expected_high_water(), 3);
4268            assert!(!cursor.has_allocations());
4269        });
4270    }
4271
4272    #[test]
4273    fn mixed_structural_batch_rejects_duplicate_holding_targets_without_mutation() {
4274        let session = initialize();
4275        session
4276            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, vec![dynamic_payload_patch(100)])
4277            .expect("the holding fixture should initialize");
4278
4279        let duplicate = session
4280            .execute_trusted_dynamic_mutation_batch(vec![
4281                DynamicMutation::Update {
4282                    entity: ENTITY_NAME.to_string(),
4283                    key: InputValue::Nat64(1),
4284                    patch: dynamic_payload_patch(60),
4285                },
4286                DynamicMutation::Delete {
4287                    entity: ENTITY_NAME.to_string(),
4288                    key: InputValue::Nat64(1),
4289                },
4290            ])
4291            .expect_err("duplicate targets across operation kinds must reject");
4292        assert!(matches!(
4293            duplicate.diagnostic().detail(),
4294            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4295                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchDuplicateKey,
4296            }),
4297        ));
4298        assert_eq!(
4299            duplicate.diagnostic_facts(),
4300            vec![
4301                (
4302                    icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
4303                    ENTITY_TAG.value(),
4304                ),
4305                (
4306                    icydb_diagnostic_code::DiagnosticFactTag::FirstBatchPosition,
4307                    0,
4308                ),
4309                (
4310                    icydb_diagnostic_code::DiagnosticFactTag::DuplicateBatchPosition,
4311                    1,
4312                ),
4313            ],
4314        );
4315        assert_dynamic_payload(&session, 1, 100);
4316    }
4317
4318    #[test]
4319    fn mixed_structural_batch_rejects_empty_and_over_bound_before_resolution() {
4320        let session = initialize();
4321        let empty = session
4322            .execute_trusted_dynamic_mutation_batch(Vec::new())
4323            .expect_err("an empty public batch must reject");
4324        assert!(matches!(
4325            empty.diagnostic().detail(),
4326            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4327                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchEmpty,
4328            }),
4329        ));
4330        assert_eq!(
4331            empty.diagnostic_facts(),
4332            vec![(icydb_diagnostic_code::DiagnosticFactTag::ActualCount, 0,)],
4333        );
4334
4335        let requests = (0..=MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS)
4336            .map(|_| DynamicMutation::Delete {
4337                entity: ENTITY_NAME.to_string(),
4338                key: InputValue::Nat64(1),
4339            })
4340            .collect();
4341        let over_bound = session
4342            .execute_trusted_dynamic_mutation_batch(requests)
4343            .expect_err("operation cap plus one must reject before row resolution");
4344        assert!(matches!(
4345            over_bound.diagnostic().detail(),
4346            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4347                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchTooManyItems,
4348            }),
4349        ));
4350        assert_eq!(
4351            over_bound.diagnostic_facts(),
4352            vec![
4353                (
4354                    icydb_diagnostic_code::DiagnosticFactTag::ActualCount,
4355                    (MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS + 1) as u64,
4356                ),
4357                (
4358                    icydb_diagnostic_code::DiagnosticFactTag::Limit,
4359                    MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS as u64,
4360                ),
4361            ],
4362        );
4363    }
4364
4365    #[test]
4366    fn mixed_structural_batch_staged_byte_bound_uses_checked_exact_boundary() {
4367        let mut exact = 0;
4368        add_structural_mutation_staged_bytes(
4369            &mut exact,
4370            [MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES],
4371        )
4372        .expect("the exact staged-byte boundary should admit");
4373        assert_eq!(exact, MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES);
4374
4375        let error = add_structural_mutation_staged_bytes(&mut exact, [1])
4376            .expect_err("one byte above the staged-byte boundary must reject");
4377        assert!(matches!(
4378            error.diagnostic().detail(),
4379            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4380                boundary:
4381                    icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchStagedBytesExceeded,
4382            }),
4383        ));
4384        assert_eq!(
4385            error.diagnostic_facts(),
4386            vec![
4387                (
4388                    icydb_diagnostic_code::DiagnosticFactTag::ActualLength,
4389                    (MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES + 1) as u64,
4390                ),
4391                (
4392                    icydb_diagnostic_code::DiagnosticFactTag::Limit,
4393                    MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES as u64,
4394                ),
4395            ],
4396        );
4397
4398        validate_structural_mutation_result_bytes(MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES)
4399            .expect("the exact result-byte boundary should admit");
4400        let error = validate_structural_mutation_result_bytes(
4401            MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES + 1,
4402        )
4403        .expect_err("one byte above the result-byte boundary must reject");
4404        assert!(matches!(
4405            error.diagnostic().detail(),
4406            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4407                boundary:
4408                    icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchResultBytesExceeded,
4409            }),
4410        ));
4411        assert_eq!(
4412            error.diagnostic_facts(),
4413            vec![
4414                (
4415                    icydb_diagnostic_code::DiagnosticFactTag::ActualLength,
4416                    (MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES + 1) as u64,
4417                ),
4418                (
4419                    icydb_diagnostic_code::DiagnosticFactTag::Limit,
4420                    MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES as u64,
4421                ),
4422            ],
4423        );
4424    }
4425
4426    #[expect(
4427        clippy::too_many_lines,
4428        reason = "one lifecycle proves shared materialization and every maintained frontend against the same zero-state owner"
4429    )]
4430    #[test]
4431    fn identity_insert_frontends_share_one_committed_range_without_rejected_consumption() {
4432        let session = initialize();
4433        let catalog = session
4434            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
4435            .expect("identity catalog should resolve");
4436        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
4437            .expect("identity row layout should build");
4438        let initial_description = session
4439            .try_describe_entity_by_name(ENTITY_NAME)
4440            .expect("accepted Identity description should resolve");
4441        assert_eq!(
4442            initial_description.entity_tag(),
4443            catalog.identity().entity_tag().value()
4444        );
4445        assert_eq!(
4446            initial_description.accepted_schema_fingerprint_method(),
4447            catalog.fingerprint_method_version()
4448        );
4449        assert_eq!(
4450            initial_description.accepted_schema_fingerprint(),
4451            catalog.fingerprint()
4452        );
4453        let initial_identity = initial_description
4454            .identity()
4455            .expect("accepted Identity policy should be described");
4456        assert_eq!(initial_identity.field(), "id");
4457        assert_eq!(initial_identity.generator(), "Identity::next");
4458        assert_eq!(initial_identity.accepted_kind(), "nat64");
4459        assert_eq!(initial_identity.minimum(), 1);
4460        assert_eq!(initial_identity.maximum(), u128::from(u64::MAX));
4461        assert_eq!(initial_identity.high_water(), 0);
4462        assert_eq!(initial_identity.remaining(), u128::from(u64::MAX));
4463        assert!(!initial_identity.exhausted());
4464
4465        let rejected = session
4466            .execute_accepted_structural_save_batch(
4467                &catalog,
4468                &descriptor,
4469                batch(&[1_000, 2_000]),
4470                Timestamp::from_millis(6),
4471                |_| Err::<(), _>(InternalError::executor_unsupported()),
4472            )
4473            .expect_err("a rejected precommit result must not publish its tentative range");
4474        assert_eq!(rejected.class(), ErrorClass::Unsupported);
4475        assert_eq!(DATA_STORE.with(|store| store.borrow().len()), 0);
4476
4477        let rows = session
4478            .execute_accepted_structural_save_batch(
4479                &catalog,
4480                &descriptor,
4481                batch(&[10, 20, 30]),
4482                Timestamp::from_millis(7),
4483                Ok,
4484            )
4485            .expect("one accepted batch should commit rows and one identity range");
4486        assert_eq!(
4487            rows.into_iter().map(|row| row.values).collect::<Vec<_>>(),
4488            vec![
4489                vec![Value::Nat64(1), Value::Nat64(10)],
4490                vec![Value::Nat64(2), Value::Nat64(20)],
4491                vec![Value::Nat64(3), Value::Nat64(30)],
4492            ],
4493        );
4494
4495        let dynamic = session
4496            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
4497                entity: ENTITY_NAME.to_string(),
4498                patch: DynamicStructuralPatch::new(vec![(
4499                    "payload".to_string(),
4500                    DynamicWriteCell::Value(InputValue::Nat64(40)),
4501                )]),
4502            })
4503            .expect("dynamic omission should commit through shared Identity generation");
4504        assert_eq!(dynamic.affected_rows, 1);
4505
4506        for (request, operation) in [
4507            (
4508                DynamicMutation::Insert {
4509                    entity: ENTITY_NAME.to_string(),
4510                    patch: DynamicStructuralPatch::new(vec![
4511                        (
4512                            "id".to_string(),
4513                            DynamicWriteCell::Value(InputValue::Nat64(41)),
4514                        ),
4515                        (
4516                            "payload".to_string(),
4517                            DynamicWriteCell::Value(InputValue::Nat64(42)),
4518                        ),
4519                    ]),
4520                },
4521                icydb_diagnostic_code::DiagnosticMutationOperation::Insert,
4522            ),
4523            (
4524                DynamicMutation::Update {
4525                    entity: ENTITY_NAME.to_string(),
4526                    key: InputValue::Nat64(1),
4527                    patch: DynamicStructuralPatch::new(vec![(
4528                        "id".to_string(),
4529                        DynamicWriteCell::Default,
4530                    )]),
4531                },
4532                icydb_diagnostic_code::DiagnosticMutationOperation::Update,
4533            ),
4534        ] {
4535            let error = session
4536                .execute_trusted_dynamic_mutation(&request)
4537                .expect_err("structural Identity authorship and regeneration must reject");
4538            assert_eq!(error.class(), ErrorClass::Unsupported);
4539            assert_eq!(error.origin(), ErrorOrigin::Executor);
4540            assert_eq!(
4541                error.diagnostic_facts(),
4542                vec![
4543                    (
4544                        icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
4545                        ENTITY_TAG.value(),
4546                    ),
4547                    (icydb_diagnostic_code::DiagnosticFactTag::FieldId, 1),
4548                    (
4549                        icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
4550                        operation.raw(),
4551                    ),
4552                    (icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 0,),
4553                ],
4554            );
4555        }
4556
4557        let binding = session
4558            .issue_typed_entity_binding(
4559                ENTITY_SOURCE,
4560                &[
4561                    DynamicTypedFieldBindingRequest::new(
4562                        ID_SOURCE.to_string(),
4563                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
4564                        false,
4565                    ),
4566                    DynamicTypedFieldBindingRequest::new(
4567                        PAYLOAD_SOURCE.to_string(),
4568                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
4569                        false,
4570                    ),
4571                ],
4572            )
4573            .expect("typed output should bind the Identity field");
4574        let typed_patch = binding
4575            .bind_write_fields(vec![(
4576                PAYLOAD_SOURCE.to_string(),
4577                DynamicWriteCell::Value(InputValue::Nat64(50)),
4578            )])
4579            .expect("typed payload should lower");
4580        let typed = session
4581            .execute_trusted_typed_mutation(
4582                &binding,
4583                &DynamicTypedMutation::Insert { patch: typed_patch },
4584            )
4585            .expect("typed omission should commit through shared Identity generation");
4586        assert_eq!(
4587            typed
4588                .expect("typed insert should return one mutation result")
4589                .affected_rows,
4590            1,
4591        );
4592        let explicit_typed_patch = binding
4593            .bind_write_fields(vec![
4594                (
4595                    ID_SOURCE.to_string(),
4596                    DynamicWriteCell::Value(InputValue::Nat64(51)),
4597                ),
4598                (
4599                    PAYLOAD_SOURCE.to_string(),
4600                    DynamicWriteCell::Value(InputValue::Nat64(52)),
4601                ),
4602            ])
4603            .expect("the low-level binding should retain exact authored intent");
4604        let explicit_typed_error = session
4605            .execute_trusted_typed_mutation(
4606                &binding,
4607                &DynamicTypedMutation::Insert {
4608                    patch: explicit_typed_patch,
4609                },
4610            )
4611            .expect_err("typed Identity authorship must reject before allocation");
4612        assert_eq!(explicit_typed_error.class(), ErrorClass::Unsupported);
4613        assert_eq!(explicit_typed_error.origin(), ErrorOrigin::Executor);
4614        assert_eq!(
4615            explicit_typed_error.diagnostic_facts(),
4616            vec![
4617                (
4618                    icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
4619                    ENTITY_TAG.value(),
4620                ),
4621                (icydb_diagnostic_code::DiagnosticFactTag::FieldId, 1),
4622                (
4623                    icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
4624                    icydb_diagnostic_code::DiagnosticMutationOperation::Insert.raw(),
4625                ),
4626                (icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 0,),
4627            ],
4628        );
4629
4630        let replace_error = session
4631            .execute_trusted_dynamic_mutation(&DynamicMutation::Replace {
4632                entity: ENTITY_NAME.to_string(),
4633                key: InputValue::Nat64(99),
4634                patch: DynamicStructuralPatch::new(vec![(
4635                    "payload".to_string(),
4636                    DynamicWriteCell::Value(InputValue::Nat64(60)),
4637                )]),
4638            })
4639            .expect_err("save-as-insert with a chosen Identity must reject");
4640        assert_eq!(replace_error.class(), ErrorClass::Unsupported);
4641        assert_eq!(replace_error.origin(), ErrorOrigin::Executor);
4642
4643        #[cfg(feature = "sql")]
4644        {
4645            for sql in [
4646                "INSERT INTO IdentityRow (payload) VALUES (70) RETURNING id, payload",
4647                "INSERT INTO IdentityRow (id, payload) VALUES (DEFAULT, 80) RETURNING id",
4648            ] {
4649                let _result = session
4650                    .execute_trusted_sql_mutation(sql)
4651                    .expect("SQL omission and DEFAULT should commit Identity generation");
4652            }
4653
4654            let error = session
4655                .execute_trusted_sql_mutation(
4656                    "INSERT INTO IdentityRow (id, payload) VALUES (42, 90)",
4657                )
4658                .expect_err("an explicit SQL Identity value must reject before allocation");
4659            let diagnostic = error.diagnostic();
4660            assert_eq!(
4661                diagnostic.code(),
4662                icydb_diagnostic_code::DiagnosticCode::QuerySqlWriteBoundary,
4663            );
4664            assert!(matches!(
4665                diagnostic.detail(),
4666                Some(icydb_diagnostic_code::DiagnosticDetail::SqlWriteBoundary {
4667                    boundary: icydb_diagnostic_code::SqlWriteBoundaryCode::ExplicitGeneratedField,
4668                }),
4669            ));
4670        }
4671
4672        let expected_committed = if cfg!(feature = "sql") { 7 } else { 5 };
4673        assert_eq!(
4674            DATA_STORE.with(|store| store.borrow().len()),
4675            expected_committed
4676        );
4677        SCHEMA_STORE.with(|store| {
4678            let cursor = store
4679                .borrow()
4680                .identity_statement_cursor(
4681                    database_incarnation_id().expect("database incarnation should remain readable"),
4682                    ENTITY_TAG,
4683                    FieldId::new(1),
4684                    &AcceptedFieldKind::Nat64,
4685                )
4686                .expect("committed writes must leave active state readable");
4687            assert_eq!(cursor.expected_high_water(), u128::from(expected_committed),);
4688            assert!(!cursor.has_allocations());
4689        });
4690        let committed_description = session
4691            .try_describe_entity_by_name(ENTITY_NAME)
4692            .expect("committed Identity description should resolve");
4693        let committed_identity = committed_description
4694            .identity()
4695            .expect("accepted Identity policy should remain described");
4696        assert_eq!(
4697            committed_identity.high_water(),
4698            u128::from(expected_committed),
4699        );
4700        assert_eq!(
4701            committed_identity.remaining(),
4702            u128::from(u64::MAX - expected_committed),
4703        );
4704        assert!(!committed_identity.exhausted());
4705    }
4706
4707    #[test]
4708    #[expect(
4709        clippy::too_many_lines,
4710        reason = "one ordered scenario exercises every durable interruption boundary, guarded recovery, derived rebuild, and both integrity tiers"
4711    )]
4712    fn journaled_identity_recovery_quiesces_every_publication_interruption_before_reallocation() {
4713        let session = initialize_journaled();
4714        let catalog = session
4715            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
4716            .expect("journaled identity catalog should resolve");
4717        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
4718            .expect("journaled identity row layout should build");
4719
4720        for (ordinal, interruption) in [
4721            MutationCommitInterruption::MarkerPersisted,
4722            MutationCommitInterruption::JournalPublished,
4723            MutationCommitInterruption::RowsPublished,
4724            MutationCommitInterruption::StateMaterialized,
4725        ]
4726        .into_iter()
4727        .enumerate()
4728        {
4729            interrupt_next_mutation_commit_for_tests(interruption);
4730            let interrupted = session.execute_accepted_structural_save_batch(
4731                &catalog,
4732                &descriptor,
4733                batch(&[u64::try_from(ordinal).expect("ordinal should fit")]),
4734                Timestamp::from_millis(8),
4735                Ok,
4736            );
4737            assert!(
4738                interrupted.is_err(),
4739                "the selected durable boundary should interrupt",
4740            );
4741
4742            let committed = session
4743                .execute_accepted_structural_save_batch(
4744                    &catalog,
4745                    &descriptor,
4746                    batch(&[100 + u64::try_from(ordinal).expect("ordinal should fit")]),
4747                    Timestamp::from_millis(9),
4748                    Ok,
4749                )
4750                .expect("the next mutation must recover before allocating");
4751            let expected_high_water =
4752                u64::try_from((ordinal + 1) * 2).expect("small test high-water should fit");
4753            assert_eq!(
4754                committed
4755                    .into_iter()
4756                    .map(|row| row.values)
4757                    .collect::<Vec<_>>(),
4758                vec![vec![
4759                    Value::Nat64(expected_high_water),
4760                    Value::Nat64(100 + u64::try_from(ordinal).expect("ordinal should fit")),
4761                ]],
4762            );
4763            assert_eq!(
4764                JOURNALED_DATA_STORE.with(|store| store.borrow().len()),
4765                expected_high_water,
4766            );
4767            JOURNALED_SCHEMA_STORE.with(|store| {
4768                let cursor = store
4769                    .borrow()
4770                    .identity_statement_cursor(
4771                        database_incarnation_id()
4772                            .expect("database incarnation should remain readable"),
4773                        ENTITY_TAG,
4774                        FieldId::new(1),
4775                        &AcceptedFieldKind::Nat64,
4776                    )
4777                    .expect("guarded recovery must leave quiescent active state");
4778                assert_eq!(
4779                    cursor.expected_high_water(),
4780                    u128::from(expected_high_water),
4781                );
4782                assert!(!cursor.has_allocations());
4783            });
4784        }
4785
4786        for (ordinal, (interruption, deleted_key)) in [
4787            (MutationCommitInterruption::MarkerPersisted, 2),
4788            (MutationCommitInterruption::JournalPublished, 4),
4789            (MutationCommitInterruption::RowPrefixPublished, 6),
4790            (MutationCommitInterruption::RowsPublished, 8),
4791            (MutationCommitInterruption::StateMaterialized, 7),
4792        ]
4793        .into_iter()
4794        .enumerate()
4795        {
4796            let expected_payload =
4797                501 + u64::try_from(ordinal).expect("small interruption ordinal should fit");
4798            interrupt_next_mutation_commit_for_tests(interruption);
4799            let interrupted = session.execute_trusted_dynamic_mutation_batch(vec![
4800                DynamicMutation::Update {
4801                    entity: ENTITY_NAME.to_string(),
4802                    key: InputValue::Nat64(1),
4803                    patch: dynamic_payload_patch(expected_payload),
4804                },
4805                DynamicMutation::Delete {
4806                    entity: ENTITY_NAME.to_string(),
4807                    key: InputValue::Nat64(deleted_key),
4808                },
4809            ]);
4810            assert!(
4811                interrupted.is_err(),
4812                "the selected caller-key mixed publication boundary should interrupt",
4813            );
4814            let recovered_update = session
4815                .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
4816                    entity: ENTITY_NAME.to_string(),
4817                    key: InputValue::Nat64(1),
4818                    patch: dynamic_payload_patch(expected_payload),
4819                })
4820                .expect("guarded reentry should complete the marker-authorized mixed batch");
4821            assert_eq!(
4822                recovered_update.affected_rows, 0,
4823                "the recovered update must already expose its admitted final image",
4824            );
4825            let recovered_delete = session
4826                .execute_trusted_dynamic_mutation(&DynamicMutation::Delete {
4827                    entity: ENTITY_NAME.to_string(),
4828                    key: InputValue::Nat64(deleted_key),
4829                })
4830                .expect_err("the recovered delete must already be materialized");
4831            assert_eq!(recovered_delete.class(), ErrorClass::NotFound);
4832            JOURNALED_SCHEMA_STORE.with(|store| {
4833                let cursor = store
4834                    .borrow()
4835                    .identity_statement_cursor(
4836                        database_incarnation_id()
4837                            .expect("database incarnation should remain readable"),
4838                        ENTITY_TAG,
4839                        FieldId::new(1),
4840                        &AcceptedFieldKind::Nat64,
4841                    )
4842                    .expect("caller-key recovery must preserve active Identity state");
4843                assert_eq!(cursor.expected_high_water(), 8);
4844                assert!(!cursor.has_allocations());
4845            });
4846        }
4847
4848        forget_recovered_domain_for_tests(&session.db)
4849            .expect("the final journal tail should remain recoverable");
4850        session
4851            .db
4852            .ensure_recovered_state()
4853            .expect("derived rebuild must not allocate another identity");
4854
4855        let data_generation = JOURNALED_DATA_STORE.with(|store| store.borrow().generation());
4856        let index_generation = JOURNALED_INDEX_STORE.with(|store| store.borrow().generation());
4857        forget_recovered_domain_for_tests(&session.db)
4858            .expect("an empty-tail upgrade should reset recovery ownership");
4859        session
4860            .db
4861            .ensure_recovered_state()
4862            .expect("an empty-tail upgrade should admit without rebuilding stored rows or indexes");
4863        assert_eq!(
4864            JOURNALED_DATA_STORE.with(|store| store.borrow().generation()),
4865            data_generation,
4866            "empty-tail recovery must not traverse or rewrite authoritative rows",
4867        );
4868        assert_eq!(
4869            JOURNALED_INDEX_STORE.with(|store| store.borrow().generation()),
4870            index_generation,
4871            "empty-tail recovery must not clear or rebuild secondary indexes",
4872        );
4873
4874        let quick = execute_quick_integrity(&session.db, catalog.inspection_plan())
4875            .expect("quiescent Identity control inventory should be inspectable");
4876        assert_eq!(quick.status(), &QuickIntegrityStatus::CompleteClean);
4877        let row_page = execute_row_integrity_page(
4878            &session.db,
4879            catalog.inspection_plan(),
4880            PhysicalUnitCheckpoint::BeforeFirst,
4881            RowInspectionLimits::standard(),
4882        )
4883        .expect("Identity rows should remain within committed high-water");
4884        assert!(row_page.exhausted());
4885        assert!(row_page.findings().is_empty());
4886
4887        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 3);
4888        assert!(
4889            JOURNALED_INDEX_STORE.with(|store| !store.borrow().is_empty()),
4890            "derived index rebuild should restore witnesses without allocating identities",
4891        );
4892        assert!(!JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
4893        JOURNALED_SCHEMA_STORE.with(|store| {
4894            let cursor = store
4895                .borrow()
4896                .identity_statement_cursor(
4897                    database_incarnation_id().expect("database incarnation should remain readable"),
4898                    ENTITY_TAG,
4899                    FieldId::new(1),
4900                    &AcceptedFieldKind::Nat64,
4901                )
4902                .expect("folded identity state should reopen without allocating");
4903            assert_eq!(cursor.expected_high_water(), 8);
4904            assert!(!cursor.has_allocations());
4905        });
4906    }
4907
4908    #[test]
4909    fn journaled_startup_recovery_resumes_by_durable_pages_without_reallocating_ids() {
4910        let session = initialize_journaled();
4911        let catalog = session
4912            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
4913            .expect("journaled identity catalog should resolve");
4914        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
4915            .expect("journaled identity row layout should build");
4916
4917        for payload in 0_u64..129 {
4918            session
4919                .execute_accepted_structural_save_batch(
4920                    &catalog,
4921                    &descriptor,
4922                    batch(&[payload]),
4923                    Timestamp::from_millis(8),
4924                    Ok,
4925                )
4926                .expect("journaled identity fixture row should commit");
4927        }
4928
4929        forget_recovered_domain_for_tests(&session.db)
4930            .expect("upgrade should reset recovery ownership");
4931        assert!(
4932            !session
4933                .db
4934                .continue_startup_recovery()
4935                .expect("the first bounded recovery page should commit"),
4936            "one page must not consume a tail larger than the production page bound",
4937        );
4938        assert!(JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
4939        let mut pages = 1;
4940        while !session
4941            .db
4942            .continue_startup_recovery()
4943            .expect("each bounded recovery page should commit")
4944        {
4945            pages += 1;
4946            assert!(pages <= 4, "the small fixture should finish promptly");
4947        }
4948        assert!(pages >= 2);
4949
4950        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 129);
4951        assert!(!JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
4952        assert_dynamic_payload(&session, 1, 0);
4953        assert_dynamic_payload(&session, 129, 128);
4954        JOURNALED_SCHEMA_STORE.with(|store| {
4955            let cursor = store
4956                .borrow()
4957                .identity_statement_cursor(
4958                    database_incarnation_id().expect("database incarnation should remain readable"),
4959                    ENTITY_TAG,
4960                    FieldId::new(1),
4961                    &AcceptedFieldKind::Nat64,
4962                )
4963                .expect("paged recovery must preserve active Identity state");
4964            assert_eq!(cursor.expected_high_water(), 129);
4965            assert!(!cursor.has_allocations());
4966        });
4967    }
4968
4969    #[test]
4970    fn journaled_startup_recovery_resumes_within_one_large_batch() {
4971        let session = initialize_journaled();
4972        let catalog = session
4973            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
4974            .expect("journaled identity catalog should resolve");
4975        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
4976            .expect("journaled identity row layout should build");
4977        let payloads = (0_u64..129).collect::<Vec<_>>();
4978        session
4979            .execute_accepted_structural_save_batch(
4980                &catalog,
4981                &descriptor,
4982                batch(&payloads),
4983                Timestamp::from_millis(9),
4984                Ok,
4985            )
4986            .expect("one large journal batch should commit");
4987
4988        forget_recovered_domain_for_tests(&session.db)
4989            .expect("upgrade should reset recovery ownership");
4990        assert!(
4991            !session
4992                .db
4993                .continue_startup_recovery()
4994                .expect("the first record-bounded recovery page should commit"),
4995            "a single batch larger than the record bound must remain resumable",
4996        );
4997        JOURNALED_TAIL_STORE.with(|tail| {
4998            let tail = tail.borrow();
4999            let cursor = tail
5000                .fold_record_cursor()
5001                .expect("the fold cursor should decode")
5002                .expect("the incomplete batch should retain a fold cursor");
5003            assert_eq!(cursor.next_record_ordinal(), 128);
5004            assert!(tail.has_stored_batch());
5005        });
5006        assert!(
5007            session
5008                .db
5009                .continue_startup_recovery()
5010                .expect("the terminal record-bounded recovery page should commit"),
5011        );
5012
5013        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 129);
5014        JOURNALED_TAIL_STORE.with(|tail| {
5015            let tail = tail.borrow();
5016            assert!(!tail.has_stored_batch());
5017            assert!(!tail.has_fold_record_cursor());
5018        });
5019        assert_dynamic_payload(&session, 1, 0);
5020        assert_dynamic_payload(&session, 129, 128);
5021    }
5022
5023    #[test]
5024    #[ignore = "release-closeout native timing probe for one marker-authorized Identity recovery"]
5025    fn identity_recovery_closeout_reports_guarded_reentry_time() {
5026        let session = initialize_journaled();
5027        let catalog = session
5028            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
5029            .expect("journaled identity catalog should resolve");
5030        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
5031            .expect("journaled identity row layout should build");
5032
5033        interrupt_next_mutation_commit_for_tests(MutationCommitInterruption::RowsPublished);
5034        let interrupted = session.execute_accepted_structural_save_batch(
5035            &catalog,
5036            &descriptor,
5037            batch(&[1]),
5038            Timestamp::from_millis(10),
5039            Ok,
5040        );
5041        assert!(
5042            interrupted.is_err(),
5043            "the selected publication boundary should interrupt",
5044        );
5045
5046        let start = Instant::now();
5047        let committed = session
5048            .execute_accepted_structural_save_batch(
5049                &catalog,
5050                &descriptor,
5051                batch(&[2]),
5052                Timestamp::from_millis(11),
5053                Ok,
5054            )
5055            .expect("guarded reentry should recover before allocation");
5056        let elapsed = start.elapsed();
5057        assert_eq!(
5058            committed
5059                .into_iter()
5060                .map(|row| row.values)
5061                .collect::<Vec<_>>(),
5062            vec![vec![Value::Nat64(2), Value::Nat64(2)]],
5063        );
5064
5065        println!(
5066            "identity recovery closeout: guarded_reentry_nanos={}",
5067            elapsed.as_nanos(),
5068        );
5069    }
5070}
5071
5072#[cfg(test)]
5073mod targeted_rule_mutation_tests {
5074    use super::{
5075        DbSession, DynamicMutation, DynamicStructuralPatch, DynamicTypedFieldBindingRequest,
5076        DynamicTypedFieldType, DynamicTypedMutation, DynamicWriteCell,
5077    };
5078    use crate::{
5079        db::{
5080            data::{DataStore, encode_input_value_for_candidate_field_contract},
5081            index::IndexStore,
5082            registry::{StoreAllocationIdentities, StoreRegistry, StoreRuntimeStorageCapabilities},
5083            schema::{
5084                AcceptedCheckLiteralV1, AcceptedCompositeCatalog, AcceptedFieldDecodeContract,
5085                AcceptedFieldKind, AcceptedNamedTypeIdentity, AcceptedRuleOperation,
5086                AcceptedRuleTarget, AcceptedSchemaRevision, AcceptedSourceBindingCatalog,
5087                ConstraintOrigin, FieldId, FieldStorageDecode, FieldWriteManagement, LeafCodec,
5088                PersistedFieldSnapshot, PersistedNestedLeafSnapshot, PersistedSchemaSnapshot,
5089                ScalarCodec, SchemaFieldSlot, SchemaFieldWritePolicy, SchemaInsertDefault,
5090                SchemaRowLayout, SchemaStore, SchemaVersion,
5091                accepted_schema_candidate_with_catalogs_for_tests,
5092                build_record_newtype_composite_catalog_for_tests,
5093                empty_accepted_enum_catalog_for_tests, enum_catalog::ValueAdmissionBudget,
5094            },
5095        },
5096        error::InternalError,
5097        traits::{CanisterKind, Path},
5098        types::EntityTag,
5099        value::InputValue,
5100    };
5101    use icydb_schema::{
5102        ConstraintSourceKey, EntitySourceKey, FieldSourceKey, ScalarType, TypeSourceKey,
5103    };
5104    use std::{cell::RefCell, collections::BTreeMap};
5105
5106    const STORE_PATH: &str = "session::write::targeted_rule_mutation_tests::Store";
5107    const ENTITY_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity";
5108    const ID_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity::id";
5109    const PROFILE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity::profile";
5110    const UPDATED_AT_SOURCE: &str =
5111        "session::write::targeted_rule_mutation_tests::Entity::updated_at";
5112    const PROFILE_TYPE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Profile";
5113    const DEGREE_TYPE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Degree";
5114    const DEGREE_MEMBER_SOURCE: &str =
5115        "session::write::targeted_rule_mutation_tests::Profile::degree";
5116    const DEGREE_RULE_SOURCE: &str =
5117        "session::write::targeted_rule_mutation_tests::Profile::degree_multiple";
5118
5119    struct TestCanister;
5120
5121    impl Path for TestCanister {
5122        const PATH: &'static str = "session::write::targeted_rule_mutation_tests::Canister";
5123    }
5124
5125    impl CanisterKind for TestCanister {
5126        const COMMIT_MEMORY_ID: u8 = 43;
5127        const COMMIT_STABLE_KEY: &'static str = "icydb.targeted_mutation_tests.commit.v1";
5128        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 44;
5129        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
5130            "icydb.targeted_mutation_tests.integrity.progress.v1";
5131    }
5132
5133    thread_local! {
5134        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
5135        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
5136        static SCHEMA_STORE: RefCell<SchemaStore> =
5137            const { RefCell::new(SchemaStore::init_heap()) };
5138        static STORE_REGISTRY: StoreRegistry = {
5139            let mut registry = StoreRegistry::new();
5140            registry.register_store(
5141                STORE_PATH,
5142                &DATA_STORE,
5143                &INDEX_STORE,
5144                &SCHEMA_STORE,
5145                StoreAllocationIdentities::absent(),
5146                StoreRuntimeStorageCapabilities::heap(),
5147            ).expect("targeted mutation test store should register");
5148            registry
5149        };
5150    }
5151
5152    fn source<T, E: std::fmt::Debug>(raw: &str, parse: impl FnOnce(String) -> Result<T, E>) -> T {
5153        parse(raw.to_string()).expect("test source identity should admit")
5154    }
5155
5156    fn profile_input(degree: u64) -> InputValue {
5157        InputValue::Map(vec![(
5158            InputValue::Text("degree".to_string()),
5159            InputValue::Nat64(degree),
5160        )])
5161    }
5162
5163    fn structural_patch(id: u64, degree: u64) -> DynamicStructuralPatch {
5164        DynamicStructuralPatch::new(vec![
5165            (
5166                "id".to_string(),
5167                DynamicWriteCell::Value(InputValue::Nat64(id)),
5168            ),
5169            (
5170                "profile".to_string(),
5171                DynamicWriteCell::Value(profile_input(degree)),
5172            ),
5173        ])
5174    }
5175
5176    fn encoded_value(
5177        enum_catalog: &crate::db::schema::AcceptedEnumCatalog,
5178        composite_catalog: &AcceptedCompositeCatalog,
5179        name: &str,
5180        kind: &AcceptedFieldKind,
5181        storage_decode: FieldStorageDecode,
5182        leaf_codec: LeafCodec,
5183        value: InputValue,
5184    ) -> Vec<u8> {
5185        let field = AcceptedFieldDecodeContract::new(name, kind, false, storage_decode, leaf_codec);
5186        encode_input_value_for_candidate_field_contract(
5187            enum_catalog,
5188            composite_catalog,
5189            field,
5190            value,
5191            &mut ValueAdmissionBudget::standard(),
5192        )
5193        .expect("test accepted value should encode")
5194    }
5195
5196    fn nat64_literal(
5197        enum_catalog: &crate::db::schema::AcceptedEnumCatalog,
5198        composite_catalog: &AcceptedCompositeCatalog,
5199        value: u64,
5200    ) -> AcceptedCheckLiteralV1 {
5201        let kind = AcceptedFieldKind::Nat64;
5202        AcceptedCheckLiteralV1::from_accepted_parts(
5203            kind.clone(),
5204            FieldStorageDecode::ByKind,
5205            LeafCodec::Scalar(ScalarCodec::Nat64),
5206            encoded_value(
5207                enum_catalog,
5208                composite_catalog,
5209                "degree_bound",
5210                &kind,
5211                FieldStorageDecode::ByKind,
5212                LeafCodec::Scalar(ScalarCodec::Nat64),
5213                InputValue::Nat64(value),
5214            ),
5215        )
5216    }
5217
5218    fn targeted_constraint_id(error: &InternalError) -> u32 {
5219        let facts = error.diagnostic_facts();
5220        assert!(facts.contains(&(
5221            icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
5222            icydb_diagnostic_code::DiagnosticMutationOperation::Insert.raw(),
5223        )));
5224        assert!(facts.contains(&(icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 0,)));
5225        assert!(facts.contains(&(
5226            icydb_diagnostic_code::DiagnosticFactTag::ConstraintKind,
5227            icydb_diagnostic_code::DiagnosticConstraintKind::TargetedRule.raw(),
5228        )));
5229        assert_eq!(
5230            facts
5231                .iter()
5232                .filter(|(tag, _)| matches!(
5233                    tag,
5234                    icydb_diagnostic_code::DiagnosticFactTag::RootField
5235                        | icydb_diagnostic_code::DiagnosticFactTag::RecordMember
5236                ))
5237                .copied()
5238                .collect::<Vec<_>>(),
5239            vec![
5240                (icydb_diagnostic_code::DiagnosticFactTag::RootField, 2),
5241                (
5242                    icydb_diagnostic_code::DiagnosticFactTag::RecordMember,
5243                    icydb_diagnostic_code::pack_u32_pair(1, 1),
5244                ),
5245            ]
5246        );
5247        let value = facts
5248            .iter()
5249            .find_map(|(tag, value)| {
5250                (*tag == icydb_diagnostic_code::DiagnosticFactTag::ConstraintId).then_some(*value)
5251            })
5252            .expect("targeted mutation should retain its accepted constraint ID");
5253        u32::try_from(value).expect("accepted constraint ID fits u32")
5254    }
5255
5256    #[expect(
5257        clippy::too_many_lines,
5258        reason = "one end-to-end fixture proves every maintained write frontend converges on the same accepted targeted-rule schedule"
5259    )]
5260    #[test]
5261    fn targeted_rules_converge_across_dynamic_typed_sql_default_timestamp_and_batch_writes() {
5262        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
5263        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
5264        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
5265
5266        let entity_tag = EntityTag::new(93);
5267        let enum_catalog = empty_accepted_enum_catalog_for_tests();
5268        let (composite_catalog, profile_type, degree_type, degree_member) =
5269            build_record_newtype_composite_catalog_for_tests(
5270                "tests::TargetedProfile".to_string(),
5271                "degree".to_string(),
5272                "tests::TargetedDegree".to_string(),
5273                AcceptedFieldKind::Nat64,
5274                &enum_catalog,
5275            )
5276            .expect("targeted mutation composites should close");
5277        let profile_kind = AcceptedFieldKind::Composite {
5278            type_id: profile_type,
5279        };
5280        let profile_default = encoded_value(
5281            &enum_catalog,
5282            &composite_catalog,
5283            "profile",
5284            &profile_kind,
5285            FieldStorageDecode::CatalogValue,
5286            LeafCodec::Structural,
5287            profile_input(12),
5288        );
5289        let fields = vec![
5290            PersistedFieldSnapshot::new_initial(
5291                FieldId::new(1),
5292                "id".to_string(),
5293                SchemaFieldSlot::new(0),
5294                AcceptedFieldKind::Nat64,
5295                Vec::new(),
5296                false,
5297                SchemaInsertDefault::None,
5298                FieldStorageDecode::ByKind,
5299                LeafCodec::Scalar(ScalarCodec::Nat64),
5300            ),
5301            PersistedFieldSnapshot::new_initial(
5302                FieldId::new(2),
5303                "profile".to_string(),
5304                SchemaFieldSlot::new(1),
5305                profile_kind,
5306                vec![PersistedNestedLeafSnapshot::new(
5307                    vec!["degree".to_string()],
5308                    AcceptedFieldKind::Composite {
5309                        type_id: degree_type,
5310                    },
5311                    false,
5312                )],
5313                false,
5314                SchemaInsertDefault::SlotPayload(profile_default),
5315                FieldStorageDecode::CatalogValue,
5316                LeafCodec::Structural,
5317            ),
5318            PersistedFieldSnapshot::new_initial_with_write_policy(
5319                FieldId::new(3),
5320                "updated_at".to_string(),
5321                SchemaFieldSlot::new(2),
5322                AcceptedFieldKind::Timestamp,
5323                Vec::new(),
5324                false,
5325                SchemaInsertDefault::None,
5326                SchemaFieldWritePolicy::from_model_policies(
5327                    None,
5328                    Some(FieldWriteManagement::UpdatedAt),
5329                ),
5330                FieldStorageDecode::ByKind,
5331                LeafCodec::Scalar(ScalarCodec::Timestamp),
5332            ),
5333        ];
5334        let mut snapshot = PersistedSchemaSnapshot::new(
5335            SchemaVersion::initial(),
5336            ENTITY_SOURCE.to_string(),
5337            "TargetedMutation".to_string(),
5338            FieldId::new(1),
5339            SchemaRowLayout::initial(
5340                fields
5341                    .iter()
5342                    .map(|field| (field.id(), field.slot()))
5343                    .collect(),
5344            ),
5345            fields,
5346        );
5347        let constraint_catalog = snapshot
5348            .constraint_catalog()
5349            .clone()
5350            .with_added_targeted_rule(
5351                "profile_degree_multiple".to_string(),
5352                ConstraintOrigin::Generated,
5353                AcceptedRuleTarget::new(
5354                    FieldId::new(2),
5355                    AcceptedNamedTypeIdentity::Composite(degree_type),
5356                ),
5357                AcceptedRuleOperation::MultipleOf {
5358                    divisor: nat64_literal(&enum_catalog, &composite_catalog, 5),
5359                },
5360            )
5361            .expect("targeted mutation rule should allocate");
5362        let targeted_rule_id = constraint_catalog
5363            .constraints()
5364            .last()
5365            .expect("targeted mutation rule should persist")
5366            .id();
5367        snapshot = snapshot.with_constraint_catalog(constraint_catalog);
5368
5369        let entity_source = source(ENTITY_SOURCE, EntitySourceKey::try_new);
5370        let id_source = source(ID_SOURCE, FieldSourceKey::try_new);
5371        let profile_source = source(PROFILE_SOURCE, FieldSourceKey::try_new);
5372        let updated_at_source = source(UPDATED_AT_SOURCE, FieldSourceKey::try_new);
5373        let profile_type_source = source(PROFILE_TYPE_SOURCE, TypeSourceKey::try_new);
5374        let degree_type_source = source(DEGREE_TYPE_SOURCE, TypeSourceKey::try_new);
5375        let degree_member_source = source(DEGREE_MEMBER_SOURCE, FieldSourceKey::try_new);
5376        let degree_rule_source = source(DEGREE_RULE_SOURCE, ConstraintSourceKey::try_new);
5377        let source_bindings = AcceptedSourceBindingCatalog::initial_for_tests(
5378            BTreeMap::from([(entity_source, entity_tag)]),
5379            BTreeMap::from([
5380                ((entity_tag, id_source), FieldId::new(1)),
5381                ((entity_tag, profile_source), FieldId::new(2)),
5382                ((entity_tag, updated_at_source), FieldId::new(3)),
5383            ]),
5384            BTreeMap::from([((entity_tag, degree_rule_source), targeted_rule_id)]),
5385            BTreeMap::new(),
5386            BTreeMap::new(),
5387        )
5388        .with_initial_named_types_for_tests(
5389            BTreeMap::from([
5390                (
5391                    profile_type_source,
5392                    AcceptedNamedTypeIdentity::Composite(profile_type),
5393                ),
5394                (
5395                    degree_type_source,
5396                    AcceptedNamedTypeIdentity::Composite(degree_type),
5397                ),
5398            ]),
5399            BTreeMap::new(),
5400            BTreeMap::from([((profile_type, degree_member_source), degree_member)]),
5401        );
5402        let candidate = accepted_schema_candidate_with_catalogs_for_tests(
5403            STORE_PATH,
5404            AcceptedSchemaRevision::INITIAL,
5405            enum_catalog,
5406            composite_catalog,
5407            source_bindings,
5408            BTreeMap::from([(entity_tag, snapshot)]),
5409        );
5410
5411        let session = DbSession::<TestCanister>::new(
5412            &STORE_REGISTRY,
5413            &crate::db::RequestExecutionRoot::__new_runtime_root(),
5414        );
5415        session
5416            .db
5417            .ensure_recovered_state()
5418            .expect("targeted mutation test database should initialize");
5419        let store = session
5420            .db
5421            .store_handle(STORE_PATH)
5422            .expect("targeted mutation test store should resolve");
5423        crate::db::commit::publish_accepted_schema_candidate(
5424            STORE_PATH,
5425            store,
5426            AcceptedSchemaRevision::NONE,
5427            &candidate,
5428        )
5429        .expect("targeted mutation candidate should publish");
5430
5431        let dynamic_error = session
5432            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
5433                entity: "TargetedMutation".to_string(),
5434                patch: structural_patch(1, 12),
5435            })
5436            .expect_err("dynamic write must enforce the targeted rule");
5437        assert_eq!(
5438            targeted_constraint_id(&dynamic_error),
5439            targeted_rule_id.get()
5440        );
5441
5442        let binding = session
5443            .issue_typed_entity_binding(
5444                ENTITY_SOURCE,
5445                &[
5446                    DynamicTypedFieldBindingRequest::new(
5447                        ID_SOURCE.to_string(),
5448                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
5449                        false,
5450                    ),
5451                    DynamicTypedFieldBindingRequest::new(
5452                        PROFILE_SOURCE.to_string(),
5453                        DynamicTypedFieldType::Named(PROFILE_TYPE_SOURCE.to_string()),
5454                        false,
5455                    ),
5456                    DynamicTypedFieldBindingRequest::new(
5457                        UPDATED_AT_SOURCE.to_string(),
5458                        DynamicTypedFieldType::Scalar(ScalarType::Timestamp),
5459                        false,
5460                    ),
5461                ],
5462            )
5463            .expect("targeted typed binding should issue");
5464        let typed_patch = binding
5465            .bind_write_fields(vec![
5466                (
5467                    ID_SOURCE.to_string(),
5468                    DynamicWriteCell::Value(InputValue::Nat64(2)),
5469                ),
5470                (
5471                    PROFILE_SOURCE.to_string(),
5472                    DynamicWriteCell::Value(profile_input(12)),
5473                ),
5474            ])
5475            .expect("targeted typed patch should bind");
5476        let typed_error = session
5477            .execute_trusted_typed_mutation(
5478                &binding,
5479                &DynamicTypedMutation::Insert { patch: typed_patch },
5480            )
5481            .expect_err("typed write must enforce the targeted rule");
5482        assert_eq!(targeted_constraint_id(&typed_error), targeted_rule_id.get());
5483
5484        #[cfg(feature = "sql")]
5485        {
5486            let sql_error = session
5487                .execute_trusted_sql_mutation("INSERT INTO TargetedMutation (id) VALUES (3)")
5488                .expect_err("SQL default resolution must enforce the targeted rule");
5489            let crate::db::QueryError::Execute(execute) = sql_error else {
5490                panic!("targeted SQL write should fail at shared execution admission");
5491            };
5492            assert_eq!(
5493                targeted_constraint_id(execute.as_internal()),
5494                targeted_rule_id.get()
5495            );
5496        }
5497
5498        session
5499            .execute_trusted_dynamic_mutation_batch(vec![
5500                DynamicMutation::Insert {
5501                    entity: "TargetedMutation".to_string(),
5502                    patch: structural_patch(4, 5),
5503                },
5504                DynamicMutation::Insert {
5505                    entity: "TargetedMutation".to_string(),
5506                    patch: structural_patch(5, 12),
5507                },
5508            ])
5509            .expect_err("one invalid targeted value must reject the whole batch");
5510        assert_eq!(
5511            DATA_STORE.with(|store| store.borrow().exact_entity_count(entity_tag)),
5512            Some(0),
5513            "no frontend or earlier valid batch row may escape targeted admission",
5514        );
5515
5516        let admitted = session
5517            .execute_trusted_dynamic_mutation_batch(vec![
5518                DynamicMutation::Insert {
5519                    entity: "TargetedMutation".to_string(),
5520                    patch: structural_patch(6, 5),
5521                },
5522                DynamicMutation::Insert {
5523                    entity: "TargetedMutation".to_string(),
5524                    patch: structural_patch(7, 10),
5525                },
5526            ])
5527            .expect("compliant targeted values should share one accepted batch");
5528        let [first, second] = admitted.rows.as_slice() else {
5529            panic!("the mixed targeted batch should return two rows");
5530        };
5531        let first_timestamp = first
5532            .get(2)
5533            .expect("the first mixed row should contain its managed timestamp");
5534        assert!(matches!(
5535            first_timestamp,
5536            crate::value::OutputValue::Timestamp(_)
5537        ));
5538        assert_eq!(
5539            second.get(2),
5540            Some(first_timestamp),
5541            "one accepted mixed batch must materialize one managed timestamp",
5542        );
5543        assert_eq!(
5544            DATA_STORE.with(|store| store.borrow().exact_entity_count(entity_tag)),
5545            Some(2),
5546        );
5547    }
5548}