Skip to main content

icydb_core/db/session/
write.rs

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