Skip to main content

icydb_core/db/session/
write.rs

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