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,
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
92struct OrderedAcceptedStructuralMutation {
93    input_ordinal: u32,
94    intent: AcceptedStructuralMutation,
95}
96
97const MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS: usize = 4_096;
98const MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES: usize = 16 * 1024 * 1024;
99const MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES: usize = 1024 * 1024;
100
101fn add_structural_mutation_staged_bytes(
102    total: &mut usize,
103    lengths: impl IntoIterator<Item = usize>,
104) -> Result<(), InternalError> {
105    for length in lengths {
106        *total = total
107            .checked_add(length)
108            .ok_or_else(InternalError::mutation_batch_staged_bytes_exceeded)?;
109        if *total > MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES {
110            return Err(InternalError::mutation_batch_staged_bytes_exceeded());
111        }
112    }
113    Ok(())
114}
115
116fn validate_structural_mutation_result_bytes(encoded_bytes: usize) -> Result<(), InternalError> {
117    if encoded_bytes > MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES {
118        return Err(InternalError::mutation_batch_result_bytes_exceeded());
119    }
120    Ok(())
121}
122
123/// One canonical row produced by structural mutation materialization.
124pub(in crate::db::session) struct AcceptedStructuralMutationRow {
125    values: Vec<Value>,
126    logical_changed: bool,
127}
128
129impl AcceptedStructuralMutationRow {
130    #[cfg(feature = "sql")]
131    pub(in crate::db::session) fn into_values(self) -> Vec<Value> {
132        self.values
133    }
134
135    pub(in crate::db::session) const fn logical_changed(&self) -> bool {
136        self.logical_changed
137    }
138}
139
140const fn dynamic_mutation_mode(request: &DynamicMutation) -> Option<MutationMode> {
141    match request {
142        DynamicMutation::Insert { .. } => Some(MutationMode::Insert),
143        DynamicMutation::Update { .. } => Some(MutationMode::Update),
144        DynamicMutation::Replace { .. } => Some(MutationMode::Replace),
145        DynamicMutation::Delete { .. } => None,
146    }
147}
148
149const fn dynamic_typed_mutation_mode(request: &DynamicTypedMutation) -> MutationMode {
150    match request {
151        DynamicTypedMutation::Insert { .. } => MutationMode::Insert,
152        DynamicTypedMutation::Update { .. } => MutationMode::Update,
153        DynamicTypedMutation::Replace { .. } => MutationMode::Replace,
154    }
155}
156
157const fn dynamic_write_context(operation_timestamp: Timestamp) -> AcceptedWriteContext {
158    AcceptedWriteContext::new(operation_timestamp)
159}
160
161fn insert_key_exists_after_generation(identity_generated: bool) -> InternalError {
162    if identity_generated {
163        InternalError::identity_state_corruption()
164    } else {
165        mutation_key_exists_error()
166    }
167}
168
169fn dynamic_key(
170    entity_tag: crate::types::EntityTag,
171    key: &InputValue,
172) -> Result<DecodedDataStoreKey, InternalError> {
173    let value = key
174        .clone()
175        .try_into_runtime_non_enum()
176        .ok_or_else(InternalError::executor_unsupported)?;
177    DecodedDataStoreKey::try_from_structural_key(entity_tag, &value)
178}
179
180fn lower_dynamic_patch(
181    entity_path: &str,
182    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
183    patch: &DynamicStructuralPatch,
184    mode: MutationMode,
185) -> Result<AcceptedMutationIntentPatch, InternalError> {
186    let mut lowered = AcceptedMutationIntentPatch::new();
187    for (field_name, cell) in patch.fields() {
188        let slot = descriptor
189            .field_slot_index_by_name(field_name)
190            .ok_or_else(|| {
191                InternalError::mutation_structural_field_unknown(entity_path, field_name)
192            })?;
193        let field = descriptor
194            .field_for_slot_index(slot)
195            .ok_or_else(InternalError::executor_invariant)?;
196        if !matches!(cell, DynamicWriteCell::Omitted)
197            && (field.write_policy().insert_generation().is_some()
198                || field.write_policy().write_management().is_some())
199        {
200            return Err(InternalError::mutation_database_owned_field_explicit(
201                entity_path,
202                field.name(),
203            ));
204        }
205        let slot = FieldSlot::from_validated_index(slot);
206        lowered = match cell {
207            DynamicWriteCell::Omitted => lowered,
208            DynamicWriteCell::Default => match mode {
209                MutationMode::Insert | MutationMode::Replace => {
210                    lowered.set_explicit_insert_default(slot)
211                }
212                MutationMode::Update => lowered.set_explicit_update_default(slot),
213            },
214            DynamicWriteCell::Null => lowered.set_authored(slot, InputValue::Null),
215            DynamicWriteCell::Value(value) => lowered.set_authored(slot, value.clone()),
216        };
217    }
218    Ok(lowered)
219}
220
221fn lower_dynamic_mutation_intent(
222    entity_tag: crate::types::EntityTag,
223    entity_path: &str,
224    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
225    request: &DynamicMutation,
226) -> Result<(AcceptedStructuralMutation, Option<SaveMutationKind>), InternalError> {
227    match request {
228        DynamicMutation::Insert { patch, .. } => Ok((
229            AcceptedStructuralMutation::save(
230                MutationMode::Insert,
231                AcceptedStructuralMutationTarget::ResolveFromAfterImage,
232                lower_dynamic_patch(entity_path, descriptor, patch, MutationMode::Insert)?,
233            ),
234            Some(SaveMutationKind::Insert),
235        )),
236        DynamicMutation::Update { key, patch, .. }
237        | DynamicMutation::Replace { key, patch, .. } => {
238            let mode =
239                dynamic_mutation_mode(request).ok_or_else(InternalError::executor_invariant)?;
240            let kind = match mode {
241                MutationMode::Insert => SaveMutationKind::Insert,
242                MutationMode::Replace => SaveMutationKind::Replace,
243                MutationMode::Update => SaveMutationKind::Update,
244            };
245            Ok((
246                AcceptedStructuralMutation::save(
247                    mode,
248                    AcceptedStructuralMutationTarget::expected(dynamic_key(entity_tag, key)?),
249                    lower_dynamic_patch(entity_path, descriptor, patch, mode)?,
250                ),
251                Some(kind),
252            ))
253        }
254        DynamicMutation::Delete { key, .. } => Ok((
255            AcceptedStructuralMutation::delete(dynamic_key(entity_tag, key)?),
256            None,
257        )),
258    }
259}
260
261fn lower_typed_patch(
262    entity_path: &str,
263    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
264    patch: &DynamicTypedStructuralPatch,
265    mode: MutationMode,
266) -> Result<AcceptedMutationIntentPatch, InternalError> {
267    let mut lowered = AcceptedMutationIntentPatch::new();
268    for (field_id, slot, cell) in patch.fields() {
269        let slot_index = usize::from(*slot);
270        let field = descriptor
271            .field_for_slot_index(slot_index)
272            .ok_or_else(InternalError::store_invariant)?;
273        if field.field_id().get() != *field_id {
274            return Err(InternalError::store_invariant());
275        }
276        if !matches!(cell, DynamicWriteCell::Omitted)
277            && (field.write_policy().insert_generation().is_some()
278                || field.write_policy().write_management().is_some())
279        {
280            return Err(InternalError::mutation_database_owned_field_explicit(
281                entity_path,
282                field.name(),
283            ));
284        }
285        let slot = FieldSlot::from_validated_index(slot_index);
286        lowered = match cell {
287            DynamicWriteCell::Omitted => lowered,
288            DynamicWriteCell::Default => match mode {
289                MutationMode::Insert | MutationMode::Replace => {
290                    lowered.set_explicit_insert_default(slot)
291                }
292                MutationMode::Update => lowered.set_explicit_update_default(slot),
293            },
294            DynamicWriteCell::Null => lowered.set_authored(slot, InputValue::Null),
295            DynamicWriteCell::Value(value) => lowered.set_authored(slot, value.clone()),
296        };
297    }
298    Ok(lowered)
299}
300
301fn preserve_dynamic_replacement_identity(
302    key: &DecodedDataStoreKey,
303    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
304    mut patch: AcceptedMutationIntentPatch,
305) -> Result<AcceptedMutationIntentPatch, InternalError> {
306    let primary_key_slots = descriptor.primary_key_slot_indices();
307    let runtime_key = key.primary_key_runtime_value();
308    let components = match runtime_key {
309        Value::List(values) if primary_key_slots.len() > 1 => values,
310        value if primary_key_slots.len() == 1 => vec![value],
311        _ => return Err(InternalError::executor_invariant()),
312    };
313    if components.len() != primary_key_slots.len() {
314        return Err(InternalError::executor_invariant());
315    }
316
317    for (slot, value) in primary_key_slots.iter().copied().zip(components) {
318        let _ = descriptor
319            .field_for_slot_index(slot)
320            .ok_or_else(InternalError::executor_invariant)?;
321        let has_explicit_intent = patch
322            .entries()
323            .iter()
324            .any(|entry| entry.slot().index() == slot);
325        if has_explicit_intent {
326            continue;
327        }
328        let value = InputValue::try_from_runtime_non_enum(&value)
329            .ok_or_else(InternalError::executor_invariant)?;
330        patch =
331            patch.set_preserved_replacement_identity(FieldSlot::from_validated_index(slot), value);
332    }
333
334    Ok(patch)
335}
336
337// Locate the sole accepted Identity owner that is eligible to resolve a
338// keyless insert. Accepted-schema integrity already freezes the exact shape;
339// this runtime check fails closed if a malformed contract reaches execution.
340fn accepted_identity_insert_field(
341    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
342) -> Result<Option<AcceptedIdentityInsertField>, InternalError> {
343    let mut identity = None;
344    for field in descriptor.fields() {
345        if field.write_policy().insert_generation() != Some(FieldInsertGeneration::Identity) {
346            continue;
347        }
348        let field_slot = usize::from(field.slot().get());
349        if identity
350            .replace(AcceptedIdentityInsertField {
351                field_id: field.field_id(),
352                field_slot,
353                accepted_kind: field.kind().clone(),
354            })
355            .is_some()
356            || descriptor.primary_key_slot_indices() != [field_slot]
357        {
358            return Err(InternalError::identity_corruption());
359        }
360    }
361    Ok(identity)
362}
363
364fn checked_pre_key_candidate_count(count: usize) -> Result<u32, InternalError> {
365    u32::try_from(count).map_err(|_| InternalError::identity_candidate_count_exhausted())
366}
367
368fn validate_identity_materialization(
369    entity_tag: crate::types::EntityTag,
370    identity_field: &AcceptedIdentityInsertField,
371    candidate: &AcceptedPreKeyInsert,
372    allocation: &AcceptedIdentityAllocation,
373    data_key: &DecodedDataStoreKey,
374    reader: &StructuralSlotReader<'_>,
375) -> Result<(), InternalError> {
376    let owner = allocation.owner();
377    let slot_value = reader.required_cached_value(identity_field.field_slot)?;
378    if candidate.entity_tag() != entity_tag
379        || candidate.input_ordinal() != allocation.input_ordinal()
380        || owner.entity_tag() != entity_tag
381        || owner.field_id() != identity_field.field_id
382        || allocation.field_slot() != identity_field.field_slot
383        || slot_value != allocation.value()
384        || data_key.primary_key_runtime_value() != *allocation.value()
385    {
386        return Err(InternalError::identity_corruption());
387    }
388    Ok(())
389}
390
391fn data_key_from_row(
392    entity_tag: crate::types::EntityTag,
393    contract: &StructuralRowContract,
394    row: &RawRow,
395) -> Result<DecodedDataStoreKey, InternalError> {
396    let reader =
397        StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(row, contract)?;
398    let values = contract
399        .primary_key_slot_indices()
400        .iter()
401        .map(|slot| reader.required_cached_value(*slot).cloned())
402        .collect::<Result<Vec<_>, _>>()?;
403    let value = match values.as_slice() {
404        [value] => value.clone(),
405        _ => Value::List(values),
406    };
407    DecodedDataStoreKey::try_from_structural_key(entity_tag, &value)
408}
409
410#[cfg(feature = "sql")]
411pub(in crate::db::session) fn structural_data_key_from_runtime_values(
412    entity_tag: crate::types::EntityTag,
413    values: Vec<Value>,
414) -> Result<DecodedDataStoreKey, InternalError> {
415    let value = match values.as_slice() {
416        [value] => value.clone(),
417        _ => Value::List(values),
418    };
419    DecodedDataStoreKey::try_from_structural_key(entity_tag, &value)
420}
421
422fn validated_existing_row(
423    store: crate::db::registry::StoreHandle,
424    data_key: &DecodedDataStoreKey,
425    contract: &StructuralRowContract,
426) -> Result<Option<RawRow>, InternalError> {
427    let raw_key = data_key.to_raw()?;
428    let row = store.with_data(|data| data.get(&raw_key));
429    if let Some(row) = row.as_ref() {
430        let reader =
431            StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(row, contract)?;
432        reader.validate_primary_key(data_key)?;
433    }
434    Ok(row)
435}
436
437fn prepare_dynamic_mutation_result(
438    catalog: &AcceptedSchemaCatalogContext,
439    descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
440    rows: Vec<AcceptedStructuralMutationRow>,
441    enforce_mixed_batch_result_bound: bool,
442) -> Result<DynamicMutationResult, InternalError> {
443    let affected_rows = rows.iter().try_fold(0_u32, |total, row| {
444        total
445            .checked_add(u32::from(row.logical_changed()))
446            .ok_or_else(InternalError::executor_invariant)
447    })?;
448    let columns = descriptor
449        .fields()
450        .iter()
451        .map(|field| field.name().to_string())
452        .collect();
453    let rows = rows
454        .into_iter()
455        .map(|row| {
456            row.values
457                .iter()
458                .map(|value| {
459                    output_value_from_runtime(catalog.enum_catalog(), value)
460                        .map_err(|_| InternalError::store_invariant())
461                })
462                .collect::<Result<Vec<_>, _>>()
463        })
464        .collect::<Result<Vec<_>, _>>()?;
465    let result = DynamicMutationResult {
466        entity: catalog.snapshot().entity_name().to_string(),
467        columns,
468        rows,
469        affected_rows,
470    };
471    if enforce_mixed_batch_result_bound {
472        let encoded =
473            candid::encode_one(&result).map_err(|_| InternalError::executor_invariant())?;
474        validate_structural_mutation_result_bytes(encoded.len())?;
475    }
476    Ok(result)
477}
478
479fn dynamic_typed_field_type(
480    field_type: DynamicTypedFieldType,
481) -> Result<FieldType, DynamicTypedBindingError> {
482    match field_type {
483        DynamicTypedFieldType::Scalar(scalar) => Ok(FieldType::Scalar(scalar)),
484        DynamicTypedFieldType::List(item) => {
485            Ok(FieldType::List(Box::new(dynamic_typed_field_type(*item)?)))
486        }
487        DynamicTypedFieldType::Named(source_key) => TypeSourceKey::try_new(source_key)
488            .map(FieldType::Named)
489            .map_err(|_| DynamicTypedBindingError::FieldUnavailable),
490    }
491}
492
493fn typed_adapter_field_kind_matches(
494    accepted: &AcceptedFieldKind,
495    expected: &AcceptedFieldKind,
496) -> bool {
497    if accepted == expected {
498        return true;
499    }
500    match (accepted, expected) {
501        (AcceptedFieldKind::Relation { key_kind, .. }, expected) => {
502            typed_adapter_field_kind_matches(key_kind, expected)
503        }
504        (AcceptedFieldKind::List(accepted), AcceptedFieldKind::List(expected)) => {
505            typed_adapter_field_kind_matches(accepted, expected)
506        }
507        _ => false,
508    }
509}
510
511impl<C: CanisterKind> DbSession<C> {
512    /// Issue one opaque accepted binding for immutable generated source keys.
513    pub fn issue_typed_entity_binding(
514        &self,
515        entity_source_key: &str,
516        field_requests: &[DynamicTypedFieldBindingRequest],
517    ) -> Result<DynamicTypedEntityBinding, DynamicTypedBindingError> {
518        let entity_source = EntitySourceKey::try_new(entity_source_key)
519            .map_err(|_| DynamicTypedBindingError::FieldUnavailable)?;
520        let field_requests = field_requests
521            .iter()
522            .map(|request| {
523                Ok((
524                    FieldSourceKey::try_new(request.source_key.clone())
525                        .map_err(|_| DynamicTypedBindingError::FieldUnavailable)?,
526                    dynamic_typed_field_type(request.field_type.clone())?,
527                    request.nullable,
528                ))
529            })
530            .collect::<Result<Vec<_>, DynamicTypedBindingError>>()?;
531        let catalog = self
532            .find_accepted_schema_catalog_context_for_entity_source_key(entity_source.as_str())?
533            .ok_or(DynamicTypedBindingError::FieldUnavailable)?;
534        let identity = catalog.identity();
535        if identity.entity_path() != entity_source.as_str() {
536            return Err(InternalError::store_invariant().into());
537        }
538        let store = self.db.recovered_store(identity.store_path())?;
539        let bundle = store
540            .with_schema(crate::db::schema::SchemaStore::current_accepted_schema_bundle)?
541            .ok_or_else(InternalError::store_invariant)?;
542        let entity_tag = identity.entity_tag();
543        if bundle.source_bindings().entity(&entity_source) != Some(entity_tag)
544            || bundle.revision() != catalog.revision()
545        {
546            return Err(InternalError::store_invariant().into());
547        }
548        let snapshot = bundle
549            .entity_snapshots()
550            .get(&entity_tag)
551            .ok_or_else(InternalError::store_invariant)?;
552        let descriptor =
553            AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())?;
554        let mut fields = Vec::with_capacity(field_requests.len());
555        for (source, field_type, nullable) in &field_requests {
556            let field_id = bundle
557                .source_bindings()
558                .field(entity_tag, source)
559                .ok_or(DynamicTypedBindingError::FieldUnavailable)?;
560            let field = snapshot
561                .fields()
562                .iter()
563                .find(|field| field.id() == field_id)
564                .ok_or_else(InternalError::store_invariant)?;
565            let runtime_field = descriptor
566                .field_for_slot_index(usize::from(field.slot().get()))
567                .ok_or_else(InternalError::store_invariant)?;
568            if runtime_field.field_id() != field_id {
569                return Err(InternalError::store_invariant().into());
570            }
571            let expected_kind = lower_field_type(field_type, |source| {
572                bundle.source_bindings().named_type(source)
573            })
574            .map_err(|_| DynamicTypedBindingError::IncompatibleField)?;
575            if field.nullable() != *nullable
576                || !typed_adapter_field_kind_matches(field.kind(), &expected_kind)
577            {
578                return Err(DynamicTypedBindingError::IncompatibleField);
579            }
580            fields.push((
581                source.as_str().to_string(),
582                field_id.get(),
583                field.slot().get(),
584                field.name().to_string(),
585            ));
586        }
587        let adapter_names = bundle.typed_adapter_names()?;
588
589        DynamicTypedEntityBinding::new(
590            database_incarnation_id()?.to_bytes(),
591            entity_source.as_str().to_string(),
592            snapshot.entity_name().to_string(),
593            entity_tag.value(),
594            catalog.revision().get(),
595            catalog.fingerprint(),
596            descriptor.current_layout_version().get(),
597            fields,
598            adapter_names.named_types,
599            adapter_names.enum_variants,
600            adapter_names.composite_fields,
601        )
602        .map_err(Into::into)
603    }
604
605    pub(in crate::db::session) fn current_typed_entity_binding_catalog(
606        &self,
607        binding: &DynamicTypedEntityBinding,
608    ) -> Result<Option<AcceptedSchemaCatalogContext>, InternalError> {
609        if database_incarnation_id()?.to_bytes() != binding.database_incarnation {
610            return Ok(None);
611        }
612        let Some(catalog) = self.find_accepted_schema_catalog_context_for_entity_source_key(
613            binding.entity_source.as_str(),
614        )?
615        else {
616            return Ok(None);
617        };
618        let descriptor =
619            AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())?;
620        let identity = catalog.identity();
621        if identity.entity_path() != binding.entity_source.as_str()
622            || identity.entity_tag().value() != binding.entity_tag
623            || catalog.revision().get() != binding.accepted_revision
624            || catalog.fingerprint() != binding.accepted_fingerprint
625            || descriptor.current_layout_version().get() != binding.entity_generation
626        {
627            return Ok(None);
628        }
629        let entity_source = EntitySourceKey::try_new(binding.entity_source.clone())
630            .map_err(|_| InternalError::store_invariant())?;
631        let store = self.db.recovered_store(identity.store_path())?;
632        let bundle = store
633            .with_schema(crate::db::schema::SchemaStore::current_accepted_schema_bundle)?
634            .ok_or_else(InternalError::store_invariant)?;
635        if bundle.revision() != catalog.revision()
636            || bundle.source_bindings().entity(&entity_source) != Some(identity.entity_tag())
637        {
638            return Ok(None);
639        }
640        let snapshot = bundle
641            .entity_snapshots()
642            .get(&identity.entity_tag())
643            .ok_or_else(InternalError::store_invariant)?;
644        for (source_key, expected_field_id, expected_slot) in binding.field_identity_bindings() {
645            let source = FieldSourceKey::try_new(source_key)
646                .map_err(|_| InternalError::store_invariant())?;
647            let Some(field_id) = bundle
648                .source_bindings()
649                .field(identity.entity_tag(), &source)
650            else {
651                return Ok(None);
652            };
653            let Some(field) = snapshot
654                .fields()
655                .iter()
656                .find(|field| field.id() == field_id)
657            else {
658                return Err(InternalError::store_invariant());
659            };
660            if field_id.get() != expected_field_id || field.slot().get() != expected_slot {
661                return Ok(None);
662            }
663        }
664        Ok(Some(catalog))
665    }
666
667    /// Verify that an opaque typed binding still names the exact accepted authority.
668    pub fn typed_entity_binding_is_current(
669        &self,
670        binding: &DynamicTypedEntityBinding,
671    ) -> Result<bool, InternalError> {
672        self.current_typed_entity_binding_catalog(binding)
673            .map(|catalog| catalog.is_some())
674    }
675
676    /// Materialize one accepted delete batch, run bounded frontend validation,
677    /// then commit it atomically.
678    #[cfg(feature = "sql")]
679    pub(in crate::db::session) fn execute_accepted_structural_delete_batch(
680        &self,
681        catalog: &AcceptedSchemaCatalogContext,
682        descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
683        keys: Vec<DecodedDataStoreKey>,
684        precommit_validation: impl FnOnce(&[Vec<Value>]) -> Result<(), InternalError>,
685    ) -> Result<Vec<Vec<Value>>, InternalError> {
686        let mutations = keys
687            .into_iter()
688            .map(AcceptedStructuralMutation::delete)
689            .collect();
690        self.execute_accepted_structural_mutation_batch_inner(
691            catalog,
692            descriptor,
693            mutations,
694            Timestamp::now(),
695            false,
696            |rows| {
697                let rows = rows
698                    .into_iter()
699                    .map(AcceptedStructuralMutationRow::into_values)
700                    .collect::<Vec<_>>();
701                precommit_validation(rows.as_slice())?;
702                Ok(rows)
703            },
704        )
705    }
706
707    /// Materialize one accepted structural batch, let its caller prepare and
708    /// validate the final after-images, then commit atomically.
709    ///
710    /// The caller freezes one operation timestamp and supplies frontend-lowered
711    /// intent only. Accepted defaults, generated values, managed timestamps,
712    /// constraints, relations, row encoding, and commit preparation remain
713    /// owned by this database boundary.
714    pub(in crate::db::session) fn execute_accepted_structural_save_batch<T>(
715        &self,
716        catalog: &AcceptedSchemaCatalogContext,
717        descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
718        mutations: Vec<AcceptedStructuralMutation>,
719        operation_timestamp: Timestamp,
720        precommit_preparation: impl FnOnce(
721            Vec<AcceptedStructuralMutationRow>,
722        ) -> Result<T, InternalError>,
723    ) -> Result<T, InternalError> {
724        self.execute_accepted_structural_mutation_batch_inner(
725            catalog,
726            descriptor,
727            mutations,
728            operation_timestamp,
729            false,
730            precommit_preparation,
731        )
732    }
733
734    /// Commit the largest durable prefix of one accepted resumable update page.
735    #[cfg(feature = "sql")]
736    pub(in crate::db::session) fn execute_accepted_structural_update_prefix(
737        &self,
738        catalog: &AcceptedSchemaCatalogContext,
739        descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
740        mutations: Vec<AcceptedStructuralMutation>,
741        operation_timestamp: Timestamp,
742    ) -> Result<usize, InternalError> {
743        self.execute_accepted_structural_mutation_batch_inner(
744            catalog,
745            descriptor,
746            mutations,
747            operation_timestamp,
748            true,
749            |rows| Ok(rows.len()),
750        )
751    }
752
753    #[expect(
754        clippy::too_many_lines,
755        reason = "one phased owner keeps accepted authority, mutation context, precommit preparation, output capture, and commit staging inseparable"
756    )]
757    fn execute_accepted_structural_mutation_batch_inner<T>(
758        &self,
759        catalog: &AcceptedSchemaCatalogContext,
760        descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
761        mutations: Vec<AcceptedStructuralMutation>,
762        operation_timestamp: Timestamp,
763        largest_journaled_prefix: bool,
764        precommit_preparation: impl FnOnce(
765            Vec<AcceptedStructuralMutationRow>,
766        ) -> Result<T, InternalError>,
767    ) -> Result<T, InternalError> {
768        let identity = catalog.identity();
769        let entity_path = identity.entity_path();
770        let store_path = identity.store_path();
771        let row_decode_contract =
772            descriptor.row_decode_contract(catalog.value_catalog_handle().clone());
773        let row_contract = StructuralRowContract::from_accepted_decode_contract(
774            entity_path,
775            row_decode_contract.clone(),
776        );
777        let store = self.db.recovered_store(store_path)?;
778        let write_context = dynamic_write_context(operation_timestamp);
779        let identity_field = accepted_identity_insert_field(descriptor)?;
780        let identity_incarnation = identity_field
781            .as_ref()
782            .map(|_| database_incarnation_id())
783            .transpose()?;
784        if mutations.len() > MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS {
785            return Err(InternalError::mutation_batch_too_many_items());
786        }
787        let identity_candidate_count = mutations
788            .iter()
789            .filter(|mutation| {
790                matches!(
791                    mutation,
792                    AcceptedStructuralMutation::Save {
793                        mode: MutationMode::Insert,
794                        target: AcceptedStructuralMutationTarget::ResolveFromAfterImage,
795                        ..
796                    }
797                )
798            })
799            .count();
800        let _ = checked_pre_key_candidate_count(identity_candidate_count)?;
801        let mutations = mutations
802            .into_iter()
803            .enumerate()
804            .map(|(input_index, intent)| {
805                u32::try_from(input_index)
806                    .map(|input_ordinal| OrderedAcceptedStructuralMutation {
807                        input_ordinal,
808                        intent,
809                    })
810                    .map_err(|_| InternalError::mutation_batch_too_many_items())
811            })
812            .collect::<Result<Vec<_>, _>>()?;
813        let mut identity_cursor: Option<IdentityStatementCursor> = None;
814        let mut identity_insert_ordinal = 0_u32;
815        let mut scheduler = AcceptedMutationConstraintScheduler::new(
816            entity_path,
817            row_decode_contract.clone(),
818            catalog.fingerprint(),
819            catalog.accepted_row_constraints(),
820            mutations.len(),
821        );
822        let mut output = Vec::with_capacity(mutations.len());
823        let mut staged_bytes = 0_usize;
824
825        for mutation in mutations {
826            let batch_input_ordinal = mutation.input_ordinal;
827            let mutation = mutation.intent;
828            let AcceptedStructuralMutation::Save {
829                mode,
830                target,
831                patch: authored_patch,
832            } = mutation
833            else {
834                let AcceptedStructuralMutation::Delete { key } = mutation else {
835                    return Err(InternalError::executor_invariant());
836                };
837                let before = validated_existing_row(store, &key, &row_contract)?
838                    .ok_or_else(|| InternalError::store_not_found(&key))?;
839                let raw_key = key.to_raw()?;
840                let canonical_before = canonical_row_from_raw_row_with_accepted_decode_contract(
841                    entity_path,
842                    row_decode_contract.clone(),
843                    &before,
844                )?;
845                add_structural_mutation_staged_bytes(
846                    &mut staged_bytes,
847                    [
848                        raw_key.as_bytes().len(),
849                        canonical_before.as_raw_row().as_bytes().len(),
850                    ],
851                )?;
852                scheduler.schedule_delete(CommitRowOp::new(
853                    entity_path,
854                    raw_key,
855                    Some(canonical_before.as_raw_row().as_bytes().to_vec()),
856                    None,
857                    catalog.fingerprint(),
858                ))?;
859                let reader = StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(
860                    canonical_before.as_raw_row(),
861                    &row_contract,
862                )?;
863                let mut values = Vec::with_capacity(descriptor.fields().len());
864                for field in descriptor.fields() {
865                    values.push(
866                        reader
867                            .required_cached_value(usize::from(field.slot().get()))?
868                            .clone(),
869                    );
870                }
871                output.push(AcceptedStructuralMutationRow {
872                    values,
873                    logical_changed: true,
874                });
875                continue;
876            };
877            let (expected_key, pre_key_insert, mut keyed_patch) = match target {
878                AcceptedStructuralMutationTarget::ResolveFromAfterImage => {
879                    let candidate_ordinal =
880                        if identity_field.is_some() && matches!(mode, MutationMode::Insert) {
881                            identity_insert_ordinal
882                        } else {
883                            batch_input_ordinal
884                        };
885                    (
886                        None,
887                        Some(AcceptedPreKeyInsert::new(
888                            identity.entity_tag(),
889                            authored_patch,
890                            candidate_ordinal,
891                        )),
892                        None,
893                    )
894                }
895                AcceptedStructuralMutationTarget::Expected(key) => {
896                    (Some(*key), None, Some(authored_patch))
897                }
898            };
899            if matches!(mode, MutationMode::Replace)
900                && let Some(key) = expected_key.as_ref()
901            {
902                let patch = keyed_patch
903                    .take()
904                    .ok_or_else(InternalError::executor_invariant)?;
905                keyed_patch = Some(preserve_dynamic_replacement_identity(
906                    key, descriptor, patch,
907                )?);
908            }
909            let patch = pre_key_insert
910                .as_ref()
911                .map(AcceptedPreKeyInsert::fields)
912                .or(keyed_patch.as_ref())
913                .ok_or_else(InternalError::executor_invariant)?;
914            let before = expected_key
915                .as_ref()
916                .map(|key| validated_existing_row(store, key, &row_contract))
917                .transpose()?
918                .flatten();
919            match mode {
920                MutationMode::Insert if before.is_some() => {
921                    return Err(mutation_key_exists_error());
922                }
923                MutationMode::Update if before.is_none() => {
924                    let key = expected_key
925                        .as_ref()
926                        .ok_or_else(InternalError::executor_invariant)?;
927                    return Err(InternalError::store_not_found(key));
928                }
929                MutationMode::Insert | MutationMode::Replace | MutationMode::Update => {}
930            }
931
932            let identity_allocation = if let Some(identity_field) = identity_field.as_ref()
933                && matches!(mode, MutationMode::Insert)
934                && before.is_none()
935            {
936                let candidate = pre_key_insert.as_ref().ok_or_else(|| {
937                    let field_name = descriptor
938                        .field_for_slot_index(identity_field.field_slot)
939                        .map_or("", |field| field.name());
940                    InternalError::mutation_database_owned_field_explicit(entity_path, field_name)
941                })?;
942                if identity_cursor.is_none() {
943                    let incarnation = identity_incarnation
944                        .ok_or_else(InternalError::identity_state_corruption)?;
945                    identity_cursor = Some(store.with_schema(|schema_store| {
946                        schema_store.identity_statement_cursor(
947                            incarnation,
948                            identity.entity_tag(),
949                            identity_field.field_id,
950                            &identity_field.accepted_kind,
951                        )
952                    })?);
953                }
954                let allocation = identity_cursor
955                    .as_mut()
956                    .ok_or_else(InternalError::identity_state_corruption)?
957                    .allocate(identity_field.field_slot, candidate.input_ordinal())?;
958                identity_insert_ordinal = identity_insert_ordinal
959                    .checked_add(1)
960                    .ok_or_else(InternalError::identity_candidate_count_exhausted)?;
961                Some(allocation)
962            } else if let Some(identity_field) = identity_field.as_ref()
963                && matches!(mode, MutationMode::Replace)
964                && before.is_none()
965            {
966                let field_name = descriptor
967                    .field_for_slot_index(identity_field.field_slot)
968                    .map_or("", |field| field.name());
969                return Err(InternalError::mutation_database_owned_field_explicit(
970                    entity_path,
971                    field_name,
972                ));
973            } else {
974                None
975            };
976
977            let resolved = match (mode, before.as_ref()) {
978                (MutationMode::Insert | MutationMode::Replace, None) => {
979                    resolve_insert_structural_patch_with_accepted_contract(
980                        entity_path,
981                        row_decode_contract.clone(),
982                        catalog.fingerprint(),
983                        catalog.accepted_row_constraints(),
984                        patch,
985                        write_context,
986                        identity_allocation.as_ref(),
987                    )?
988                }
989                (MutationMode::Update, Some(before)) => {
990                    resolve_update_structural_patch_with_accepted_contract(
991                        entity_path,
992                        row_decode_contract.clone(),
993                        catalog.fingerprint(),
994                        catalog.accepted_row_constraints(),
995                        before,
996                        patch,
997                        write_context,
998                    )?
999                }
1000                (MutationMode::Replace, Some(before)) => {
1001                    resolve_existing_replace_structural_patch_with_accepted_contract(
1002                        entity_path,
1003                        row_decode_contract.clone(),
1004                        catalog.fingerprint(),
1005                        catalog.accepted_row_constraints(),
1006                        before,
1007                        patch,
1008                        write_context,
1009                    )?
1010                }
1011                (MutationMode::Insert, Some(_)) | (MutationMode::Update, None) => {
1012                    return Err(InternalError::executor_invariant());
1013                }
1014            };
1015            let (after, provenance) = resolved.into_parts();
1016            let reader = StructuralSlotReader::from_raw_row_with_validated_borrowed_contract(
1017                after.as_raw_row(),
1018                &row_contract,
1019            )?;
1020            let data_key = match expected_key {
1021                Some(key) => {
1022                    reader.validate_primary_key(&key)?;
1023                    key
1024                }
1025                None => {
1026                    data_key_from_row(identity.entity_tag(), &row_contract, after.as_raw_row())?
1027                }
1028            };
1029            if let Some(allocation) = identity_allocation.as_ref() {
1030                validate_identity_materialization(
1031                    identity.entity_tag(),
1032                    identity_field
1033                        .as_ref()
1034                        .ok_or_else(InternalError::identity_corruption)?,
1035                    pre_key_insert
1036                        .as_ref()
1037                        .ok_or_else(InternalError::identity_corruption)?,
1038                    allocation,
1039                    &data_key,
1040                    &reader,
1041                )?;
1042            }
1043            if matches!(mode, MutationMode::Insert)
1044                && validated_existing_row(store, &data_key, &row_contract)?.is_some()
1045            {
1046                return Err(insert_key_exists_after_generation(
1047                    identity_allocation.is_some(),
1048                ));
1049            }
1050            let raw_key = data_key.to_raw()?;
1051            let canonical_before = before
1052                .as_ref()
1053                .map(|before| {
1054                    canonical_row_from_raw_row_with_accepted_decode_contract(
1055                        entity_path,
1056                        row_decode_contract.clone(),
1057                        before,
1058                    )
1059                })
1060                .transpose()?;
1061            let logical_changed = canonical_before.as_ref().is_none_or(|before| {
1062                before.as_raw_row().as_bytes() != after.as_raw_row().as_bytes()
1063            });
1064            let physical_changed = before
1065                .as_ref()
1066                .is_none_or(|before| before.as_bytes() != after.as_raw_row().as_bytes());
1067            add_structural_mutation_staged_bytes(
1068                &mut staged_bytes,
1069                [
1070                    raw_key.as_bytes().len(),
1071                    canonical_before
1072                        .as_ref()
1073                        .map_or(0, |before| before.as_raw_row().as_bytes().len()),
1074                    after.as_raw_row().as_bytes().len(),
1075                ],
1076            )?;
1077            let row_op = physical_changed.then(|| {
1078                CommitRowOp::new(
1079                    entity_path,
1080                    raw_key.clone(),
1081                    canonical_before
1082                        .as_ref()
1083                        .map(|before| before.as_raw_row().as_bytes().to_vec()),
1084                    Some(after.as_raw_row().as_bytes().to_vec()),
1085                    catalog.fingerprint(),
1086                )
1087            });
1088            scheduler.schedule_save_after_image(
1089                mode,
1090                &data_key,
1091                after.as_raw_row(),
1092                provenance.as_slice(),
1093                row_op,
1094            )?;
1095            if physical_changed {
1096                #[cfg(feature = "sql")]
1097                if largest_journaled_prefix
1098                    && !crate::db::commit::journaled_row_ops_fit_commit_window(scheduler.rows())
1099                {
1100                    scheduler.pop_last_save_row()?;
1101                    if output.is_empty() {
1102                        return Err(InternalError::query_sql_write_boundary(
1103                            icydb_diagnostic_code::SqlWriteBoundaryCode::ResumableUpdateSingleRowResourceExceeded,
1104                        ));
1105                    }
1106                    break;
1107                }
1108            }
1109
1110            let mut values = Vec::with_capacity(descriptor.fields().len());
1111            for field in descriptor.fields() {
1112                values.push(
1113                    reader
1114                        .required_cached_value(usize::from(field.slot().get()))?
1115                        .clone(),
1116                );
1117            }
1118            output.push(AcceptedStructuralMutationRow {
1119                values,
1120                logical_changed,
1121            });
1122        }
1123
1124        #[cfg(not(feature = "sql"))]
1125        let _ = largest_journaled_prefix;
1126
1127        let batch = scheduler.finish();
1128        let prepared = precommit_preparation(output)?;
1129        let identity_ranges = identity_cursor
1130            .map(IdentityStatementCursor::into_range_advance)
1131            .transpose()?
1132            .into_iter()
1133            .flatten()
1134            .collect::<Vec<_>>();
1135        if batch.is_empty() && !identity_ranges.is_empty() {
1136            return Err(InternalError::identity_corruption());
1137        }
1138        if !batch.is_empty() {
1139            commit_structural_row_ops_with_window_for_path(
1140                &self.db,
1141                entity_path,
1142                batch,
1143                identity_ranges,
1144                "accepted_structural_batch_apply",
1145            )?;
1146        }
1147        Ok(prepared)
1148    }
1149
1150    fn execute_one_accepted_save_mutation(
1151        &self,
1152        catalog: &AcceptedSchemaCatalogContext,
1153        descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
1154        mode: MutationMode,
1155        target: AcceptedStructuralMutationTarget,
1156        patch: AcceptedMutationIntentPatch,
1157    ) -> Result<DynamicMutationResult, InternalError> {
1158        let identity = catalog.identity();
1159        let entity_path = identity.entity_path();
1160        let result = self.execute_accepted_structural_save_batch(
1161            catalog,
1162            descriptor,
1163            vec![AcceptedStructuralMutation::save(mode, target, patch)],
1164            Timestamp::now(),
1165            |rows| prepare_dynamic_mutation_result(catalog, descriptor, rows, false),
1166        )?;
1167        record(MetricsEvent::SaveMutation {
1168            entity_path: entity_path.into(),
1169            kind: match mode {
1170                MutationMode::Insert => SaveMutationKind::Insert,
1171                MutationMode::Replace => SaveMutationKind::Replace,
1172                MutationMode::Update => SaveMutationKind::Update,
1173            },
1174            rows_touched: u64::from(result.affected_rows),
1175        });
1176        Ok(result)
1177    }
1178
1179    /// Execute one trusted entity-name-driven structural mutation.
1180    ///
1181    /// This lane resolves public values, defaults, generation, management,
1182    /// constraints, relations, and commit preparation from accepted schema.
1183    /// It never materializes a generated entity or invokes application
1184    /// validators/normalizers.
1185    pub fn execute_trusted_dynamic_mutation(
1186        &self,
1187        request: &DynamicMutation,
1188    ) -> Result<DynamicMutationResult, InternalError> {
1189        self.execute_trusted_dynamic_mutation_batch_with_result_policy(vec![request.clone()], false)
1190    }
1191
1192    /// Execute one bounded same-entity structural mutation batch atomically.
1193    ///
1194    /// Every item binds to the same accepted catalog identity, shares one
1195    /// operation timestamp, and is projected to its public result before the
1196    /// commit marker can be published.
1197    pub fn execute_trusted_dynamic_mutation_batch(
1198        &self,
1199        requests: Vec<DynamicMutation>,
1200    ) -> Result<DynamicMutationResult, InternalError> {
1201        self.execute_trusted_dynamic_mutation_batch_with_result_policy(requests, true)
1202    }
1203
1204    fn execute_trusted_dynamic_mutation_batch_with_result_policy(
1205        &self,
1206        requests: Vec<DynamicMutation>,
1207        enforce_mixed_batch_result_bound: bool,
1208    ) -> Result<DynamicMutationResult, InternalError> {
1209        if requests.is_empty() {
1210            return Err(InternalError::mutation_batch_empty());
1211        }
1212        if requests.len() > MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS {
1213            return Err(InternalError::mutation_batch_too_many_items());
1214        }
1215        let first = requests
1216            .first()
1217            .ok_or_else(InternalError::mutation_batch_empty)?;
1218        if first.entity().is_empty() {
1219            return Err(InternalError::executor_unsupported());
1220        }
1221        let catalog = self.accepted_schema_catalog_context_for_entity_name(Some(first.entity()))?;
1222        let accepted_identity = catalog.identity();
1223        let descriptor =
1224            AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())?;
1225        let mut mutations = Vec::with_capacity(requests.len());
1226        let mut save_kinds = Vec::with_capacity(requests.len());
1227
1228        for request in &requests {
1229            if request.entity().is_empty() {
1230                return Err(InternalError::executor_unsupported());
1231            }
1232            let item_catalog =
1233                self.accepted_schema_catalog_context_for_entity_name(Some(request.entity()))?;
1234            if item_catalog.identity() != accepted_identity {
1235                return Err(InternalError::mutation_batch_entity_mismatch());
1236            }
1237            let (mutation, save_kind) = lower_dynamic_mutation_intent(
1238                accepted_identity.entity_tag(),
1239                accepted_identity.entity_path(),
1240                &descriptor,
1241                request,
1242            )?;
1243            mutations.push(mutation);
1244            save_kinds.push(save_kind);
1245        }
1246
1247        let entity_path = accepted_identity.entity_path_handle();
1248        let (result, metrics) = self.execute_accepted_structural_mutation_batch_inner(
1249            &catalog,
1250            &descriptor,
1251            mutations,
1252            Timestamp::now(),
1253            false,
1254            |rows| {
1255                if rows.len() != save_kinds.len() {
1256                    return Err(InternalError::executor_invariant());
1257                }
1258                let metrics = rows
1259                    .iter()
1260                    .zip(save_kinds)
1261                    .filter_map(|(row, kind)| kind.map(|kind| (kind, row.logical_changed())))
1262                    .collect::<Vec<_>>();
1263                let result = prepare_dynamic_mutation_result(
1264                    &catalog,
1265                    &descriptor,
1266                    rows,
1267                    enforce_mixed_batch_result_bound,
1268                )?;
1269                Ok((result, metrics))
1270            },
1271        )?;
1272        for (kind, logical_changed) in metrics {
1273            record(MetricsEvent::SaveMutation {
1274                entity_path: entity_path.clone(),
1275                kind,
1276                rows_touched: u64::from(logical_changed),
1277            });
1278        }
1279        Ok(result)
1280    }
1281
1282    /// Execute one generated typed write through immutable accepted entity and
1283    /// field identities. `None` means the opaque binding is stale.
1284    #[doc(hidden)]
1285    pub fn execute_trusted_typed_mutation(
1286        &self,
1287        binding: &DynamicTypedEntityBinding,
1288        request: &DynamicTypedMutation,
1289    ) -> Result<Option<DynamicMutationResult>, InternalError> {
1290        let Some(catalog) = self.current_typed_entity_binding_catalog(binding)? else {
1291            return Ok(None);
1292        };
1293        let identity = catalog.identity();
1294        let descriptor =
1295            AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())?;
1296        let mode = dynamic_typed_mutation_mode(request);
1297        let (target, patch) = match request {
1298            DynamicTypedMutation::Insert { patch } => (
1299                AcceptedStructuralMutationTarget::ResolveFromAfterImage,
1300                patch,
1301            ),
1302            DynamicTypedMutation::Update { key, patch }
1303            | DynamicTypedMutation::Replace { key, patch } => (
1304                AcceptedStructuralMutationTarget::expected(dynamic_key(
1305                    identity.entity_tag(),
1306                    key,
1307                )?),
1308                patch,
1309            ),
1310        };
1311        if !patch.is_bound_to(binding) {
1312            return Ok(None);
1313        }
1314        let patch = lower_typed_patch(identity.entity_path(), &descriptor, patch, mode)?;
1315        self.execute_one_accepted_save_mutation(&catalog, &descriptor, mode, target, patch)
1316            .map(Some)
1317    }
1318
1319    /// Execute one trusted atomic insert batch from entity-name-driven patches.
1320    ///
1321    /// Every patch is lowered against the same accepted snapshot and shares
1322    /// one operation timestamp before the canonical structural batch owner
1323    /// stages any durable effect.
1324    pub fn execute_trusted_dynamic_insert_batch(
1325        &self,
1326        entity: &str,
1327        patches: Vec<DynamicStructuralPatch>,
1328    ) -> Result<DynamicMutationResult, InternalError> {
1329        let mutations = patches
1330            .into_iter()
1331            .map(|patch| DynamicMutation::Insert {
1332                entity: entity.to_string(),
1333                patch,
1334            })
1335            .collect();
1336        self.execute_trusted_dynamic_mutation_batch_with_result_policy(mutations, false)
1337    }
1338}
1339
1340#[cfg(test)]
1341mod typed_adapter_tests {
1342    use super::{
1343        AcceptedFieldKind, DbSession, DynamicTypedBindingError, DynamicTypedFieldBindingRequest,
1344        DynamicTypedFieldType, DynamicTypedMutation, DynamicWriteCell, dynamic_typed_field_type,
1345        typed_adapter_field_kind_matches,
1346    };
1347    use crate::{
1348        db::{
1349            data::DataStore,
1350            index::IndexStore,
1351            registry::{StoreAllocationIdentities, StoreRegistry, StoreRuntimeStorageCapabilities},
1352            schema::{
1353                AcceptedSchemaRevision, FieldId, FieldStorageDecode, LeafCodec,
1354                PersistedFieldSnapshot, PersistedSchemaSnapshot, ScalarCodec, SchemaFieldSlot,
1355                SchemaInsertDefault, SchemaRowLayout, SchemaStore, SchemaVersion,
1356                accepted_schema_candidate_with_field_bindings_for_tests,
1357            },
1358        },
1359        traits::{CanisterKind, Path},
1360        types::EntityTag,
1361        value::InputValue,
1362    };
1363    use icydb_schema::{EntitySourceKey, FieldSourceKey, ScalarType};
1364    use std::{cell::RefCell, collections::BTreeMap};
1365
1366    const STORE_PATH: &str = "session::write::typed_adapter_tests::Store";
1367    const ENTITY_SOURCE: &str = "session::write::typed_adapter_tests::Entity";
1368    const OTHER_ENTITY_SOURCE: &str = "session::write::typed_adapter_tests::OtherEntity";
1369    const ID_SOURCE: &str = "session::write::typed_adapter_tests::Entity::id";
1370    const VALUE_SOURCE: &str = "session::write::typed_adapter_tests::Entity::value";
1371    const REPLACEMENT_SOURCE: &str =
1372        "session::write::typed_adapter_tests::Entity::replacement_value";
1373    const OTHER_ID_SOURCE: &str = "session::write::typed_adapter_tests::OtherEntity::id";
1374
1375    struct TestCanister;
1376
1377    impl Path for TestCanister {
1378        const PATH: &'static str = "session::write::typed_adapter_tests::Canister";
1379    }
1380
1381    impl CanisterKind for TestCanister {
1382        const COMMIT_MEMORY_ID: u8 = 41;
1383        const COMMIT_STABLE_KEY: &'static str = "icydb.typed_adapter_tests.commit.v1";
1384        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 42;
1385        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
1386            "icydb.typed_adapter_tests.integrity.progress.v1";
1387    }
1388
1389    thread_local! {
1390        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
1391        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
1392        static SCHEMA_STORE: RefCell<SchemaStore> =
1393            const { RefCell::new(SchemaStore::init_heap()) };
1394        static STORE_REGISTRY: StoreRegistry = {
1395            let mut registry = StoreRegistry::new();
1396            registry.register_store(
1397                STORE_PATH,
1398                &DATA_STORE,
1399                &INDEX_STORE,
1400                &SCHEMA_STORE,
1401                StoreAllocationIdentities::absent(),
1402                StoreRuntimeStorageCapabilities::heap(),
1403            ).expect("typed adapter test store should register");
1404            registry
1405        };
1406    }
1407
1408    fn nat64_field(id: u32, name: &str, slot: u16) -> PersistedFieldSnapshot {
1409        PersistedFieldSnapshot::new_initial(
1410            FieldId::new(id),
1411            name.to_string(),
1412            SchemaFieldSlot::new(slot),
1413            AcceptedFieldKind::Nat64,
1414            Vec::new(),
1415            false,
1416            SchemaInsertDefault::None,
1417            FieldStorageDecode::ByKind,
1418            LeafCodec::Scalar(ScalarCodec::Nat64),
1419        )
1420    }
1421
1422    fn snapshot(
1423        entity_source: &str,
1424        entity_name: &str,
1425        fields: Vec<PersistedFieldSnapshot>,
1426    ) -> PersistedSchemaSnapshot {
1427        let layout = SchemaRowLayout::initial(
1428            fields
1429                .iter()
1430                .map(|field| (field.id(), field.slot()))
1431                .collect(),
1432        );
1433        PersistedSchemaSnapshot::new(
1434            SchemaVersion::initial(),
1435            entity_source.to_string(),
1436            entity_name.to_string(),
1437            FieldId::new(1),
1438            layout,
1439            fields,
1440        )
1441    }
1442
1443    fn field_source(source: &str) -> FieldSourceKey {
1444        FieldSourceKey::try_new(source).expect("typed field source should admit")
1445    }
1446
1447    fn entity_source(source: &str) -> EntitySourceKey {
1448        EntitySourceKey::try_new(source).expect("typed entity source should admit")
1449    }
1450
1451    fn publish(
1452        session: &DbSession<TestCanister>,
1453        expected: AcceptedSchemaRevision,
1454        revision: AcceptedSchemaRevision,
1455        snapshots: BTreeMap<EntityTag, PersistedSchemaSnapshot>,
1456        fields: BTreeMap<(EntityTag, FieldSourceKey), FieldId>,
1457    ) {
1458        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
1459            STORE_PATH, revision, snapshots, fields,
1460        );
1461        let store = session
1462            .db
1463            .store_handle(STORE_PATH)
1464            .expect("typed adapter test store should resolve");
1465        crate::db::commit::publish_accepted_schema_candidate(
1466            STORE_PATH, store, expected, &candidate,
1467        )
1468        .expect("typed binding candidate should publish");
1469    }
1470
1471    fn request(source: &str) -> DynamicTypedFieldBindingRequest {
1472        DynamicTypedFieldBindingRequest::new(
1473            source.to_string(),
1474            DynamicTypedFieldType::Scalar(ScalarType::Nat64),
1475            false,
1476        )
1477    }
1478
1479    #[test]
1480    fn typed_adapter_kind_matching_is_exact_but_accepts_relation_key_wrappers() {
1481        let relation = AcceptedFieldKind::Relation {
1482            target_path: "test::Target".to_string(),
1483            target_entity_name: "Target".to_string(),
1484            target_entity_tag: EntityTag::new(7),
1485            target_store_path: "test::Store".to_string(),
1486            key_kind: Box::new(AcceptedFieldKind::Nat64),
1487        };
1488
1489        assert!(typed_adapter_field_kind_matches(
1490            &relation,
1491            &AcceptedFieldKind::Nat64,
1492        ));
1493        assert!(typed_adapter_field_kind_matches(
1494            &AcceptedFieldKind::List(Box::new(relation)),
1495            &AcceptedFieldKind::List(Box::new(AcceptedFieldKind::Nat64)),
1496        ));
1497        assert!(!typed_adapter_field_kind_matches(
1498            &AcceptedFieldKind::Nat64,
1499            &AcceptedFieldKind::Nat32,
1500        ));
1501    }
1502
1503    #[test]
1504    fn typed_adapter_field_contract_rejects_invalid_named_source_identity() {
1505        assert!(matches!(
1506            dynamic_typed_field_type(DynamicTypedFieldType::Named(String::new())),
1507            Err(DynamicTypedBindingError::FieldUnavailable),
1508        ));
1509        assert!(matches!(
1510            dynamic_typed_field_type(DynamicTypedFieldType::Scalar(ScalarType::Nat16)),
1511            Ok(icydb_schema::FieldType::Scalar(ScalarType::Nat16)),
1512        ));
1513    }
1514
1515    // Keep the full rename, stale-binding, and old-name-reuse lifecycle in one
1516    // regression so each issued binding is checked against the next revision.
1517    #[expect(clippy::too_many_lines)]
1518    #[test]
1519    fn typed_binding_uses_accepted_ids_and_slots_across_renames_and_name_reuse() {
1520        let entity_tag = EntityTag::new(91);
1521        let other_entity_tag = EntityTag::new(92);
1522        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
1523        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
1524        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
1525
1526        let session = DbSession::<TestCanister>::new(&STORE_REGISTRY);
1527        session
1528            .db
1529            .ensure_recovered_state()
1530            .expect("typed adapter test database should initialize");
1531        publish(
1532            &session,
1533            AcceptedSchemaRevision::NONE,
1534            AcceptedSchemaRevision::INITIAL,
1535            BTreeMap::from([(
1536                entity_tag,
1537                snapshot(
1538                    ENTITY_SOURCE,
1539                    "Entity",
1540                    vec![nat64_field(1, "id", 0), nat64_field(2, "value", 1)],
1541                ),
1542            )]),
1543            BTreeMap::from([
1544                ((entity_tag, field_source(ID_SOURCE)), FieldId::new(1)),
1545                ((entity_tag, field_source(VALUE_SOURCE)), FieldId::new(2)),
1546            ]),
1547        );
1548
1549        let initial_catalog = session
1550            .find_accepted_schema_catalog_context_for_entity_source_key(ENTITY_SOURCE)
1551            .expect("initial source catalog lookup should inspect")
1552            .expect("initial source catalog should exist");
1553        assert_eq!(initial_catalog.identity().entity_tag(), entity_tag);
1554        let initial = session
1555            .issue_typed_entity_binding(
1556                entity_source(ENTITY_SOURCE).as_str(),
1557                &[request(ID_SOURCE), request(VALUE_SOURCE)],
1558            )
1559            .expect("initial typed binding should issue");
1560        assert_eq!(initial.field_slot(ID_SOURCE), Some(0));
1561        assert_eq!(initial.field_slot(VALUE_SOURCE), Some(1));
1562        assert_eq!(initial.output_field_slot("value"), Some(1));
1563        let initial_patch = initial
1564            .bind_write_fields(vec![(
1565                VALUE_SOURCE.to_string(),
1566                DynamicWriteCell::Value(InputValue::Nat64(7)),
1567            )])
1568            .expect("source-bound patch should lower");
1569        assert_eq!(
1570            initial_patch.fields(),
1571            &[(2, 1, DynamicWriteCell::Value(InputValue::Nat64(7)))]
1572        );
1573
1574        publish(
1575            &session,
1576            AcceptedSchemaRevision::INITIAL,
1577            AcceptedSchemaRevision::new(2),
1578            BTreeMap::from([
1579                (
1580                    entity_tag,
1581                    snapshot(
1582                        ENTITY_SOURCE,
1583                        "RenamedEntity",
1584                        vec![
1585                            nat64_field(1, "id", 0),
1586                            nat64_field(2, "renamed_value", 1),
1587                            nat64_field(3, "value", 2),
1588                        ],
1589                    ),
1590                ),
1591                (
1592                    other_entity_tag,
1593                    snapshot(OTHER_ENTITY_SOURCE, "Entity", vec![nat64_field(1, "id", 0)]),
1594                ),
1595            ]),
1596            BTreeMap::from([
1597                ((entity_tag, field_source(ID_SOURCE)), FieldId::new(1)),
1598                ((entity_tag, field_source(VALUE_SOURCE)), FieldId::new(2)),
1599                (
1600                    (entity_tag, field_source(REPLACEMENT_SOURCE)),
1601                    FieldId::new(3),
1602                ),
1603                (
1604                    (other_entity_tag, field_source(OTHER_ID_SOURCE)),
1605                    FieldId::new(1),
1606                ),
1607            ]),
1608        );
1609
1610        assert!(
1611            !session
1612                .typed_entity_binding_is_current(&initial)
1613                .expect("renamed binding currentness should inspect")
1614        );
1615        let renamed = session
1616            .issue_typed_entity_binding(ENTITY_SOURCE, &[request(ID_SOURCE), request(VALUE_SOURCE)])
1617            .expect("renamed source-bound adapter should rebind");
1618        assert_eq!(renamed.entity(), "RenamedEntity");
1619        assert_eq!(renamed.field_slot(VALUE_SOURCE), Some(1));
1620        assert_eq!(renamed.output_field_slot("renamed_value"), Some(1));
1621        assert_eq!(renamed.output_field_slot("value"), None);
1622
1623        publish(
1624            &session,
1625            AcceptedSchemaRevision::new(2),
1626            AcceptedSchemaRevision::new(3),
1627            BTreeMap::from([
1628                (
1629                    entity_tag,
1630                    snapshot(
1631                        ENTITY_SOURCE,
1632                        "RenamedEntity",
1633                        vec![nat64_field(1, "id", 0), nat64_field(2, "value", 1)],
1634                    ),
1635                ),
1636                (
1637                    other_entity_tag,
1638                    snapshot(OTHER_ENTITY_SOURCE, "Entity", vec![nat64_field(1, "id", 0)]),
1639                ),
1640            ]),
1641            BTreeMap::from([
1642                ((entity_tag, field_source(ID_SOURCE)), FieldId::new(1)),
1643                (
1644                    (entity_tag, field_source(REPLACEMENT_SOURCE)),
1645                    FieldId::new(2),
1646                ),
1647                (
1648                    (other_entity_tag, field_source(OTHER_ID_SOURCE)),
1649                    FieldId::new(1),
1650                ),
1651            ]),
1652        );
1653
1654        assert!(matches!(
1655            session.issue_typed_entity_binding(
1656                ENTITY_SOURCE,
1657                &[request(ID_SOURCE), request(VALUE_SOURCE)],
1658            ),
1659            Err(DynamicTypedBindingError::FieldUnavailable),
1660        ));
1661        assert!(
1662            !session
1663                .typed_entity_binding_is_current(&renamed)
1664                .expect("removed source binding should become stale")
1665        );
1666
1667        let replacement = session
1668            .issue_typed_entity_binding(
1669                ENTITY_SOURCE,
1670                &[request(ID_SOURCE), request(REPLACEMENT_SOURCE)],
1671            )
1672            .expect("explicit replacement source should bind");
1673        assert!(
1674            session
1675                .execute_trusted_typed_mutation(
1676                    &replacement,
1677                    &DynamicTypedMutation::Insert {
1678                        patch: initial_patch
1679                    },
1680                )
1681                .expect("cross-binding patch should fail closed")
1682                .is_none()
1683        );
1684        let patch = replacement
1685            .bind_write_fields(vec![
1686                (
1687                    ID_SOURCE.to_string(),
1688                    DynamicWriteCell::Value(InputValue::Nat64(1)),
1689                ),
1690                (
1691                    REPLACEMENT_SOURCE.to_string(),
1692                    DynamicWriteCell::Value(InputValue::Nat64(9)),
1693                ),
1694            ])
1695            .expect("replacement source write should bind by accepted IDs and slots");
1696        let result = session
1697            .execute_trusted_typed_mutation(&replacement, &DynamicTypedMutation::Insert { patch })
1698            .expect("typed insert should use the accepted mutation pipeline")
1699            .expect("replacement binding should remain current");
1700        assert_eq!(result.entity, "RenamedEntity");
1701        assert_eq!(result.columns, vec!["id".to_string(), "value".to_string()]);
1702        assert_eq!(
1703            result.rows,
1704            vec![vec![
1705                crate::value::OutputValue::Nat64(1),
1706                crate::value::OutputValue::Nat64(9)
1707            ]]
1708        );
1709        assert_eq!(result.affected_rows, 1);
1710
1711        #[cfg(feature = "query")]
1712        {
1713            let query = crate::db::DynamicQuery::new("RenamedEntity")
1714                .select(["id", "value"])
1715                .order_by(crate::db::asc("id"))
1716                .limit(1);
1717            let result = session
1718                .execute_trusted_dynamic_query(&query)
1719                .expect("query-only dynamic execution should use accepted authority");
1720            assert_eq!(result.entity, "RenamedEntity");
1721            assert_eq!(result.columns, vec!["id".to_string(), "value".to_string()]);
1722            assert_eq!(
1723                result.rows,
1724                vec![vec![
1725                    crate::value::OutputValue::Nat64(1),
1726                    crate::value::OutputValue::Nat64(9)
1727                ]]
1728            );
1729            assert_eq!(result.row_count, 1);
1730        }
1731    }
1732}
1733
1734#[cfg(test)]
1735mod mixed_relation_batch_tests {
1736    use super::{DbSession, DynamicMutation, DynamicStructuralPatch, DynamicWriteCell};
1737    use crate::{
1738        db::{
1739            data::DataStore,
1740            index::IndexStore,
1741            registry::{StoreAllocationIdentities, StoreRegistry, StoreRuntimeStorageCapabilities},
1742            schema::{
1743                AcceptedConstraintCatalog, AcceptedFieldKind, AcceptedSchemaRevision, FieldId,
1744                FieldStorageDecode, LeafCodec, PersistedFieldSnapshot,
1745                PersistedIndexFieldPathSnapshot, PersistedIndexKeySnapshot, PersistedIndexSnapshot,
1746                PersistedRelationEdgeSnapshot, PersistedSchemaSnapshot, RelationId, ScalarCodec,
1747                SchemaFieldSlot, SchemaIndexId, SchemaInsertDefault, SchemaRowLayout, SchemaStore,
1748                SchemaVersion, accepted_schema_candidate_with_field_bindings_for_tests,
1749            },
1750        },
1751        error::{ConstraintDiagnosticKind, ErrorClass},
1752        traits::{CanisterKind, Path},
1753        types::EntityTag,
1754        value::{InputValue, OutputValue},
1755    };
1756    use icydb_schema::FieldSourceKey;
1757    use std::{cell::RefCell, collections::BTreeMap};
1758
1759    const STORE_PATH: &str = "session::write::mixed_relation_batch_tests::Store";
1760    const ENTITY_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node";
1761    const ID_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node::id";
1762    const PARENT_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node::parent_id";
1763    const CODE_SOURCE: &str = "session::write::mixed_relation_batch_tests::Node::code";
1764    const ENTITY_NAME: &str = "MixedRelationNode";
1765    const ENTITY_TAG: EntityTag = EntityTag::new(94);
1766    const OTHER_ENTITY_SOURCE: &str = "session::write::mixed_relation_batch_tests::Other";
1767    const OTHER_ID_SOURCE: &str = "session::write::mixed_relation_batch_tests::Other::id";
1768    const OTHER_VALUE_SOURCE: &str = "session::write::mixed_relation_batch_tests::Other::value";
1769    const OTHER_ENTITY_NAME: &str = "MixedRelationOther";
1770    const OTHER_ENTITY_TAG: EntityTag = EntityTag::new(95);
1771
1772    struct TestCanister;
1773
1774    impl Path for TestCanister {
1775        const PATH: &'static str = "session::write::mixed_relation_batch_tests::Canister";
1776    }
1777
1778    impl CanisterKind for TestCanister {
1779        const COMMIT_MEMORY_ID: u8 = 47;
1780        const COMMIT_STABLE_KEY: &'static str = "icydb.mixed_relation_batch_tests.commit.v1";
1781        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 48;
1782        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
1783            "icydb.mixed_relation_batch_tests.integrity.progress.v1";
1784    }
1785
1786    thread_local! {
1787        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
1788        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
1789        static SCHEMA_STORE: RefCell<SchemaStore> =
1790            const { RefCell::new(SchemaStore::init_heap()) };
1791        static STORE_REGISTRY: StoreRegistry = {
1792            let mut registry = StoreRegistry::new();
1793            registry.register_store(
1794                STORE_PATH,
1795                &DATA_STORE,
1796                &INDEX_STORE,
1797                &SCHEMA_STORE,
1798                StoreAllocationIdentities::absent(),
1799                StoreRuntimeStorageCapabilities::heap(),
1800            ).expect("mixed relation test store should register");
1801            registry
1802        };
1803    }
1804
1805    fn source_key(source: &str) -> FieldSourceKey {
1806        FieldSourceKey::try_new(source).expect("mixed relation field source should admit")
1807    }
1808
1809    fn relation_snapshot() -> PersistedSchemaSnapshot {
1810        let fields = vec![
1811            PersistedFieldSnapshot::new_initial(
1812                FieldId::new(1),
1813                "id".to_string(),
1814                SchemaFieldSlot::new(0),
1815                AcceptedFieldKind::Nat64,
1816                Vec::new(),
1817                false,
1818                SchemaInsertDefault::None,
1819                FieldStorageDecode::ByKind,
1820                LeafCodec::Scalar(ScalarCodec::Nat64),
1821            ),
1822            PersistedFieldSnapshot::new_initial(
1823                FieldId::new(2),
1824                "parent_id".to_string(),
1825                SchemaFieldSlot::new(1),
1826                AcceptedFieldKind::Relation {
1827                    target_path: ENTITY_SOURCE.to_string(),
1828                    target_entity_name: ENTITY_NAME.to_string(),
1829                    target_entity_tag: ENTITY_TAG,
1830                    target_store_path: STORE_PATH.to_string(),
1831                    key_kind: Box::new(AcceptedFieldKind::Nat64),
1832                },
1833                Vec::new(),
1834                true,
1835                SchemaInsertDefault::None,
1836                FieldStorageDecode::ByKind,
1837                LeafCodec::Scalar(ScalarCodec::Nat64),
1838            ),
1839            PersistedFieldSnapshot::new_initial(
1840                FieldId::new(3),
1841                "code".to_string(),
1842                SchemaFieldSlot::new(2),
1843                AcceptedFieldKind::Nat64,
1844                Vec::new(),
1845                false,
1846                SchemaInsertDefault::None,
1847                FieldStorageDecode::ByKind,
1848                LeafCodec::Scalar(ScalarCodec::Nat64),
1849            ),
1850        ];
1851        let relation = PersistedRelationEdgeSnapshot::new(
1852            RelationId::new(1).expect("mixed relation identity should be non-zero"),
1853            "parent".to_string(),
1854            ENTITY_SOURCE.to_string(),
1855            vec![FieldId::new(2)],
1856        );
1857        let snapshot = PersistedSchemaSnapshot::new_with_indexes(
1858            SchemaVersion::initial(),
1859            ENTITY_SOURCE.to_string(),
1860            ENTITY_NAME.to_string(),
1861            FieldId::new(1),
1862            SchemaRowLayout::initial(
1863                fields
1864                    .iter()
1865                    .map(|field| (field.id(), field.slot()))
1866                    .collect(),
1867            ),
1868            fields,
1869            vec![PersistedIndexSnapshot::new(
1870                SchemaIndexId::new(1).expect("mixed unique index identity should be non-zero"),
1871                1,
1872                "by_code".to_string(),
1873                STORE_PATH.to_string(),
1874                true,
1875                PersistedIndexKeySnapshot::FieldPath(vec![PersistedIndexFieldPathSnapshot::new(
1876                    FieldId::new(3),
1877                    SchemaFieldSlot::new(2),
1878                    vec!["code".to_string()],
1879                    AcceptedFieldKind::Nat64,
1880                    false,
1881                )]),
1882                None,
1883            )],
1884        )
1885        .with_relations(vec![relation]);
1886        let constraints = AcceptedConstraintCatalog::initial(
1887            snapshot.fields(),
1888            snapshot.indexes(),
1889            snapshot.relations(),
1890        )
1891        .expect("mixed relation constraints should close");
1892        snapshot.with_constraint_catalog(constraints)
1893    }
1894
1895    fn other_snapshot() -> PersistedSchemaSnapshot {
1896        let fields = vec![
1897            PersistedFieldSnapshot::new_initial(
1898                FieldId::new(1),
1899                "id".to_string(),
1900                SchemaFieldSlot::new(0),
1901                AcceptedFieldKind::Nat64,
1902                Vec::new(),
1903                false,
1904                SchemaInsertDefault::None,
1905                FieldStorageDecode::ByKind,
1906                LeafCodec::Scalar(ScalarCodec::Nat64),
1907            ),
1908            PersistedFieldSnapshot::new_initial(
1909                FieldId::new(2),
1910                "value".to_string(),
1911                SchemaFieldSlot::new(1),
1912                AcceptedFieldKind::Nat64,
1913                Vec::new(),
1914                false,
1915                SchemaInsertDefault::None,
1916                FieldStorageDecode::ByKind,
1917                LeafCodec::Scalar(ScalarCodec::Nat64),
1918            ),
1919        ];
1920        PersistedSchemaSnapshot::new(
1921            SchemaVersion::initial(),
1922            OTHER_ENTITY_SOURCE.to_string(),
1923            OTHER_ENTITY_NAME.to_string(),
1924            FieldId::new(1),
1925            SchemaRowLayout::initial(
1926                fields
1927                    .iter()
1928                    .map(|field| (field.id(), field.slot()))
1929                    .collect(),
1930            ),
1931            fields,
1932        )
1933    }
1934
1935    fn initialize() -> DbSession<TestCanister> {
1936        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
1937        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
1938        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
1939        let session = DbSession::<TestCanister>::new(&STORE_REGISTRY);
1940        session
1941            .db
1942            .ensure_recovered_state()
1943            .expect("mixed relation database should initialize");
1944        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
1945            STORE_PATH,
1946            AcceptedSchemaRevision::INITIAL,
1947            BTreeMap::from([
1948                (ENTITY_TAG, relation_snapshot()),
1949                (OTHER_ENTITY_TAG, other_snapshot()),
1950            ]),
1951            BTreeMap::from([
1952                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
1953                ((ENTITY_TAG, source_key(PARENT_SOURCE)), FieldId::new(2)),
1954                ((ENTITY_TAG, source_key(CODE_SOURCE)), FieldId::new(3)),
1955                (
1956                    (OTHER_ENTITY_TAG, source_key(OTHER_ID_SOURCE)),
1957                    FieldId::new(1),
1958                ),
1959                (
1960                    (OTHER_ENTITY_TAG, source_key(OTHER_VALUE_SOURCE)),
1961                    FieldId::new(2),
1962                ),
1963            ]),
1964        );
1965        let store = session
1966            .db
1967            .store_handle(STORE_PATH)
1968            .expect("mixed relation store should resolve");
1969        crate::db::commit::publish_accepted_schema_candidate(
1970            STORE_PATH,
1971            store,
1972            AcceptedSchemaRevision::NONE,
1973            &candidate,
1974        )
1975        .expect("mixed relation candidate should publish");
1976        session
1977    }
1978
1979    fn patch(id: Option<u64>, parent: Option<u64>, code: Option<u64>) -> DynamicStructuralPatch {
1980        let mut fields = Vec::new();
1981        if let Some(id) = id {
1982            fields.push((
1983                "id".to_string(),
1984                DynamicWriteCell::Value(InputValue::Nat64(id)),
1985            ));
1986        }
1987        fields.push((
1988            "parent_id".to_string(),
1989            parent.map_or(DynamicWriteCell::Null, |parent| {
1990                DynamicWriteCell::Value(InputValue::Nat64(parent))
1991            }),
1992        ));
1993        if let Some(code) = code {
1994            fields.push((
1995                "code".to_string(),
1996                DynamicWriteCell::Value(InputValue::Nat64(code)),
1997            ));
1998        }
1999        DynamicStructuralPatch::new(fields)
2000    }
2001
2002    fn insert(id: u64, parent: Option<u64>) -> DynamicMutation {
2003        insert_with_code(id, parent, id)
2004    }
2005
2006    fn insert_with_code(id: u64, parent: Option<u64>, code: u64) -> DynamicMutation {
2007        DynamicMutation::Insert {
2008            entity: ENTITY_NAME.to_string(),
2009            patch: patch(Some(id), parent, Some(code)),
2010        }
2011    }
2012
2013    fn update_parent(id: u64, parent: Option<u64>) -> DynamicMutation {
2014        DynamicMutation::Update {
2015            entity: ENTITY_NAME.to_string(),
2016            key: InputValue::Nat64(id),
2017            patch: patch(None, parent, None),
2018        }
2019    }
2020
2021    fn update_code(id: u64, code: u64) -> DynamicMutation {
2022        DynamicMutation::Update {
2023            entity: ENTITY_NAME.to_string(),
2024            key: InputValue::Nat64(id),
2025            patch: DynamicStructuralPatch::new(vec![(
2026                "code".to_string(),
2027                DynamicWriteCell::Value(InputValue::Nat64(code)),
2028            )]),
2029        }
2030    }
2031
2032    fn delete(id: u64) -> DynamicMutation {
2033        DynamicMutation::Delete {
2034            entity: ENTITY_NAME.to_string(),
2035            key: InputValue::Nat64(id),
2036        }
2037    }
2038
2039    fn expected_row(id: u64, parent: Option<u64>) -> Vec<OutputValue> {
2040        expected_row_with_code(id, parent, id)
2041    }
2042
2043    fn expected_row_with_code(id: u64, parent: Option<u64>, code: u64) -> Vec<OutputValue> {
2044        vec![
2045            OutputValue::Nat64(id),
2046            parent.map_or(OutputValue::Null, OutputValue::Nat64),
2047            OutputValue::Nat64(code),
2048        ]
2049    }
2050
2051    fn other_patch(id: Option<u64>, value: u64) -> DynamicStructuralPatch {
2052        let mut fields = Vec::new();
2053        if let Some(id) = id {
2054            fields.push((
2055                "id".to_string(),
2056                DynamicWriteCell::Value(InputValue::Nat64(id)),
2057            ));
2058        }
2059        fields.push((
2060            "value".to_string(),
2061            DynamicWriteCell::Value(InputValue::Nat64(value)),
2062        ));
2063        DynamicStructuralPatch::new(fields)
2064    }
2065
2066    fn assert_relation_violation(error: &crate::error::InternalError) {
2067        let diagnostic = error
2068            .constraint_diagnostic()
2069            .expect("relation violations should retain their accepted constraint");
2070        assert_eq!(
2071            diagnostic.constraint_kind(),
2072            ConstraintDiagnosticKind::Relation,
2073        );
2074    }
2075
2076    #[test]
2077    fn mixed_relation_validation_uses_the_complete_final_row_overlay() {
2078        let session = initialize();
2079        session
2080            .execute_trusted_dynamic_mutation_batch(vec![insert(1, None), insert(2, Some(1))])
2081            .expect("the initial relation should commit");
2082
2083        let blocked = session
2084            .execute_trusted_dynamic_mutation(&delete(1))
2085            .expect_err("an unaffected committed source must block target deletion");
2086        assert_relation_violation(&blocked);
2087
2088        let deleted = session
2089            .execute_trusted_dynamic_mutation_batch(vec![delete(2), delete(1)])
2090            .expect("a source and its target should delete atomically");
2091        assert_eq!(
2092            deleted.rows,
2093            vec![expected_row(2, Some(1)), expected_row(1, None)],
2094        );
2095
2096        session
2097            .execute_trusted_dynamic_mutation_batch(vec![insert(3, None), insert(4, Some(3))])
2098            .expect("the update-away fixture should commit");
2099        let updated_away = session
2100            .execute_trusted_dynamic_mutation_batch(vec![update_parent(4, None), delete(3)])
2101            .expect("an updated final source may release a deleted target");
2102        assert_eq!(
2103            updated_away.rows,
2104            vec![expected_row(4, None), expected_row(3, None)],
2105        );
2106
2107        session
2108            .execute_trusted_dynamic_mutation_batch(vec![insert(5, None), insert(6, Some(5))])
2109            .expect("the retained-reference fixture should commit");
2110        let retained = session
2111            .execute_trusted_dynamic_mutation_batch(vec![update_parent(6, Some(5)), delete(5)])
2112            .expect_err("a final updated source must still block target deletion");
2113        assert_relation_violation(&retained);
2114
2115        session
2116            .execute_trusted_dynamic_mutation(&insert(7, None))
2117            .expect("the inserted-reference fixture target should commit");
2118        let inserted_reference = session
2119            .execute_trusted_dynamic_mutation_batch(vec![insert(8, Some(7)), delete(7)])
2120            .expect_err("a final inserted source must not reference a deleted target");
2121        assert_relation_violation(&inserted_reference);
2122
2123        let inserted_target = session
2124            .execute_trusted_dynamic_mutation_batch(vec![insert(10, Some(9)), insert(9, None)])
2125            .expect("an inserted relation should see its batch-final target");
2126        assert_eq!(
2127            inserted_target.rows,
2128            vec![expected_row(10, Some(9)), expected_row(9, None)],
2129        );
2130
2131        session
2132            .execute_trusted_dynamic_mutation(&insert(11, None))
2133            .expect("the updated-reference fixture source should commit");
2134        let updated_target = session
2135            .execute_trusted_dynamic_mutation_batch(vec![
2136                update_parent(11, Some(12)),
2137                insert(12, None),
2138            ])
2139            .expect("an updated relation should see its batch-final target");
2140        assert_eq!(
2141            updated_target.rows,
2142            vec![expected_row(11, Some(12)), expected_row(12, None)],
2143        );
2144    }
2145
2146    #[test]
2147    fn mixed_batch_rejects_cross_entity_missing_and_collision_then_honors_replace() {
2148        let session = initialize();
2149        session
2150            .execute_trusted_dynamic_mutation(&insert(1, None))
2151            .expect("the primary mixed fixture row should commit");
2152        session
2153            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
2154                entity: OTHER_ENTITY_NAME.to_string(),
2155                patch: other_patch(Some(1), 10),
2156            })
2157            .expect("the secondary mixed fixture row should commit");
2158
2159        let mixed_entity = session
2160            .execute_trusted_dynamic_mutation_batch(vec![
2161                update_code(1, 11),
2162                DynamicMutation::Update {
2163                    entity: OTHER_ENTITY_NAME.to_string(),
2164                    key: InputValue::Nat64(1),
2165                    patch: other_patch(None, 11),
2166                },
2167            ])
2168            .expect_err("one atomic batch must not cross accepted entities");
2169        assert_eq!(mixed_entity.class(), ErrorClass::Conflict);
2170
2171        let missing = session
2172            .execute_trusted_dynamic_mutation_batch(vec![update_code(1, 12), delete(99)])
2173            .expect_err("a late missing delete must reject the earlier staged update");
2174        assert_eq!(missing.class(), ErrorClass::NotFound);
2175
2176        session
2177            .execute_trusted_dynamic_mutation(&insert(2, None))
2178            .expect("the collision fixture should commit");
2179        let collision = session
2180            .execute_trusted_dynamic_mutation_batch(vec![update_code(1, 13), insert(2, None)])
2181            .expect_err("an insert collision must reject the earlier staged update");
2182        assert_eq!(collision.class(), ErrorClass::Conflict);
2183        let failures_unchanged = session
2184            .execute_trusted_dynamic_mutation(&update_code(1, 1))
2185            .expect("failed batches must preserve the original unique value");
2186        assert_eq!(failures_unchanged.affected_rows, 0);
2187
2188        let replaced = session
2189            .execute_trusted_dynamic_mutation_batch(vec![
2190                update_code(1, 14),
2191                DynamicMutation::Replace {
2192                    entity: ENTITY_NAME.to_string(),
2193                    key: InputValue::Nat64(99),
2194                    patch: patch(None, None, Some(99)),
2195                },
2196            ])
2197            .expect("ordinary caller-key replace should insert its absent final row");
2198        assert_eq!(
2199            replaced.rows,
2200            vec![
2201                expected_row_with_code(1, None, 14),
2202                expected_row_with_code(99, None, 99),
2203            ],
2204        );
2205
2206        let unchanged = session
2207            .execute_trusted_dynamic_mutation(&update_code(1, 14))
2208            .expect("the successful mixed replace must publish its preceding update");
2209        assert_eq!(unchanged.affected_rows, 0);
2210        let other_unchanged = session
2211            .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
2212                entity: OTHER_ENTITY_NAME.to_string(),
2213                key: InputValue::Nat64(1),
2214                patch: other_patch(None, 10),
2215            })
2216            .expect("cross-entity rejection must preserve the secondary row");
2217        assert_eq!(other_unchanged.affected_rows, 0);
2218    }
2219
2220    #[test]
2221    fn mixed_batch_unique_swap_and_delete_release_use_the_final_overlay() {
2222        let session = initialize();
2223        session
2224            .execute_trusted_dynamic_mutation_batch(vec![
2225                insert_with_code(1, None, 10),
2226                insert_with_code(2, None, 20),
2227            ])
2228            .expect("the unique-overlay fixture should commit");
2229
2230        let swapped = session
2231            .execute_trusted_dynamic_mutation_batch(vec![update_code(1, 20), update_code(2, 10)])
2232            .expect("two final rows should atomically swap unique memberships");
2233        assert_eq!(
2234            swapped.rows,
2235            vec![
2236                expected_row_with_code(1, None, 20),
2237                expected_row_with_code(2, None, 10),
2238            ],
2239        );
2240
2241        let released = session
2242            .execute_trusted_dynamic_mutation_batch(vec![delete(1), insert_with_code(3, None, 20)])
2243            .expect("a delete should release unique membership to a final inserted row");
2244        assert_eq!(
2245            released.rows,
2246            vec![
2247                expected_row_with_code(1, None, 20),
2248                expected_row_with_code(3, None, 20),
2249            ],
2250        );
2251    }
2252}
2253
2254#[cfg(test)]
2255mod identity_pre_key_tests {
2256    use super::{
2257        AcceptedMutationIntentPatch, AcceptedRowLayoutRuntimeContract, AcceptedStructuralMutation,
2258        AcceptedStructuralMutationTarget, DbSession, DynamicMutation, DynamicStructuralPatch,
2259        DynamicTypedFieldBindingRequest, DynamicTypedFieldType, DynamicTypedMutation,
2260        DynamicWriteCell, FieldSlot, MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS,
2261        MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES, MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES,
2262        add_structural_mutation_staged_bytes, checked_pre_key_candidate_count,
2263        insert_key_exists_after_generation, validate_structural_mutation_result_bytes,
2264    };
2265    use crate::{
2266        db::{
2267            commit::{database_incarnation_id, forget_recovered_domain_for_tests},
2268            data::DataStore,
2269            executor::{MutationCommitInterruption, interrupt_next_mutation_commit_for_tests},
2270            index::IndexStore,
2271            integrity::{
2272                PhysicalUnitCheckpoint, QuickIntegrityStatus, RowInspectionLimits,
2273                execute_quick_integrity, execute_row_integrity_page,
2274            },
2275            journal::JournalTailStore,
2276            registry::{
2277                StoreAllocationIdentities, StoreAllocationIdentity, StoreRegistry,
2278                StoreRuntimeStorageCapabilities,
2279            },
2280            schema::{
2281                AcceptedFieldKind, AcceptedSchemaRevision, FieldId, FieldInsertGeneration,
2282                FieldStorageDecode, LeafCodec, PersistedFieldSnapshot,
2283                PersistedIndexFieldPathSnapshot, PersistedIndexKeySnapshot, PersistedIndexSnapshot,
2284                PersistedSchemaSnapshot, ScalarCodec, SchemaFieldSlot, SchemaFieldWritePolicy,
2285                SchemaIndexId, SchemaInsertDefault, SchemaRowLayout, SchemaStore, SchemaVersion,
2286                accepted_schema_candidate_with_field_bindings_for_tests,
2287            },
2288            write_context::MutationMode,
2289        },
2290        error::{ErrorClass, ErrorOrigin, InternalError},
2291        testing::test_memory,
2292        traits::{CanisterKind, Path},
2293        types::{EntityTag, Timestamp},
2294        value::{InputValue, OutputValue, Value},
2295    };
2296    use icydb_schema::{FieldSourceKey, ScalarType};
2297    use std::{cell::RefCell, collections::BTreeMap, time::Instant};
2298
2299    const STORE_PATH: &str = "session::write::identity_pre_key_tests::Store";
2300    const ENTITY_SOURCE: &str = "session::write::identity_pre_key_tests::Entity";
2301    const ID_SOURCE: &str = "session::write::identity_pre_key_tests::Entity::id";
2302    const PAYLOAD_SOURCE: &str = "session::write::identity_pre_key_tests::Entity::payload";
2303    const ENTITY_NAME: &str = "IdentityRow";
2304    const ENTITY_TAG: EntityTag = EntityTag::new(93);
2305    const JOURNALED_STORE_PATH: &str = "session::write::identity_pre_key_tests::JournaledStore";
2306
2307    struct TestCanister;
2308
2309    impl Path for TestCanister {
2310        const PATH: &'static str = "session::write::identity_pre_key_tests::Canister";
2311    }
2312
2313    impl CanisterKind for TestCanister {
2314        const COMMIT_MEMORY_ID: u8 = 45;
2315        const COMMIT_STABLE_KEY: &'static str = "icydb.identity_pre_key_tests.commit.v1";
2316        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 46;
2317        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
2318            "icydb.identity_pre_key_tests.integrity.progress.v1";
2319    }
2320
2321    thread_local! {
2322        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
2323        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
2324        static SCHEMA_STORE: RefCell<SchemaStore> =
2325            const { RefCell::new(SchemaStore::init_heap()) };
2326        static STORE_REGISTRY: StoreRegistry = {
2327            let mut registry = StoreRegistry::new();
2328            registry.register_store(
2329                STORE_PATH,
2330                &DATA_STORE,
2331                &INDEX_STORE,
2332                &SCHEMA_STORE,
2333                StoreAllocationIdentities::absent(),
2334                StoreRuntimeStorageCapabilities::heap(),
2335            ).expect("identity pre-key test store should register");
2336            registry
2337        };
2338        static JOURNALED_DATA_STORE: RefCell<DataStore> =
2339            RefCell::new(DataStore::init_journaled(test_memory(186)));
2340        static JOURNALED_INDEX_STORE: RefCell<IndexStore> =
2341            RefCell::new(IndexStore::init_journaled(test_memory(187)));
2342        static JOURNALED_SCHEMA_STORE: RefCell<SchemaStore> =
2343            RefCell::new(SchemaStore::init_journaled(test_memory(188)));
2344        static JOURNALED_TAIL_STORE: RefCell<JournalTailStore> =
2345            RefCell::new(JournalTailStore::init(test_memory(189)));
2346        static JOURNALED_STORE_REGISTRY: StoreRegistry = {
2347            let mut registry = StoreRegistry::new();
2348            registry.register_journaled_store(
2349                JOURNALED_STORE_PATH,
2350                &JOURNALED_DATA_STORE,
2351                &JOURNALED_INDEX_STORE,
2352                &JOURNALED_SCHEMA_STORE,
2353                &JOURNALED_TAIL_STORE,
2354                StoreAllocationIdentities::new_journaled(
2355                    StoreAllocationIdentity::new(186, "icydb.test.identity-range.data.v1"),
2356                    StoreAllocationIdentity::new(187, "icydb.test.identity-range.index.v1"),
2357                    StoreAllocationIdentity::new(188, "icydb.test.identity-range.schema.v1"),
2358                    StoreAllocationIdentity::new(189, "icydb.test.identity-range.journal.v1"),
2359                ),
2360                StoreRuntimeStorageCapabilities::journaled(),
2361            ).expect("identity range journaled store should register");
2362            registry
2363        };
2364    }
2365
2366    struct JournaledTestCanister;
2367
2368    impl Path for JournaledTestCanister {
2369        const PATH: &'static str = "session::write::identity_pre_key_tests::JournaledCanister";
2370    }
2371
2372    impl CanisterKind for JournaledTestCanister {
2373        const COMMIT_MEMORY_ID: u8 = 190;
2374        const COMMIT_STABLE_KEY: &'static str = "icydb.identity_range_tests.commit.v1";
2375        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 191;
2376        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
2377            "icydb.identity_range_tests.integrity.progress.v1";
2378    }
2379
2380    fn source_key(source: &str) -> FieldSourceKey {
2381        FieldSourceKey::try_new(source).expect("identity test field source should admit")
2382    }
2383
2384    fn identity_snapshot(store_path: &str) -> PersistedSchemaSnapshot {
2385        let fields = vec![
2386            PersistedFieldSnapshot::new_initial_with_write_policy(
2387                FieldId::new(1),
2388                "id".to_string(),
2389                SchemaFieldSlot::new(0),
2390                AcceptedFieldKind::Nat64,
2391                Vec::new(),
2392                false,
2393                SchemaInsertDefault::None,
2394                SchemaFieldWritePolicy::from_model_policies(
2395                    Some(FieldInsertGeneration::Identity),
2396                    None,
2397                ),
2398                FieldStorageDecode::ByKind,
2399                LeafCodec::Scalar(ScalarCodec::Nat64),
2400            ),
2401            PersistedFieldSnapshot::new_initial(
2402                FieldId::new(2),
2403                "payload".to_string(),
2404                SchemaFieldSlot::new(1),
2405                AcceptedFieldKind::Nat64,
2406                Vec::new(),
2407                false,
2408                SchemaInsertDefault::None,
2409                FieldStorageDecode::ByKind,
2410                LeafCodec::Scalar(ScalarCodec::Nat64),
2411            ),
2412        ];
2413        PersistedSchemaSnapshot::new_with_indexes(
2414            SchemaVersion::initial(),
2415            ENTITY_SOURCE.to_string(),
2416            ENTITY_NAME.to_string(),
2417            FieldId::new(1),
2418            SchemaRowLayout::initial(
2419                fields
2420                    .iter()
2421                    .map(|field| (field.id(), field.slot()))
2422                    .collect(),
2423            ),
2424            fields,
2425            vec![PersistedIndexSnapshot::new(
2426                SchemaIndexId::new(1).expect("identity test index ID should admit"),
2427                1,
2428                "by_payload".to_string(),
2429                store_path.to_string(),
2430                false,
2431                PersistedIndexKeySnapshot::FieldPath(vec![PersistedIndexFieldPathSnapshot::new(
2432                    FieldId::new(2),
2433                    SchemaFieldSlot::new(1),
2434                    vec!["payload".to_string()],
2435                    AcceptedFieldKind::Nat64,
2436                    false,
2437                )]),
2438                None,
2439            )],
2440        )
2441    }
2442
2443    fn initialize() -> DbSession<TestCanister> {
2444        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
2445        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
2446        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
2447        let session = DbSession::<TestCanister>::new(&STORE_REGISTRY);
2448        session
2449            .db
2450            .ensure_recovered_state()
2451            .expect("identity pre-key test database should initialize");
2452        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
2453            STORE_PATH,
2454            AcceptedSchemaRevision::INITIAL,
2455            BTreeMap::from([(ENTITY_TAG, identity_snapshot(STORE_PATH))]),
2456            BTreeMap::from([
2457                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
2458                ((ENTITY_TAG, source_key(PAYLOAD_SOURCE)), FieldId::new(2)),
2459            ]),
2460        );
2461        let store = session
2462            .db
2463            .store_handle(STORE_PATH)
2464            .expect("identity pre-key test store should resolve");
2465        crate::db::commit::publish_accepted_schema_candidate(
2466            STORE_PATH,
2467            store,
2468            AcceptedSchemaRevision::NONE,
2469            &candidate,
2470        )
2471        .expect("identity candidate should publish with explicit zero state");
2472        session
2473    }
2474
2475    fn initialize_journaled() -> DbSession<JournaledTestCanister> {
2476        let session = DbSession::<JournaledTestCanister>::new(&JOURNALED_STORE_REGISTRY);
2477        session
2478            .db
2479            .ensure_recovered_state()
2480            .expect("journaled identity database should initialize");
2481        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
2482            JOURNALED_STORE_PATH,
2483            AcceptedSchemaRevision::INITIAL,
2484            BTreeMap::from([(ENTITY_TAG, identity_snapshot(JOURNALED_STORE_PATH))]),
2485            BTreeMap::from([
2486                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
2487                ((ENTITY_TAG, source_key(PAYLOAD_SOURCE)), FieldId::new(2)),
2488            ]),
2489        );
2490        let store = session
2491            .db
2492            .store_handle(JOURNALED_STORE_PATH)
2493            .expect("journaled identity store should resolve");
2494        crate::db::commit::publish_accepted_schema_candidate(
2495            JOURNALED_STORE_PATH,
2496            store,
2497            AcceptedSchemaRevision::NONE,
2498            &candidate,
2499        )
2500        .expect("journaled identity candidate should publish");
2501        session
2502    }
2503
2504    fn payload_patch(value: u64) -> AcceptedMutationIntentPatch {
2505        AcceptedMutationIntentPatch::new()
2506            .set_authored(FieldSlot::from_validated_index(1), InputValue::Nat64(value))
2507    }
2508
2509    fn dynamic_payload_patch(value: u64) -> DynamicStructuralPatch {
2510        DynamicStructuralPatch::new(vec![(
2511            "payload".to_string(),
2512            DynamicWriteCell::Value(InputValue::Nat64(value)),
2513        )])
2514    }
2515
2516    fn expected_dynamic_row(id: u64, payload: u64) -> Vec<OutputValue> {
2517        vec![OutputValue::Nat64(id), OutputValue::Nat64(payload)]
2518    }
2519
2520    fn assert_dynamic_payload(session: &DbSession<TestCanister>, key: u64, expected_payload: u64) {
2521        let unchanged = session
2522            .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
2523                entity: ENTITY_NAME.to_string(),
2524                key: InputValue::Nat64(key),
2525                patch: dynamic_payload_patch(expected_payload),
2526            })
2527            .expect("the expected row should remain readable through a no-op update");
2528        assert_eq!(unchanged.affected_rows, 0);
2529        assert_eq!(
2530            unchanged.rows,
2531            vec![expected_dynamic_row(key, expected_payload)],
2532        );
2533    }
2534
2535    fn batch(values: &[u64]) -> Vec<AcceptedStructuralMutation> {
2536        values
2537            .iter()
2538            .map(|value| {
2539                AcceptedStructuralMutation::save(
2540                    MutationMode::Insert,
2541                    AcceptedStructuralMutationTarget::ResolveFromAfterImage,
2542                    payload_patch(*value),
2543                )
2544            })
2545            .collect()
2546    }
2547
2548    fn assert_identity_boundary(error: &InternalError) {
2549        assert_eq!(error.class(), ErrorClass::Unsupported);
2550        assert_eq!(error.origin(), ErrorOrigin::Identity);
2551    }
2552
2553    #[test]
2554    fn generated_candidate_collision_is_identity_corruption_before_generic_uniqueness() {
2555        let generated = insert_key_exists_after_generation(true);
2556        assert_eq!(generated.class(), ErrorClass::Corruption);
2557        assert_eq!(generated.origin(), ErrorOrigin::Identity);
2558
2559        let ordinary = insert_key_exists_after_generation(false);
2560        assert_ne!(ordinary.origin(), ErrorOrigin::Identity);
2561    }
2562
2563    #[cfg(target_pointer_width = "64")]
2564    #[test]
2565    fn pre_key_candidate_count_rejects_values_beyond_the_persisted_u32_bound() {
2566        let error = checked_pre_key_candidate_count(
2567            usize::try_from(u64::from(u32::MAX) + 1).expect("64-bit usize should hold u32 + 1"),
2568        )
2569        .expect_err("candidate counts beyond u32 must reject");
2570        assert_identity_boundary(&error);
2571    }
2572
2573    #[test]
2574    #[expect(
2575        clippy::too_many_lines,
2576        reason = "one holding lifecycle proves split, merge, transfer, late-failure neutrality, result order, and Identity state"
2577    )]
2578    fn mixed_structural_batch_preserves_holding_conservation_and_failure_atomicity() {
2579        let session = initialize();
2580        let seeded = session
2581            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, vec![dynamic_payload_patch(100)])
2582            .expect("seed rows should commit");
2583        assert_eq!(seeded.affected_rows, 1);
2584
2585        let split = session
2586            .execute_trusted_dynamic_mutation_batch(vec![
2587                DynamicMutation::Update {
2588                    entity: ENTITY_NAME.to_string(),
2589                    key: InputValue::Nat64(1),
2590                    patch: dynamic_payload_patch(60),
2591                },
2592                DynamicMutation::Insert {
2593                    entity: ENTITY_NAME.to_string(),
2594                    patch: dynamic_payload_patch(40),
2595                },
2596            ])
2597            .expect("one holding should split atomically");
2598        assert_eq!(split.affected_rows, 2);
2599        assert_eq!(
2600            split.rows,
2601            vec![expected_dynamic_row(1, 60), expected_dynamic_row(2, 40),],
2602            "split after-images must retain input order and exact quantity",
2603        );
2604
2605        let rejected_split = session
2606            .execute_trusted_dynamic_mutation_batch(vec![
2607                DynamicMutation::Update {
2608                    entity: ENTITY_NAME.to_string(),
2609                    key: InputValue::Nat64(1),
2610                    patch: dynamic_payload_patch(50),
2611                },
2612                DynamicMutation::Insert {
2613                    entity: ENTITY_NAME.to_string(),
2614                    patch: DynamicStructuralPatch::new(Vec::new()),
2615                },
2616            ])
2617            .expect_err("an invalid split output must reject the staged source update");
2618        assert_eq!(rejected_split.class(), ErrorClass::Unsupported);
2619        assert_eq!(rejected_split.origin(), ErrorOrigin::Executor);
2620        assert_dynamic_payload(&session, 1, 60);
2621        assert_dynamic_payload(&session, 2, 40);
2622
2623        let transfer = session
2624            .execute_trusted_dynamic_mutation_batch(vec![
2625                DynamicMutation::Update {
2626                    entity: ENTITY_NAME.to_string(),
2627                    key: InputValue::Nat64(1),
2628                    patch: dynamic_payload_patch(70),
2629                },
2630                DynamicMutation::Update {
2631                    entity: ENTITY_NAME.to_string(),
2632                    key: InputValue::Nat64(2),
2633                    patch: dynamic_payload_patch(30),
2634                },
2635            ])
2636            .expect("distinct transfer patches should share one atomic batch");
2637        assert_eq!(
2638            transfer.rows,
2639            vec![expected_dynamic_row(1, 70), expected_dynamic_row(2, 30),],
2640            "the transfer must preserve the exact total quantity",
2641        );
2642
2643        let merge = session
2644            .execute_trusted_dynamic_mutation_batch(vec![
2645                DynamicMutation::Delete {
2646                    entity: ENTITY_NAME.to_string(),
2647                    key: InputValue::Nat64(2),
2648                },
2649                DynamicMutation::Update {
2650                    entity: ENTITY_NAME.to_string(),
2651                    key: InputValue::Nat64(1),
2652                    patch: dynamic_payload_patch(100),
2653                },
2654            ])
2655            .expect("two holdings should merge atomically");
2656        assert_eq!(
2657            merge.rows,
2658            vec![expected_dynamic_row(2, 30), expected_dynamic_row(1, 100),],
2659            "delete before-images and update after-images must retain input order",
2660        );
2661
2662        let resplit = session
2663            .execute_trusted_dynamic_mutation_batch(vec![
2664                DynamicMutation::Update {
2665                    entity: ENTITY_NAME.to_string(),
2666                    key: InputValue::Nat64(1),
2667                    patch: dynamic_payload_patch(60),
2668                },
2669                DynamicMutation::Insert {
2670                    entity: ENTITY_NAME.to_string(),
2671                    patch: dynamic_payload_patch(40),
2672                },
2673            ])
2674            .expect("the merged holding should split again");
2675        assert_eq!(
2676            resplit.rows,
2677            vec![expected_dynamic_row(1, 60), expected_dynamic_row(3, 40),],
2678        );
2679
2680        let rejected_merge = session
2681            .execute_trusted_dynamic_mutation_batch(vec![
2682                DynamicMutation::Delete {
2683                    entity: ENTITY_NAME.to_string(),
2684                    key: InputValue::Nat64(3),
2685                },
2686                DynamicMutation::Update {
2687                    entity: ENTITY_NAME.to_string(),
2688                    key: InputValue::Nat64(99),
2689                    patch: dynamic_payload_patch(100),
2690                },
2691            ])
2692            .expect_err("a late missing merge target must preserve the earlier staged delete");
2693        assert_eq!(rejected_merge.class(), ErrorClass::NotFound);
2694        assert_dynamic_payload(&session, 1, 60);
2695        assert_dynamic_payload(&session, 3, 40);
2696
2697        SCHEMA_STORE.with(|store| {
2698            let cursor = store
2699                .borrow()
2700                .identity_statement_cursor(
2701                    database_incarnation_id().expect("database incarnation should remain readable"),
2702                    ENTITY_TAG,
2703                    FieldId::new(1),
2704                    &AcceptedFieldKind::Nat64,
2705                )
2706                .expect("mixed Identity state should remain readable");
2707            assert_eq!(cursor.expected_high_water(), 3);
2708            assert!(!cursor.has_allocations());
2709        });
2710    }
2711
2712    #[test]
2713    fn mixed_structural_batch_rejects_duplicate_holding_targets_without_mutation() {
2714        let session = initialize();
2715        session
2716            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, vec![dynamic_payload_patch(100)])
2717            .expect("the holding fixture should initialize");
2718
2719        let duplicate = session
2720            .execute_trusted_dynamic_mutation_batch(vec![
2721                DynamicMutation::Update {
2722                    entity: ENTITY_NAME.to_string(),
2723                    key: InputValue::Nat64(1),
2724                    patch: dynamic_payload_patch(60),
2725                },
2726                DynamicMutation::Delete {
2727                    entity: ENTITY_NAME.to_string(),
2728                    key: InputValue::Nat64(1),
2729                },
2730            ])
2731            .expect_err("duplicate targets across operation kinds must reject");
2732        assert!(matches!(
2733            duplicate.diagnostic().detail(),
2734            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2735                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchDuplicateKey,
2736            }),
2737        ));
2738        assert_dynamic_payload(&session, 1, 100);
2739    }
2740
2741    #[test]
2742    fn mixed_structural_batch_rejects_empty_and_over_bound_before_resolution() {
2743        let session = initialize();
2744        let empty = session
2745            .execute_trusted_dynamic_mutation_batch(Vec::new())
2746            .expect_err("an empty public batch must reject");
2747        assert!(matches!(
2748            empty.diagnostic().detail(),
2749            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2750                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchEmpty,
2751            }),
2752        ));
2753
2754        let requests = (0..=MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS)
2755            .map(|_| DynamicMutation::Delete {
2756                entity: ENTITY_NAME.to_string(),
2757                key: InputValue::Nat64(1),
2758            })
2759            .collect();
2760        let over_bound = session
2761            .execute_trusted_dynamic_mutation_batch(requests)
2762            .expect_err("operation cap plus one must reject before row resolution");
2763        assert!(matches!(
2764            over_bound.diagnostic().detail(),
2765            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2766                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchTooManyItems,
2767            }),
2768        ));
2769    }
2770
2771    #[test]
2772    fn mixed_structural_batch_staged_byte_bound_uses_checked_exact_boundary() {
2773        let mut exact = 0;
2774        add_structural_mutation_staged_bytes(
2775            &mut exact,
2776            [MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES],
2777        )
2778        .expect("the exact staged-byte boundary should admit");
2779        assert_eq!(exact, MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES);
2780
2781        let error = add_structural_mutation_staged_bytes(&mut exact, [1])
2782            .expect_err("one byte above the staged-byte boundary must reject");
2783        assert!(matches!(
2784            error.diagnostic().detail(),
2785            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2786                boundary:
2787                    icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchStagedBytesExceeded,
2788            }),
2789        ));
2790
2791        validate_structural_mutation_result_bytes(MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES)
2792            .expect("the exact result-byte boundary should admit");
2793        let error = validate_structural_mutation_result_bytes(
2794            MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES + 1,
2795        )
2796        .expect_err("one byte above the result-byte boundary must reject");
2797        assert!(matches!(
2798            error.diagnostic().detail(),
2799            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
2800                boundary:
2801                    icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchResultBytesExceeded,
2802            }),
2803        ));
2804    }
2805
2806    #[expect(
2807        clippy::too_many_lines,
2808        reason = "one lifecycle proves shared materialization and every maintained frontend against the same zero-state owner"
2809    )]
2810    #[test]
2811    fn identity_insert_frontends_share_one_committed_range_without_rejected_consumption() {
2812        let session = initialize();
2813        let catalog = session
2814            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
2815            .expect("identity catalog should resolve");
2816        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
2817            .expect("identity row layout should build");
2818        let initial_description = session
2819            .try_describe_entity_by_name(ENTITY_NAME)
2820            .expect("accepted Identity description should resolve");
2821        let initial_identity = initial_description
2822            .identity()
2823            .expect("accepted Identity policy should be described");
2824        assert_eq!(initial_identity.field(), "id");
2825        assert_eq!(initial_identity.generator(), "Identity::next");
2826        assert_eq!(initial_identity.accepted_kind(), "nat64");
2827        assert_eq!(initial_identity.minimum(), 1);
2828        assert_eq!(initial_identity.maximum(), u128::from(u64::MAX));
2829        assert_eq!(initial_identity.high_water(), 0);
2830        assert_eq!(initial_identity.remaining(), u128::from(u64::MAX));
2831        assert!(!initial_identity.exhausted());
2832
2833        let rejected = session
2834            .execute_accepted_structural_save_batch(
2835                &catalog,
2836                &descriptor,
2837                batch(&[1_000, 2_000]),
2838                Timestamp::from_millis(6),
2839                |_| Err::<(), _>(InternalError::executor_unsupported()),
2840            )
2841            .expect_err("a rejected precommit result must not publish its tentative range");
2842        assert_eq!(rejected.class(), ErrorClass::Unsupported);
2843        assert_eq!(DATA_STORE.with(|store| store.borrow().len()), 0);
2844
2845        let rows = session
2846            .execute_accepted_structural_save_batch(
2847                &catalog,
2848                &descriptor,
2849                batch(&[10, 20, 30]),
2850                Timestamp::from_millis(7),
2851                Ok,
2852            )
2853            .expect("one accepted batch should commit rows and one identity range");
2854        assert_eq!(
2855            rows.into_iter().map(|row| row.values).collect::<Vec<_>>(),
2856            vec![
2857                vec![Value::Nat64(1), Value::Nat64(10)],
2858                vec![Value::Nat64(2), Value::Nat64(20)],
2859                vec![Value::Nat64(3), Value::Nat64(30)],
2860            ],
2861        );
2862
2863        let dynamic = session
2864            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
2865                entity: ENTITY_NAME.to_string(),
2866                patch: DynamicStructuralPatch::new(vec![(
2867                    "payload".to_string(),
2868                    DynamicWriteCell::Value(InputValue::Nat64(40)),
2869                )]),
2870            })
2871            .expect("dynamic omission should commit through shared Identity generation");
2872        assert_eq!(dynamic.affected_rows, 1);
2873
2874        for request in [
2875            DynamicMutation::Insert {
2876                entity: ENTITY_NAME.to_string(),
2877                patch: DynamicStructuralPatch::new(vec![
2878                    (
2879                        "id".to_string(),
2880                        DynamicWriteCell::Value(InputValue::Nat64(41)),
2881                    ),
2882                    (
2883                        "payload".to_string(),
2884                        DynamicWriteCell::Value(InputValue::Nat64(42)),
2885                    ),
2886                ]),
2887            },
2888            DynamicMutation::Update {
2889                entity: ENTITY_NAME.to_string(),
2890                key: InputValue::Nat64(1),
2891                patch: DynamicStructuralPatch::new(vec![(
2892                    "id".to_string(),
2893                    DynamicWriteCell::Default,
2894                )]),
2895            },
2896        ] {
2897            let error = session
2898                .execute_trusted_dynamic_mutation(&request)
2899                .expect_err("structural Identity authorship and regeneration must reject");
2900            assert_eq!(error.class(), ErrorClass::Unsupported);
2901            assert_eq!(error.origin(), ErrorOrigin::Executor);
2902        }
2903
2904        let binding = session
2905            .issue_typed_entity_binding(
2906                ENTITY_SOURCE,
2907                &[
2908                    DynamicTypedFieldBindingRequest::new(
2909                        ID_SOURCE.to_string(),
2910                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
2911                        false,
2912                    ),
2913                    DynamicTypedFieldBindingRequest::new(
2914                        PAYLOAD_SOURCE.to_string(),
2915                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
2916                        false,
2917                    ),
2918                ],
2919            )
2920            .expect("typed output should bind the Identity field");
2921        let typed_patch = binding
2922            .bind_write_fields(vec![(
2923                PAYLOAD_SOURCE.to_string(),
2924                DynamicWriteCell::Value(InputValue::Nat64(50)),
2925            )])
2926            .expect("typed payload should lower");
2927        let typed = session
2928            .execute_trusted_typed_mutation(
2929                &binding,
2930                &DynamicTypedMutation::Insert { patch: typed_patch },
2931            )
2932            .expect("typed omission should commit through shared Identity generation");
2933        assert_eq!(
2934            typed
2935                .expect("typed insert should return one mutation result")
2936                .affected_rows,
2937            1,
2938        );
2939        let explicit_typed_patch = binding
2940            .bind_write_fields(vec![
2941                (
2942                    ID_SOURCE.to_string(),
2943                    DynamicWriteCell::Value(InputValue::Nat64(51)),
2944                ),
2945                (
2946                    PAYLOAD_SOURCE.to_string(),
2947                    DynamicWriteCell::Value(InputValue::Nat64(52)),
2948                ),
2949            ])
2950            .expect("the low-level binding should retain exact authored intent");
2951        let explicit_typed_error = session
2952            .execute_trusted_typed_mutation(
2953                &binding,
2954                &DynamicTypedMutation::Insert {
2955                    patch: explicit_typed_patch,
2956                },
2957            )
2958            .expect_err("typed Identity authorship must reject before allocation");
2959        assert_eq!(explicit_typed_error.class(), ErrorClass::Unsupported);
2960        assert_eq!(explicit_typed_error.origin(), ErrorOrigin::Executor);
2961
2962        let replace_error = session
2963            .execute_trusted_dynamic_mutation(&DynamicMutation::Replace {
2964                entity: ENTITY_NAME.to_string(),
2965                key: InputValue::Nat64(99),
2966                patch: DynamicStructuralPatch::new(vec![(
2967                    "payload".to_string(),
2968                    DynamicWriteCell::Value(InputValue::Nat64(60)),
2969                )]),
2970            })
2971            .expect_err("save-as-insert with a chosen Identity must reject");
2972        assert_eq!(replace_error.class(), ErrorClass::Unsupported);
2973        assert_eq!(replace_error.origin(), ErrorOrigin::Executor);
2974
2975        #[cfg(feature = "sql")]
2976        {
2977            for sql in [
2978                "INSERT INTO IdentityRow (payload) VALUES (70) RETURNING id, payload",
2979                "INSERT INTO IdentityRow (id, payload) VALUES (DEFAULT, 80) RETURNING id",
2980            ] {
2981                let _result = session
2982                    .execute_trusted_sql_mutation(sql)
2983                    .expect("SQL omission and DEFAULT should commit Identity generation");
2984            }
2985
2986            let error = session
2987                .execute_trusted_sql_mutation(
2988                    "INSERT INTO IdentityRow (id, payload) VALUES (42, 90)",
2989                )
2990                .expect_err("an explicit SQL Identity value must reject before allocation");
2991            let diagnostic = error.diagnostic();
2992            assert_eq!(
2993                diagnostic.code(),
2994                icydb_diagnostic_code::DiagnosticCode::QuerySqlWriteBoundary,
2995            );
2996            assert!(matches!(
2997                diagnostic.detail(),
2998                Some(icydb_diagnostic_code::DiagnosticDetail::SqlWriteBoundary {
2999                    boundary: icydb_diagnostic_code::SqlWriteBoundaryCode::ExplicitGeneratedField,
3000                }),
3001            ));
3002        }
3003
3004        let expected_committed = if cfg!(feature = "sql") { 7 } else { 5 };
3005        assert_eq!(
3006            DATA_STORE.with(|store| store.borrow().len()),
3007            expected_committed
3008        );
3009        SCHEMA_STORE.with(|store| {
3010            let cursor = store
3011                .borrow()
3012                .identity_statement_cursor(
3013                    database_incarnation_id().expect("database incarnation should remain readable"),
3014                    ENTITY_TAG,
3015                    FieldId::new(1),
3016                    &AcceptedFieldKind::Nat64,
3017                )
3018                .expect("committed writes must leave active state readable");
3019            assert_eq!(cursor.expected_high_water(), u128::from(expected_committed),);
3020            assert!(!cursor.has_allocations());
3021        });
3022        let committed_description = session
3023            .try_describe_entity_by_name(ENTITY_NAME)
3024            .expect("committed Identity description should resolve");
3025        let committed_identity = committed_description
3026            .identity()
3027            .expect("accepted Identity policy should remain described");
3028        assert_eq!(
3029            committed_identity.high_water(),
3030            u128::from(expected_committed),
3031        );
3032        assert_eq!(
3033            committed_identity.remaining(),
3034            u128::from(u64::MAX - expected_committed),
3035        );
3036        assert!(!committed_identity.exhausted());
3037    }
3038
3039    #[test]
3040    #[expect(
3041        clippy::too_many_lines,
3042        reason = "one ordered scenario exercises every durable interruption boundary, guarded recovery, derived rebuild, and both integrity tiers"
3043    )]
3044    fn journaled_identity_recovery_quiesces_every_publication_interruption_before_reallocation() {
3045        let session = initialize_journaled();
3046        let catalog = session
3047            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
3048            .expect("journaled identity catalog should resolve");
3049        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
3050            .expect("journaled identity row layout should build");
3051
3052        for (ordinal, interruption) in [
3053            MutationCommitInterruption::MarkerPersisted,
3054            MutationCommitInterruption::JournalPublished,
3055            MutationCommitInterruption::RowsPublished,
3056            MutationCommitInterruption::StateMaterialized,
3057        ]
3058        .into_iter()
3059        .enumerate()
3060        {
3061            interrupt_next_mutation_commit_for_tests(interruption);
3062            let interrupted = session.execute_accepted_structural_save_batch(
3063                &catalog,
3064                &descriptor,
3065                batch(&[u64::try_from(ordinal).expect("ordinal should fit")]),
3066                Timestamp::from_millis(8),
3067                Ok,
3068            );
3069            assert!(
3070                interrupted.is_err(),
3071                "the selected durable boundary should interrupt",
3072            );
3073
3074            let committed = session
3075                .execute_accepted_structural_save_batch(
3076                    &catalog,
3077                    &descriptor,
3078                    batch(&[100 + u64::try_from(ordinal).expect("ordinal should fit")]),
3079                    Timestamp::from_millis(9),
3080                    Ok,
3081                )
3082                .expect("the next mutation must recover before allocating");
3083            let expected_high_water =
3084                u64::try_from((ordinal + 1) * 2).expect("small test high-water should fit");
3085            assert_eq!(
3086                committed
3087                    .into_iter()
3088                    .map(|row| row.values)
3089                    .collect::<Vec<_>>(),
3090                vec![vec![
3091                    Value::Nat64(expected_high_water),
3092                    Value::Nat64(100 + u64::try_from(ordinal).expect("ordinal should fit")),
3093                ]],
3094            );
3095            assert_eq!(
3096                JOURNALED_DATA_STORE.with(|store| store.borrow().len()),
3097                expected_high_water,
3098            );
3099            JOURNALED_SCHEMA_STORE.with(|store| {
3100                let cursor = store
3101                    .borrow()
3102                    .identity_statement_cursor(
3103                        database_incarnation_id()
3104                            .expect("database incarnation should remain readable"),
3105                        ENTITY_TAG,
3106                        FieldId::new(1),
3107                        &AcceptedFieldKind::Nat64,
3108                    )
3109                    .expect("guarded recovery must leave quiescent active state");
3110                assert_eq!(
3111                    cursor.expected_high_water(),
3112                    u128::from(expected_high_water),
3113                );
3114                assert!(!cursor.has_allocations());
3115            });
3116        }
3117
3118        for (ordinal, (interruption, deleted_key)) in [
3119            (MutationCommitInterruption::MarkerPersisted, 2),
3120            (MutationCommitInterruption::JournalPublished, 4),
3121            (MutationCommitInterruption::RowPrefixPublished, 6),
3122            (MutationCommitInterruption::RowsPublished, 8),
3123            (MutationCommitInterruption::StateMaterialized, 7),
3124        ]
3125        .into_iter()
3126        .enumerate()
3127        {
3128            let expected_payload =
3129                501 + u64::try_from(ordinal).expect("small interruption ordinal should fit");
3130            interrupt_next_mutation_commit_for_tests(interruption);
3131            let interrupted = session.execute_trusted_dynamic_mutation_batch(vec![
3132                DynamicMutation::Update {
3133                    entity: ENTITY_NAME.to_string(),
3134                    key: InputValue::Nat64(1),
3135                    patch: dynamic_payload_patch(expected_payload),
3136                },
3137                DynamicMutation::Delete {
3138                    entity: ENTITY_NAME.to_string(),
3139                    key: InputValue::Nat64(deleted_key),
3140                },
3141            ]);
3142            assert!(
3143                interrupted.is_err(),
3144                "the selected caller-key mixed publication boundary should interrupt",
3145            );
3146            let recovered_update = session
3147                .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
3148                    entity: ENTITY_NAME.to_string(),
3149                    key: InputValue::Nat64(1),
3150                    patch: dynamic_payload_patch(expected_payload),
3151                })
3152                .expect("guarded reentry should complete the marker-authorized mixed batch");
3153            assert_eq!(
3154                recovered_update.affected_rows, 0,
3155                "the recovered update must already expose its admitted final image",
3156            );
3157            let recovered_delete = session
3158                .execute_trusted_dynamic_mutation(&DynamicMutation::Delete {
3159                    entity: ENTITY_NAME.to_string(),
3160                    key: InputValue::Nat64(deleted_key),
3161                })
3162                .expect_err("the recovered delete must already be materialized");
3163            assert_eq!(recovered_delete.class(), ErrorClass::NotFound);
3164            JOURNALED_SCHEMA_STORE.with(|store| {
3165                let cursor = store
3166                    .borrow()
3167                    .identity_statement_cursor(
3168                        database_incarnation_id()
3169                            .expect("database incarnation should remain readable"),
3170                        ENTITY_TAG,
3171                        FieldId::new(1),
3172                        &AcceptedFieldKind::Nat64,
3173                    )
3174                    .expect("caller-key recovery must preserve active Identity state");
3175                assert_eq!(cursor.expected_high_water(), 8);
3176                assert!(!cursor.has_allocations());
3177            });
3178        }
3179
3180        forget_recovered_domain_for_tests(&session.db)
3181            .expect("the final journal tail should remain recoverable");
3182        session
3183            .db
3184            .ensure_recovered_state()
3185            .expect("derived rebuild must not allocate another identity");
3186
3187        let quick = execute_quick_integrity(&session.db, catalog.inspection_plan())
3188            .expect("quiescent Identity control inventory should be inspectable");
3189        assert_eq!(quick.status(), &QuickIntegrityStatus::CompleteClean);
3190        let row_page = execute_row_integrity_page(
3191            &session.db,
3192            catalog.inspection_plan(),
3193            PhysicalUnitCheckpoint::BeforeFirst,
3194            RowInspectionLimits::standard(),
3195        )
3196        .expect("Identity rows should remain within committed high-water");
3197        assert!(row_page.exhausted());
3198        assert!(row_page.findings().is_empty());
3199
3200        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 3);
3201        assert!(
3202            JOURNALED_INDEX_STORE.with(|store| !store.borrow().is_empty()),
3203            "derived index rebuild should restore witnesses without allocating identities",
3204        );
3205        assert!(!JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
3206        JOURNALED_SCHEMA_STORE.with(|store| {
3207            let cursor = store
3208                .borrow()
3209                .identity_statement_cursor(
3210                    database_incarnation_id().expect("database incarnation should remain readable"),
3211                    ENTITY_TAG,
3212                    FieldId::new(1),
3213                    &AcceptedFieldKind::Nat64,
3214                )
3215                .expect("folded identity state should reopen without allocating");
3216            assert_eq!(cursor.expected_high_water(), 8);
3217            assert!(!cursor.has_allocations());
3218        });
3219    }
3220
3221    #[test]
3222    #[ignore = "release-closeout native timing probe for one marker-authorized Identity recovery"]
3223    fn identity_recovery_closeout_reports_guarded_reentry_time() {
3224        let session = initialize_journaled();
3225        let catalog = session
3226            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
3227            .expect("journaled identity catalog should resolve");
3228        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
3229            .expect("journaled identity row layout should build");
3230
3231        interrupt_next_mutation_commit_for_tests(MutationCommitInterruption::RowsPublished);
3232        let interrupted = session.execute_accepted_structural_save_batch(
3233            &catalog,
3234            &descriptor,
3235            batch(&[1]),
3236            Timestamp::from_millis(10),
3237            Ok,
3238        );
3239        assert!(
3240            interrupted.is_err(),
3241            "the selected publication boundary should interrupt",
3242        );
3243
3244        let start = Instant::now();
3245        let committed = session
3246            .execute_accepted_structural_save_batch(
3247                &catalog,
3248                &descriptor,
3249                batch(&[2]),
3250                Timestamp::from_millis(11),
3251                Ok,
3252            )
3253            .expect("guarded reentry should recover before allocation");
3254        let elapsed = start.elapsed();
3255        assert_eq!(
3256            committed
3257                .into_iter()
3258                .map(|row| row.values)
3259                .collect::<Vec<_>>(),
3260            vec![vec![Value::Nat64(2), Value::Nat64(2)]],
3261        );
3262
3263        println!(
3264            "identity recovery closeout: guarded_reentry_nanos={}",
3265            elapsed.as_nanos(),
3266        );
3267    }
3268}
3269
3270#[cfg(test)]
3271mod targeted_rule_mutation_tests {
3272    use super::{
3273        DbSession, DynamicMutation, DynamicStructuralPatch, DynamicTypedFieldBindingRequest,
3274        DynamicTypedFieldType, DynamicTypedMutation, DynamicWriteCell,
3275    };
3276    use crate::{
3277        db::{
3278            data::{DataStore, encode_input_value_for_candidate_field_contract},
3279            index::IndexStore,
3280            registry::{StoreAllocationIdentities, StoreRegistry, StoreRuntimeStorageCapabilities},
3281            schema::{
3282                AcceptedCheckLiteralV1, AcceptedCompositeCatalog, AcceptedFieldDecodeContract,
3283                AcceptedFieldKind, AcceptedNamedTypeIdentity, AcceptedRuleOperation,
3284                AcceptedRuleTarget, AcceptedSchemaRevision, AcceptedSourceBindingCatalog,
3285                ConstraintOrigin, FieldId, FieldStorageDecode, FieldWriteManagement, LeafCodec,
3286                PersistedFieldSnapshot, PersistedNestedLeafSnapshot, PersistedSchemaSnapshot,
3287                ScalarCodec, SchemaFieldSlot, SchemaFieldWritePolicy, SchemaInsertDefault,
3288                SchemaRowLayout, SchemaStore, SchemaVersion,
3289                accepted_schema_candidate_with_catalogs_for_tests,
3290                build_record_newtype_composite_catalog_for_tests,
3291                empty_accepted_enum_catalog_for_tests, enum_catalog::ValueAdmissionBudget,
3292            },
3293        },
3294        error::{
3295            ConstraintDiagnostic, ConstraintDiagnosticKind, ConstraintValuePathComponent,
3296            InternalError,
3297        },
3298        traits::{CanisterKind, Path},
3299        types::EntityTag,
3300        value::InputValue,
3301    };
3302    use icydb_schema::{
3303        ConstraintSourceKey, EntitySourceKey, FieldSourceKey, ScalarType, TypeSourceKey,
3304    };
3305    use std::{cell::RefCell, collections::BTreeMap};
3306
3307    const STORE_PATH: &str = "session::write::targeted_rule_mutation_tests::Store";
3308    const ENTITY_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity";
3309    const ID_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity::id";
3310    const PROFILE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity::profile";
3311    const UPDATED_AT_SOURCE: &str =
3312        "session::write::targeted_rule_mutation_tests::Entity::updated_at";
3313    const PROFILE_TYPE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Profile";
3314    const DEGREE_TYPE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Degree";
3315    const DEGREE_MEMBER_SOURCE: &str =
3316        "session::write::targeted_rule_mutation_tests::Profile::degree";
3317    const DEGREE_RULE_SOURCE: &str =
3318        "session::write::targeted_rule_mutation_tests::Profile::degree_multiple";
3319
3320    struct TestCanister;
3321
3322    impl Path for TestCanister {
3323        const PATH: &'static str = "session::write::targeted_rule_mutation_tests::Canister";
3324    }
3325
3326    impl CanisterKind for TestCanister {
3327        const COMMIT_MEMORY_ID: u8 = 43;
3328        const COMMIT_STABLE_KEY: &'static str = "icydb.targeted_mutation_tests.commit.v1";
3329        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 44;
3330        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
3331            "icydb.targeted_mutation_tests.integrity.progress.v1";
3332    }
3333
3334    thread_local! {
3335        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
3336        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
3337        static SCHEMA_STORE: RefCell<SchemaStore> =
3338            const { RefCell::new(SchemaStore::init_heap()) };
3339        static STORE_REGISTRY: StoreRegistry = {
3340            let mut registry = StoreRegistry::new();
3341            registry.register_store(
3342                STORE_PATH,
3343                &DATA_STORE,
3344                &INDEX_STORE,
3345                &SCHEMA_STORE,
3346                StoreAllocationIdentities::absent(),
3347                StoreRuntimeStorageCapabilities::heap(),
3348            ).expect("targeted mutation test store should register");
3349            registry
3350        };
3351    }
3352
3353    fn source<T, E: std::fmt::Debug>(raw: &str, parse: impl FnOnce(String) -> Result<T, E>) -> T {
3354        parse(raw.to_string()).expect("test source identity should admit")
3355    }
3356
3357    fn profile_input(degree: u64) -> InputValue {
3358        InputValue::Map(vec![(
3359            InputValue::Text("degree".to_string()),
3360            InputValue::Nat64(degree),
3361        )])
3362    }
3363
3364    fn structural_patch(id: u64, degree: u64) -> DynamicStructuralPatch {
3365        DynamicStructuralPatch::new(vec![
3366            (
3367                "id".to_string(),
3368                DynamicWriteCell::Value(InputValue::Nat64(id)),
3369            ),
3370            (
3371                "profile".to_string(),
3372                DynamicWriteCell::Value(profile_input(degree)),
3373            ),
3374        ])
3375    }
3376
3377    fn encoded_value(
3378        enum_catalog: &crate::db::schema::AcceptedEnumCatalog,
3379        composite_catalog: &AcceptedCompositeCatalog,
3380        name: &str,
3381        kind: &AcceptedFieldKind,
3382        storage_decode: FieldStorageDecode,
3383        leaf_codec: LeafCodec,
3384        value: InputValue,
3385    ) -> Vec<u8> {
3386        let field = AcceptedFieldDecodeContract::new(name, kind, false, storage_decode, leaf_codec);
3387        encode_input_value_for_candidate_field_contract(
3388            enum_catalog,
3389            composite_catalog,
3390            field,
3391            value,
3392            &mut ValueAdmissionBudget::standard(),
3393        )
3394        .expect("test accepted value should encode")
3395    }
3396
3397    fn nat64_literal(
3398        enum_catalog: &crate::db::schema::AcceptedEnumCatalog,
3399        composite_catalog: &AcceptedCompositeCatalog,
3400        value: u64,
3401    ) -> AcceptedCheckLiteralV1 {
3402        let kind = AcceptedFieldKind::Nat64;
3403        AcceptedCheckLiteralV1::from_accepted_parts(
3404            kind.clone(),
3405            FieldStorageDecode::ByKind,
3406            LeafCodec::Scalar(ScalarCodec::Nat64),
3407            encoded_value(
3408                enum_catalog,
3409                composite_catalog,
3410                "degree_bound",
3411                &kind,
3412                FieldStorageDecode::ByKind,
3413                LeafCodec::Scalar(ScalarCodec::Nat64),
3414                InputValue::Nat64(value),
3415            ),
3416        )
3417    }
3418
3419    fn targeted_diagnostic(error: &InternalError) -> &ConstraintDiagnostic {
3420        let diagnostic = error
3421            .constraint_diagnostic()
3422            .expect("targeted mutation should retain a public diagnostic");
3423        assert_eq!(
3424            diagnostic.constraint_kind(),
3425            ConstraintDiagnosticKind::TargetedRule
3426        );
3427        assert_eq!(diagnostic.field_paths(), &["profile".to_string()]);
3428        assert_eq!(
3429            diagnostic
3430                .value_path()
3431                .expect("targeted mutation should retain its typed value path")
3432                .components(),
3433            &[
3434                ConstraintValuePathComponent::RootField { field_id: 2 },
3435                ConstraintValuePathComponent::RecordMember {
3436                    composite_type_id: 1,
3437                    member_id: 1,
3438                },
3439            ],
3440        );
3441        diagnostic
3442    }
3443
3444    #[expect(
3445        clippy::too_many_lines,
3446        reason = "one end-to-end fixture proves every maintained write frontend converges on the same accepted targeted-rule schedule"
3447    )]
3448    #[test]
3449    fn targeted_rules_converge_across_dynamic_typed_sql_default_timestamp_and_batch_writes() {
3450        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
3451        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
3452        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
3453
3454        let entity_tag = EntityTag::new(93);
3455        let enum_catalog = empty_accepted_enum_catalog_for_tests();
3456        let (composite_catalog, profile_type, degree_type, degree_member) =
3457            build_record_newtype_composite_catalog_for_tests(
3458                "tests::TargetedProfile".to_string(),
3459                "degree".to_string(),
3460                "tests::TargetedDegree".to_string(),
3461                AcceptedFieldKind::Nat64,
3462                &enum_catalog,
3463            )
3464            .expect("targeted mutation composites should close");
3465        let profile_kind = AcceptedFieldKind::Composite {
3466            type_id: profile_type,
3467        };
3468        let profile_default = encoded_value(
3469            &enum_catalog,
3470            &composite_catalog,
3471            "profile",
3472            &profile_kind,
3473            FieldStorageDecode::CatalogValue,
3474            LeafCodec::Structural,
3475            profile_input(12),
3476        );
3477        let fields = vec![
3478            PersistedFieldSnapshot::new_initial(
3479                FieldId::new(1),
3480                "id".to_string(),
3481                SchemaFieldSlot::new(0),
3482                AcceptedFieldKind::Nat64,
3483                Vec::new(),
3484                false,
3485                SchemaInsertDefault::None,
3486                FieldStorageDecode::ByKind,
3487                LeafCodec::Scalar(ScalarCodec::Nat64),
3488            ),
3489            PersistedFieldSnapshot::new_initial(
3490                FieldId::new(2),
3491                "profile".to_string(),
3492                SchemaFieldSlot::new(1),
3493                profile_kind,
3494                vec![PersistedNestedLeafSnapshot::new(
3495                    vec!["degree".to_string()],
3496                    AcceptedFieldKind::Composite {
3497                        type_id: degree_type,
3498                    },
3499                    false,
3500                )],
3501                false,
3502                SchemaInsertDefault::SlotPayload(profile_default),
3503                FieldStorageDecode::CatalogValue,
3504                LeafCodec::Structural,
3505            ),
3506            PersistedFieldSnapshot::new_initial_with_write_policy(
3507                FieldId::new(3),
3508                "updated_at".to_string(),
3509                SchemaFieldSlot::new(2),
3510                AcceptedFieldKind::Timestamp,
3511                Vec::new(),
3512                false,
3513                SchemaInsertDefault::None,
3514                SchemaFieldWritePolicy::from_model_policies(
3515                    None,
3516                    Some(FieldWriteManagement::UpdatedAt),
3517                ),
3518                FieldStorageDecode::ByKind,
3519                LeafCodec::Scalar(ScalarCodec::Timestamp),
3520            ),
3521        ];
3522        let mut snapshot = PersistedSchemaSnapshot::new(
3523            SchemaVersion::initial(),
3524            ENTITY_SOURCE.to_string(),
3525            "TargetedMutation".to_string(),
3526            FieldId::new(1),
3527            SchemaRowLayout::initial(
3528                fields
3529                    .iter()
3530                    .map(|field| (field.id(), field.slot()))
3531                    .collect(),
3532            ),
3533            fields,
3534        );
3535        let constraint_catalog = snapshot
3536            .constraint_catalog()
3537            .clone()
3538            .with_added_targeted_rule(
3539                "profile_degree_multiple".to_string(),
3540                ConstraintOrigin::Generated,
3541                AcceptedRuleTarget::new(
3542                    FieldId::new(2),
3543                    AcceptedNamedTypeIdentity::Composite(degree_type),
3544                ),
3545                AcceptedRuleOperation::MultipleOf {
3546                    divisor: nat64_literal(&enum_catalog, &composite_catalog, 5),
3547                },
3548            )
3549            .expect("targeted mutation rule should allocate");
3550        let targeted_rule_id = constraint_catalog
3551            .constraints()
3552            .last()
3553            .expect("targeted mutation rule should persist")
3554            .id();
3555        snapshot = snapshot.with_constraint_catalog(constraint_catalog);
3556
3557        let entity_source = source(ENTITY_SOURCE, EntitySourceKey::try_new);
3558        let id_source = source(ID_SOURCE, FieldSourceKey::try_new);
3559        let profile_source = source(PROFILE_SOURCE, FieldSourceKey::try_new);
3560        let updated_at_source = source(UPDATED_AT_SOURCE, FieldSourceKey::try_new);
3561        let profile_type_source = source(PROFILE_TYPE_SOURCE, TypeSourceKey::try_new);
3562        let degree_type_source = source(DEGREE_TYPE_SOURCE, TypeSourceKey::try_new);
3563        let degree_member_source = source(DEGREE_MEMBER_SOURCE, FieldSourceKey::try_new);
3564        let degree_rule_source = source(DEGREE_RULE_SOURCE, ConstraintSourceKey::try_new);
3565        let source_bindings = AcceptedSourceBindingCatalog::initial_for_tests(
3566            BTreeMap::from([(entity_source, entity_tag)]),
3567            BTreeMap::from([
3568                ((entity_tag, id_source), FieldId::new(1)),
3569                ((entity_tag, profile_source), FieldId::new(2)),
3570                ((entity_tag, updated_at_source), FieldId::new(3)),
3571            ]),
3572            BTreeMap::from([((entity_tag, degree_rule_source), targeted_rule_id)]),
3573            BTreeMap::new(),
3574            BTreeMap::new(),
3575        )
3576        .with_initial_named_types_for_tests(
3577            BTreeMap::from([
3578                (
3579                    profile_type_source,
3580                    AcceptedNamedTypeIdentity::Composite(profile_type),
3581                ),
3582                (
3583                    degree_type_source,
3584                    AcceptedNamedTypeIdentity::Composite(degree_type),
3585                ),
3586            ]),
3587            BTreeMap::new(),
3588            BTreeMap::from([((profile_type, degree_member_source), degree_member)]),
3589        );
3590        let candidate = accepted_schema_candidate_with_catalogs_for_tests(
3591            STORE_PATH,
3592            AcceptedSchemaRevision::INITIAL,
3593            enum_catalog,
3594            composite_catalog,
3595            source_bindings,
3596            BTreeMap::from([(entity_tag, snapshot)]),
3597        );
3598
3599        let session = DbSession::<TestCanister>::new(&STORE_REGISTRY);
3600        session
3601            .db
3602            .ensure_recovered_state()
3603            .expect("targeted mutation test database should initialize");
3604        let store = session
3605            .db
3606            .store_handle(STORE_PATH)
3607            .expect("targeted mutation test store should resolve");
3608        crate::db::commit::publish_accepted_schema_candidate(
3609            STORE_PATH,
3610            store,
3611            AcceptedSchemaRevision::NONE,
3612            &candidate,
3613        )
3614        .expect("targeted mutation candidate should publish");
3615
3616        let dynamic_error = session
3617            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
3618                entity: "TargetedMutation".to_string(),
3619                patch: structural_patch(1, 12),
3620            })
3621            .expect_err("dynamic write must enforce the targeted rule");
3622        let dynamic_diagnostic = targeted_diagnostic(&dynamic_error);
3623        assert_eq!(dynamic_diagnostic.constraint_id(), targeted_rule_id.get());
3624
3625        let binding = session
3626            .issue_typed_entity_binding(
3627                ENTITY_SOURCE,
3628                &[
3629                    DynamicTypedFieldBindingRequest::new(
3630                        ID_SOURCE.to_string(),
3631                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
3632                        false,
3633                    ),
3634                    DynamicTypedFieldBindingRequest::new(
3635                        PROFILE_SOURCE.to_string(),
3636                        DynamicTypedFieldType::Named(PROFILE_TYPE_SOURCE.to_string()),
3637                        false,
3638                    ),
3639                    DynamicTypedFieldBindingRequest::new(
3640                        UPDATED_AT_SOURCE.to_string(),
3641                        DynamicTypedFieldType::Scalar(ScalarType::Timestamp),
3642                        false,
3643                    ),
3644                ],
3645            )
3646            .expect("targeted typed binding should issue");
3647        let typed_patch = binding
3648            .bind_write_fields(vec![
3649                (
3650                    ID_SOURCE.to_string(),
3651                    DynamicWriteCell::Value(InputValue::Nat64(2)),
3652                ),
3653                (
3654                    PROFILE_SOURCE.to_string(),
3655                    DynamicWriteCell::Value(profile_input(12)),
3656                ),
3657            ])
3658            .expect("targeted typed patch should bind");
3659        let typed_error = session
3660            .execute_trusted_typed_mutation(
3661                &binding,
3662                &DynamicTypedMutation::Insert { patch: typed_patch },
3663            )
3664            .expect_err("typed write must enforce the targeted rule");
3665        assert_eq!(
3666            targeted_diagnostic(&typed_error).constraint_id(),
3667            targeted_rule_id.get()
3668        );
3669
3670        #[cfg(feature = "sql")]
3671        {
3672            let sql_error = session
3673                .execute_trusted_sql_mutation("INSERT INTO TargetedMutation (id) VALUES (3)")
3674                .expect_err("SQL default resolution must enforce the targeted rule");
3675            let crate::db::QueryError::Execute(execute) = sql_error else {
3676                panic!("targeted SQL write should fail at shared execution admission");
3677            };
3678            assert_eq!(
3679                targeted_diagnostic(execute.as_internal()).constraint_id(),
3680                targeted_rule_id.get()
3681            );
3682        }
3683
3684        session
3685            .execute_trusted_dynamic_mutation_batch(vec![
3686                DynamicMutation::Insert {
3687                    entity: "TargetedMutation".to_string(),
3688                    patch: structural_patch(4, 5),
3689                },
3690                DynamicMutation::Insert {
3691                    entity: "TargetedMutation".to_string(),
3692                    patch: structural_patch(5, 12),
3693                },
3694            ])
3695            .expect_err("one invalid targeted value must reject the whole batch");
3696        assert_eq!(
3697            DATA_STORE.with(|store| store.borrow().exact_entity_count(entity_tag)),
3698            Some(0),
3699            "no frontend or earlier valid batch row may escape targeted admission",
3700        );
3701
3702        let admitted = session
3703            .execute_trusted_dynamic_mutation_batch(vec![
3704                DynamicMutation::Insert {
3705                    entity: "TargetedMutation".to_string(),
3706                    patch: structural_patch(6, 5),
3707                },
3708                DynamicMutation::Insert {
3709                    entity: "TargetedMutation".to_string(),
3710                    patch: structural_patch(7, 10),
3711                },
3712            ])
3713            .expect("compliant targeted values should share one accepted batch");
3714        let [first, second] = admitted.rows.as_slice() else {
3715            panic!("the mixed targeted batch should return two rows");
3716        };
3717        let first_timestamp = first
3718            .get(2)
3719            .expect("the first mixed row should contain its managed timestamp");
3720        assert!(matches!(
3721            first_timestamp,
3722            crate::value::OutputValue::Timestamp(_)
3723        ));
3724        assert_eq!(
3725            second.get(2),
3726            Some(first_timestamp),
3727            "one accepted mixed batch must materialize one managed timestamp",
3728        );
3729        assert_eq!(
3730            DATA_STORE.with(|store| store.borrow().exact_entity_count(entity_tag)),
3731            Some(2),
3732        );
3733    }
3734}