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, JournalTailControl, 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, payload_unique: bool) -> 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                payload_unique,
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, false))]),
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_and_payload_uniqueness(
3264        payload_unique: bool,
3265    ) -> (
3266        DbSession<JournaledTestCanister>,
3267        crate::db::RequestExecutionRoot,
3268    ) {
3269        let root = crate::db::RequestExecutionRoot::__new_runtime_root();
3270        let session = DbSession::<JournaledTestCanister>::new(&JOURNALED_STORE_REGISTRY, &root);
3271        session
3272            .db
3273            .drive_startup_recovery_page()
3274            .expect("journaled identity database should initialize");
3275        let candidate = accepted_schema_candidate_with_field_bindings_for_tests(
3276            JOURNALED_STORE_PATH,
3277            AcceptedSchemaRevision::INITIAL,
3278            BTreeMap::from([(
3279                ENTITY_TAG,
3280                identity_snapshot(JOURNALED_STORE_PATH, payload_unique),
3281            )]),
3282            BTreeMap::from([
3283                ((ENTITY_TAG, source_key(ID_SOURCE)), FieldId::new(1)),
3284                ((ENTITY_TAG, source_key(PAYLOAD_SOURCE)), FieldId::new(2)),
3285            ]),
3286        );
3287        let store = session
3288            .db
3289            .store_handle(JOURNALED_STORE_PATH)
3290            .expect("journaled identity store should resolve");
3291        crate::db::commit::publish_accepted_schema_candidate(
3292            JOURNALED_STORE_PATH,
3293            store,
3294            AcceptedSchemaRevision::NONE,
3295            &candidate,
3296        )
3297        .expect("journaled identity candidate should publish");
3298        (session, root)
3299    }
3300
3301    fn initialize_journaled_with_root() -> (
3302        DbSession<JournaledTestCanister>,
3303        crate::db::RequestExecutionRoot,
3304    ) {
3305        initialize_journaled_with_root_and_payload_uniqueness(false)
3306    }
3307
3308    fn initialize_journaled() -> DbSession<JournaledTestCanister> {
3309        initialize_journaled_with_root().0
3310    }
3311
3312    fn initialize_journaled_with_unique_payload() -> DbSession<JournaledTestCanister> {
3313        initialize_journaled_with_root_and_payload_uniqueness(true).0
3314    }
3315
3316    fn drive_journaled_recovery_to_completion(session: &DbSession<JournaledTestCanister>) {
3317        for _ in 0..8 {
3318            if session
3319                .db
3320                .drive_startup_recovery_page()
3321                .expect("dedicated driver recovery should remain valid")
3322            {
3323                return;
3324            }
3325        }
3326        panic!("dedicated driver recovery should quiesce within eight complete batches");
3327    }
3328
3329    fn payload_patch(value: u64) -> AcceptedMutationIntentPatch {
3330        AcceptedMutationIntentPatch::new()
3331            .set_authored(FieldSlot::from_validated_index(1), InputValue::Nat64(value))
3332    }
3333
3334    fn dynamic_payload_patch(value: u64) -> DynamicStructuralPatch {
3335        DynamicStructuralPatch::new(vec![(
3336            "payload".to_string(),
3337            DynamicWriteCell::Value(InputValue::Nat64(value)),
3338        )])
3339    }
3340
3341    fn expected_dynamic_row(id: u64, payload: u64) -> Vec<OutputValue> {
3342        vec![OutputValue::Nat64(id), OutputValue::Nat64(payload)]
3343    }
3344
3345    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3346    fn exact_key_binding<C: CanisterKind>(session: &DbSession<C>) -> DynamicTypedEntityBinding {
3347        session
3348            .issue_typed_entity_binding(
3349                ENTITY_SOURCE,
3350                &[
3351                    DynamicTypedFieldBindingRequest::new(
3352                        ID_SOURCE.to_string(),
3353                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
3354                        false,
3355                    ),
3356                    DynamicTypedFieldBindingRequest::new(
3357                        PAYLOAD_SOURCE.to_string(),
3358                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
3359                        false,
3360                    ),
3361                ],
3362            )
3363            .expect("exact-key test binding should issue")
3364    }
3365
3366    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3367    fn insert_exact_key_fixture<C: CanisterKind>(session: &DbSession<C>, payload: u64) -> u64 {
3368        let output = session
3369            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
3370                entity: ENTITY_NAME.to_string(),
3371                patch: dynamic_payload_patch(payload),
3372            })
3373            .expect("exact-key fixture insert should commit");
3374        match output.rows.as_slice() {
3375            [row] => match row.as_slice() {
3376                [OutputValue::Nat64(id), OutputValue::Nat64(actual_payload)]
3377                    if *actual_payload == payload =>
3378                {
3379                    *id
3380                }
3381                _ => panic!("exact-key fixture should return its identity and payload"),
3382            },
3383            _ => panic!("exact-key fixture insert should return one row"),
3384        }
3385    }
3386
3387    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3388    fn identity_row_stored_bytes<C: CanisterKind>(
3389        session: &DbSession<C>,
3390        store_path: &'static str,
3391        key: u64,
3392    ) -> u64 {
3393        let data_key = DecodedDataStoreKey::try_from_structural_key(ENTITY_TAG, &Value::Nat64(key))
3394            .expect("identity row key should encode");
3395        let raw_key = data_key.to_raw().expect("identity raw key should encode");
3396        let store = session
3397            .db
3398            .recovered_store(store_path)
3399            .expect("identity store should resolve");
3400        store.with_data(|data_store| {
3401            u64::try_from(
3402                data_store
3403                    .get(&raw_key)
3404                    .expect("inserted identity row should exist")
3405                    .len(),
3406            )
3407            .expect("bounded row length should fit u64")
3408        })
3409    }
3410
3411    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3412    fn with_stored_bytes_limit<T>(
3413        limit: u64,
3414        shape_fingerprint_prefix: u64,
3415        operation: impl FnOnce() -> Result<T, crate::db::query::intent::QueryError>,
3416    ) -> Result<T, crate::db::query::intent::QueryError> {
3417        let budget = HardExecutionBudget::uniform_for_tests(
3418            u64::MAX,
3419            HardExecutionFailureHeadroom::new(500, 256),
3420        )
3421        .with_limit_for_tests(
3422            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::StoredBytesRead,
3423            limit,
3424        );
3425        let context = HardExecutionContext::new(
3426            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
3427            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
3428            shape_fingerprint_prefix,
3429        );
3430
3431        with_query_execution_budget_for_tests(budget, context, operation)
3432    }
3433
3434    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3435    fn assert_exact_key_batch<C: CanisterKind>(session: &DbSession<C>) {
3436        let first = insert_exact_key_fixture(session, 41);
3437        let second = insert_exact_key_fixture(session, 42);
3438        let missing = u64::MAX;
3439        let binding = exact_key_binding(session);
3440        let gets_before = DataStore::current_get_call_count();
3441        let result = session
3442            .execute_public_exact_key_batch_for_typed_binding(
3443                &binding,
3444                &[second, missing, first, second],
3445            )
3446            .expect("exact-key batch should execute")
3447            .expect("exact-key binding should remain current");
3448
3449        assert_eq!(result.positions, vec![0, 1, 2, 0]);
3450        assert_eq!(
3451            result.distinct_rows,
3452            vec![
3453                Some(expected_dynamic_row(second, 42)),
3454                None,
3455                Some(expected_dynamic_row(first, 41)),
3456            ],
3457        );
3458        assert_eq!(
3459            DataStore::current_get_call_count().saturating_sub(gets_before),
3460            3,
3461            "four input positions with one duplicate must perform three physical reads",
3462        );
3463    }
3464
3465    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3466    #[test]
3467    fn exact_key_batches_preserve_semantics_across_heap_and_journaled_stores() {
3468        assert_exact_key_batch(&initialize());
3469        assert_exact_key_batch(&initialize_journaled());
3470    }
3471
3472    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3473    fn assert_primary_range_materialization_fetches_once<C: CanisterKind>(
3474        session: &DbSession<C>,
3475        store_path: &'static str,
3476    ) {
3477        let key = insert_exact_key_fixture(session, 41);
3478        let stored_bytes = identity_row_stored_bytes(session, store_path, key);
3479
3480        let scalar = DynamicQuery::new(ENTITY_NAME)
3481            .select(["id", "payload"])
3482            .order_by(asc("id"))
3483            .limit(1);
3484        let gets_before = DataStore::current_get_call_count();
3485        let scalar_page = with_stored_bytes_limit(stored_bytes, 0x7072_696d_6172_792d, || {
3486            session.execute_trusted_live_page(&scalar, None)
3487        })
3488        .expect("one scalar primary-range row should fit one payload-read allowance");
3489        assert_eq!(scalar_page.row_count, 1);
3490        assert_eq!(
3491            DataStore::current_get_call_count().saturating_sub(gets_before),
3492            1,
3493            "scalar primary traversal should fetch its emitted row exactly once",
3494        );
3495
3496        let grouped = DynamicQuery::new(ENTITY_NAME)
3497            .group_by("payload")
3498            .aggregate(crate::db::count())
3499            .grouped_limits(10, 16 * 1_024)
3500            .limit(1);
3501        let gets_before = DataStore::current_get_call_count();
3502        let grouped_page = with_stored_bytes_limit(stored_bytes, 0x6772_6f75_7065_642d, || {
3503            session.execute_trusted_dynamic_grouped_query(&grouped)
3504        })
3505        .expect("one grouped primary-range row should fit one payload-read allowance");
3506        assert_eq!(grouped_page.row_count, 1);
3507        assert_eq!(
3508            DataStore::current_get_call_count().saturating_sub(gets_before),
3509            1,
3510            "grouped primary traversal should fetch its source row exactly once",
3511        );
3512    }
3513
3514    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3515    #[test]
3516    fn row_materialization_fetches_each_required_payload_at_most_once() {
3517        assert_primary_range_materialization_fetches_once(&initialize(), STORE_PATH);
3518        assert_primary_range_materialization_fetches_once(
3519            &initialize_journaled(),
3520            JOURNALED_STORE_PATH,
3521        );
3522    }
3523
3524    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3525    #[test]
3526    fn ordered_grouped_pages_close_a_group_spanning_physical_refills_before_resume() {
3527        let session = initialize();
3528        let mut patches = Vec::new();
3529        for _ in 0..70 {
3530            patches.push(dynamic_payload_patch(10));
3531        }
3532        for _ in 0..3 {
3533            patches.push(dynamic_payload_patch(20));
3534        }
3535        patches.push(dynamic_payload_patch(30));
3536        let inserted = session
3537            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, patches)
3538            .expect("ordered grouped continuation rows should insert");
3539        assert_eq!(inserted.rows.len(), 74);
3540
3541        let query = DynamicQuery::new(ENTITY_NAME)
3542            .group_by("payload")
3543            .aggregate(crate::db::count())
3544            .aggregate(crate::db::sum("id"))
3545            .order_by(asc("payload"))
3546            .grouped_limits(4, 16 * 1_024)
3547            .limit(1);
3548        let expected = [
3549            (10_u64, 70_u64, crate::types::Decimal::new(2_485, 0)),
3550            (20, 3, crate::types::Decimal::new(216, 0)),
3551            (30, 1, crate::types::Decimal::new(74, 0)),
3552        ];
3553        let mut continuation: Option<String> = None;
3554        let mut seen_cursors = std::collections::BTreeSet::new();
3555
3556        for (page_index, (group_key, row_count, id_sum)) in expected.into_iter().enumerate() {
3557            let request = continuation.as_ref().map_or_else(
3558                || query.clone(),
3559                |cursor| query.clone().cursor(cursor.clone()),
3560            );
3561            let entries_before = IndexStore::current_entry_read_count();
3562            let rows_before = DataStore::current_get_call_count();
3563            let page = session
3564                .execute_trusted_dynamic_grouped_query(&request)
3565                .unwrap_or_else(|error| {
3566                    panic!("ordered grouped page {page_index} should execute: {error:?}")
3567                });
3568            let entries_read =
3569                IndexStore::current_entry_read_count().saturating_sub(entries_before);
3570            let rows_read = DataStore::current_get_call_count().saturating_sub(rows_before);
3571
3572            assert_eq!(page.row_count, 1);
3573            let [row] = page.rows.as_slice() else {
3574                panic!("ordered grouped page must contain exactly one closed group")
3575            };
3576            assert_eq!(row.group_key(), &[OutputValue::Nat64(group_key)]);
3577            assert_eq!(
3578                row.aggregate_values(),
3579                &[OutputValue::Nat64(row_count), OutputValue::Decimal(id_sum),],
3580            );
3581            if page_index == 0 {
3582                assert!(
3583                    entries_read.saturating_add(rows_read) >= 70,
3584                    "the first closed group must span the maintained 64-entry physical refill",
3585                );
3586            }
3587
3588            continuation = page.next_cursor;
3589            if page_index + 1 < expected.len() {
3590                let cursor = continuation
3591                    .as_ref()
3592                    .expect("another closed group should retain continuation");
3593                assert!(
3594                    seen_cursors.insert(cursor.clone()),
3595                    "ordered grouped continuation must advance monotonically",
3596                );
3597            } else {
3598                assert_eq!(continuation, None);
3599            }
3600        }
3601    }
3602
3603    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3604    #[test]
3605    fn exhaustive_pages_require_and_recompare_the_complete_source_proof() {
3606        let session = initialize();
3607        let first = insert_exact_key_fixture(&session, 41);
3608        let second = insert_exact_key_fixture(&session, 42);
3609        let third = insert_exact_key_fixture(&session, 43);
3610        let query = DynamicQuery::new(ENTITY_NAME)
3611            .select(["id", "payload"])
3612            .order_by(asc("id"));
3613
3614        let page = session
3615            .execute_trusted_exhaustive_page(&query, None, None)
3616            .expect("initial exhaustive page should capture its source proof");
3617        assert_eq!(
3618            page.rows,
3619            vec![
3620                expected_dynamic_row(first, 41),
3621                expected_dynamic_row(second, 42),
3622            ],
3623        );
3624        let continuation = page
3625            .continuation
3626            .as_deref()
3627            .expect("unreturned row should retain exhaustive continuation");
3628        assert!(matches!(
3629            session.execute_trusted_exhaustive_page(&query, Some(continuation), None),
3630            Err(ExhaustiveReadError::Revision(
3631                ReadSetRevisionError::ResumeProofRequired
3632            )),
3633        ));
3634        let resumed = session
3635            .execute_trusted_exhaustive_page(&query, Some(continuation), Some(&page.proof))
3636            .expect("unchanged proof should resume exhaustive traversal");
3637        assert_eq!(resumed.rows, vec![expected_dynamic_row(third, 43)]);
3638        assert_eq!(resumed.continuation, None);
3639
3640        let stale_page = session
3641            .execute_trusted_exhaustive_page(&query, None, None)
3642            .expect("fresh exhaustive page should capture current revision");
3643        let stale_continuation = stale_page
3644            .continuation
3645            .as_deref()
3646            .expect("fresh three-row traversal should retain continuation");
3647        let _ = insert_exact_key_fixture(&session, 44);
3648        assert!(matches!(
3649            session.execute_trusted_exhaustive_page(
3650                &query,
3651                Some(stale_continuation),
3652                Some(&stale_page.proof),
3653            ),
3654            Err(ExhaustiveReadError::Revision(
3655                ReadSetRevisionError::StoreDataChanged { .. }
3656            )),
3657        ));
3658    }
3659
3660    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3661    #[test]
3662    fn heap_sources_cannot_back_durable_resumable_jobs() {
3663        let session = initialize();
3664        let proof = session
3665            .capture_read_set_revision_proof(&[ENTITY_NAME])
3666            .expect("heap source proof should capture for one-call exhaustive reads");
3667        let job_id = ResumableJobId::try_from_bytes([70; 32])
3668            .expect("nonzero heap test job identity should admit");
3669
3670        assert!(matches!(
3671            session.start_resumable_job(job_id, proof, Vec::new()),
3672            Err(ResumableJobError::SourceProof(
3673                ReadSetRevisionError::DurableStoreRequired { .. }
3674            )),
3675        ));
3676    }
3677
3678    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3679    #[test]
3680    fn proof_and_progress_controls_charge_one_shared_request_scope() {
3681        let (session, root) = initialize_journaled_with_root();
3682        let resource = icydb_diagnostic_code::DiagnosticExecutionBudgetResource::QueryExecutions;
3683        let before = root.observed(resource);
3684        let proof = session
3685            .capture_read_set_revision_proof(&[ENTITY_NAME])
3686            .expect("proof capture should use the retained request scope");
3687        let job_id = ResumableJobId::try_from_bytes([75; 32])
3688            .expect("nonzero accounting job identity should admit");
3689        session
3690            .start_resumable_job(job_id, proof, Vec::new())
3691            .expect("job start should use the same retained request scope");
3692        let _ = session
3693            .resumable_job_state(job_id)
3694            .expect("job load should use the same retained request scope");
3695
3696        assert_eq!(root.observed(resource).saturating_sub(before), 3);
3697    }
3698
3699    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3700    #[test]
3701    fn source_proofs_ignore_unrelated_stores_but_bind_access_state_changes() {
3702        let session = initialize();
3703        let proof = session
3704            .capture_read_set_revision_proof(&[ENTITY_NAME])
3705            .expect("source proof should cover only the entity's physical store");
3706        let shared_store_proof = session
3707            .capture_read_set_revision_proof(&[ENTITY_NAME, ENTITY_NAME])
3708            .expect("entities sharing one physical source should deduplicate");
3709        assert_eq!(shared_store_proof, proof);
3710        assert_eq!(shared_store_proof.stores().len(), 1);
3711        let unrelated = session
3712            .db
3713            .store_handle(UNRELATED_STORE_PATH)
3714            .expect("unrelated registered store should resolve");
3715        unrelated.with_data_mut(|store| {
3716            let _ = store.remove(&RawDataStoreKey::from_persisted_bytes(vec![1]));
3717        });
3718        session
3719            .verify_read_set_revision_proof(&proof)
3720            .expect("a nonparticipating store mutation must not invalidate the proof");
3721
3722        let source = session
3723            .db
3724            .store_handle(STORE_PATH)
3725            .expect("participating source store should resolve");
3726        source
3727            .mark_index_building()
3728            .expect("source access-state transition should advance its revision");
3729        assert!(matches!(
3730            session.verify_read_set_revision_proof(&proof),
3731            Err(ExhaustiveReadError::Revision(
3732                ReadSetRevisionError::StoreAccessChanged { .. }
3733            )),
3734        ));
3735    }
3736
3737    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3738    #[expect(
3739        clippy::too_many_lines,
3740        reason = "one lifecycle test proves successful replay plus pre-page and post-page source invalidation without sharing progress state across tests"
3741    )]
3742    #[test]
3743    fn journaled_job_advance_is_idempotent_and_revision_checked_on_both_sides() {
3744        let session = initialize_journaled();
3745        let proof = session
3746            .capture_read_set_revision_proof(&[ENTITY_NAME])
3747            .expect("journaled source proof should capture");
3748        let job_id =
3749            ResumableJobId::try_from_bytes([71; 32]).expect("nonzero job identity should admit");
3750        session
3751            .start_resumable_job(job_id, proof, vec![0])
3752            .expect("journaled job should start outside its protected source revision");
3753        let request = ResumableJobAdvanceRequest::new(
3754            job_id,
3755            0,
3756            ResumableJobIdempotencyKey::new("page-0")
3757                .expect("bounded idempotency key should admit"),
3758        );
3759        let calls = Cell::new(0_u8);
3760        let receipt = session
3761            .compare_proof_and_advance(&request, |state| {
3762                calls.set(calls.get() + 1);
3763                assert_eq!(state.application_state, vec![0]);
3764                Ok::<_, ()>(
3765                    ResumableJobAdvance::new(Some("cursor-1".to_string()), vec![1], vec![9])
3766                        .expect("bounded application advance should admit"),
3767                )
3768            })
3769            .expect("unchanged source should advance exactly once");
3770        assert_eq!(calls.get(), 1);
3771        assert_eq!(receipt.status, ResumableJobAdvanceStatus::Advanced);
3772        assert_eq!(receipt.committed_sequence, 1);
3773
3774        let replay = session
3775            .compare_proof_and_advance::<()>(&request, |_| {
3776                panic!("lost-response replay must not execute application work")
3777            })
3778            .expect("same request identity should return its persisted receipt");
3779        assert_eq!(replay, receipt);
3780        let retained = session
3781            .resumable_job_state(job_id)
3782            .expect("advanced state should remain durable");
3783        assert_eq!(retained.sequence, 1);
3784        assert_eq!(retained.application_state, vec![1]);
3785
3786        let _ = insert_exact_key_fixture(&session, 51);
3787        let pre_change_request = ResumableJobAdvanceRequest::new(
3788            job_id,
3789            1,
3790            ResumableJobIdempotencyKey::new("page-1")
3791                .expect("bounded idempotency key should admit"),
3792        );
3793        let pre_change_calls = Cell::new(0_u8);
3794        let invalidated = session
3795            .compare_proof_and_advance::<()>(&pre_change_request, |_| {
3796                pre_change_calls.set(pre_change_calls.get() + 1);
3797                unreachable!("pre-page proof failure must reject before application work")
3798            })
3799            .expect("source drift should persist one replayable invalidation receipt");
3800        assert_eq!(pre_change_calls.get(), 0);
3801        assert_eq!(invalidated.status, ResumableJobAdvanceStatus::Invalidated);
3802        let invalidated_state = session
3803            .resumable_job_state(job_id)
3804            .expect("invalidated job should remain inspectable");
3805        assert_eq!(invalidated_state.status, ResumableJobStatus::Invalidated);
3806        assert_eq!(invalidated_state.continuation, None);
3807        assert_eq!(invalidated_state.application_state, vec![1]);
3808        assert_eq!(
3809            session
3810                .compare_proof_and_advance::<()>(&pre_change_request, |_| {
3811                    panic!("invalidation replay must not execute application work")
3812                })
3813                .expect("lost invalidation reply should replay exactly"),
3814            invalidated,
3815        );
3816
3817        let post_proof = session
3818            .capture_read_set_revision_proof(&[ENTITY_NAME])
3819            .expect("post-change journaled proof should capture");
3820        let post_job_id = ResumableJobId::try_from_bytes([72; 32])
3821            .expect("nonzero post-change job identity should admit");
3822        session
3823            .start_resumable_job(post_job_id, post_proof, vec![7])
3824            .expect("post-change journaled job should start");
3825        let post_request = ResumableJobAdvanceRequest::new(
3826            post_job_id,
3827            0,
3828            ResumableJobIdempotencyKey::new("post-page-0")
3829                .expect("bounded idempotency key should admit"),
3830        );
3831        let post_receipt = session
3832            .compare_proof_and_advance::<()>(&post_request, |_| {
3833                let _ = insert_exact_key_fixture(&session, 52);
3834                Ok(ResumableJobAdvance::new(None, vec![8], vec![10])
3835                    .expect("bounded post-change candidate should admit"))
3836            })
3837            .expect("post-page drift should discard the candidate and persist invalidation");
3838        assert_eq!(post_receipt.status, ResumableJobAdvanceStatus::Invalidated);
3839        let post_state = session
3840            .resumable_job_state(post_job_id)
3841            .expect("post-page invalidation should remain inspectable");
3842        assert_eq!(post_state.status, ResumableJobStatus::Invalidated);
3843        assert_eq!(post_state.application_state, vec![7]);
3844        session
3845            .acknowledge_resumable_job(post_job_id, post_state.sequence)
3846            .expect("terminal job acknowledgement should remove retained progress");
3847        session
3848            .acknowledge_resumable_job(post_job_id, post_state.sequence)
3849            .expect("lost acknowledgement reply should be safely replayable");
3850        assert_eq!(
3851            session.resumable_job_state(post_job_id),
3852            Err(ResumableJobError::NotFound),
3853        );
3854
3855        let completed_job_id = ResumableJobId::try_from_bytes([74; 32])
3856            .expect("nonzero completed job identity should admit");
3857        let completed_proof = session
3858            .capture_read_set_revision_proof(&[ENTITY_NAME])
3859            .expect("completed-job source proof should capture");
3860        session
3861            .start_resumable_job(completed_job_id, completed_proof, Vec::new())
3862            .expect("completed-job fixture should start");
3863        let completed_request = ResumableJobAdvanceRequest::new(
3864            completed_job_id,
3865            0,
3866            ResumableJobIdempotencyKey::new("complete")
3867                .expect("bounded completion key should admit"),
3868        );
3869        let completed_receipt = session
3870            .compare_proof_and_advance::<()>(&completed_request, |_| {
3871                Ok(ResumableJobAdvance::new(None, vec![99], vec![100])
3872                    .expect("bounded terminal advance should admit"))
3873            })
3874            .expect("null continuation should commit terminal completion");
3875        let completed_state = session
3876            .resumable_job_state(completed_job_id)
3877            .expect("completed state should remain replayable before acknowledgement");
3878        assert_eq!(completed_state.status, ResumableJobStatus::Completed);
3879        assert_eq!(
3880            session
3881                .compare_proof_and_advance::<()>(&completed_request, |_| {
3882                    panic!("completed request replay must not execute application work")
3883                })
3884                .expect("completed request should replay until acknowledgement"),
3885            completed_receipt,
3886        );
3887        let after_completion = ResumableJobAdvanceRequest::new(
3888            completed_job_id,
3889            1,
3890            ResumableJobIdempotencyKey::new("after-complete")
3891                .expect("bounded post-completion key should admit"),
3892        );
3893        assert!(matches!(
3894            session.compare_proof_and_advance::<()>(&after_completion, |_| {
3895                panic!("completed jobs cannot execute another page")
3896            }),
3897            Err(CompareProofAndAdvanceError::Protocol(
3898                ResumableJobError::Completed
3899            )),
3900        ));
3901        session
3902            .acknowledge_resumable_job(completed_job_id, completed_state.sequence)
3903            .expect("completed job should acknowledge and free capacity");
3904        session
3905            .acknowledge_resumable_job(completed_job_id, completed_state.sequence)
3906            .expect("completion acknowledgement should be idempotent");
3907
3908        let stale_job_id = ResumableJobId::try_from_bytes([73; 32])
3909            .expect("nonzero stale-sequence job identity should admit");
3910        let stale_proof = session
3911            .capture_read_set_revision_proof(&[ENTITY_NAME])
3912            .expect("stale-sequence source proof should capture");
3913        session
3914            .start_resumable_job(stale_job_id, stale_proof, Vec::new())
3915            .expect("stale-sequence job should start");
3916        let stale_request = ResumableJobAdvanceRequest::new(
3917            stale_job_id,
3918            4,
3919            ResumableJobIdempotencyKey::new("stale").expect("bounded idempotency key should admit"),
3920        );
3921        assert!(matches!(
3922            session.compare_proof_and_advance::<()>(&stale_request, |_| {
3923                panic!("stale sequence must reject before application work")
3924            }),
3925            Err(CompareProofAndAdvanceError::Protocol(
3926                ResumableJobError::StaleSequence {
3927                    expected: 4,
3928                    actual: 0,
3929                }
3930            )),
3931        ));
3932        assert_eq!(
3933            session.acknowledge_resumable_job(stale_job_id, 0),
3934            Err(ResumableJobError::NotTerminal),
3935        );
3936    }
3937
3938    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3939    #[test]
3940    fn exact_key_batch_uses_typed_hard_execution_budget() {
3941        let session = initialize();
3942        let binding = exact_key_binding(&session);
3943        let budget =
3944            HardExecutionBudget::uniform_for_tests(0, HardExecutionFailureHeadroom::new(500, 256));
3945        let error = session
3946            .execute_exact_key_batch_with_hard_budget_for_tests(&binding, &[u64::MAX], &budget)
3947            .expect_err("zero query budget should reject the exact-key route");
3948
3949        assert!(matches!(
3950            error.diagnostic().detail(),
3951            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
3952                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
3953            })
3954        ));
3955        let facts = error.diagnostic_facts();
3956        assert_eq!(
3957            &facts[..5],
3958            &[
3959                (
3960                    icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
3961                    icydb_diagnostic_code::DiagnosticExecutionBudgetResource::QueryExecutions.raw(),
3962                ),
3963                (icydb_diagnostic_code::DiagnosticFactTag::Limit, 0),
3964                (icydb_diagnostic_code::DiagnosticFactTag::Actual, 1),
3965                (
3966                    icydb_diagnostic_code::DiagnosticFactTag::ExecutionBudgetScope,
3967                    icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution.raw(),
3968                ),
3969                (
3970                    icydb_diagnostic_code::DiagnosticFactTag::ExecutionLane,
3971                    icydb_diagnostic_code::DiagnosticExecutionLane::PublicRead.raw(),
3972                ),
3973            ],
3974        );
3975        assert_eq!(
3976            facts[5].0,
3977            icydb_diagnostic_code::DiagnosticFactTag::QueryShapeFingerprintPrefix,
3978        );
3979        assert_ne!(facts[5].1, 0);
3980    }
3981
3982    #[cfg(all(feature = "sql", feature = "diagnostics"))]
3983    fn assert_planned_query_exhausts(
3984        session: &DbSession<TestCanister>,
3985        query: &crate::db::DynamicQuery,
3986        resource: icydb_diagnostic_code::DiagnosticExecutionBudgetResource,
3987    ) {
3988        let budget = HardExecutionBudget::uniform_for_tests(
3989            u64::MAX,
3990            HardExecutionFailureHeadroom::new(500, 256),
3991        )
3992        .with_limit_for_tests(resource, 0);
3993        let context = HardExecutionContext::new(
3994            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
3995            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
3996            0x7068_7973_6963_616c,
3997        );
3998        let error = with_query_execution_budget_for_tests(budget, context, || {
3999            session.execute_trusted_live_page(query, None)
4000        })
4001        .expect_err("the injected zero resource allowance should reject planned execution");
4002
4003        assert!(matches!(
4004            error.diagnostic().detail(),
4005            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4006                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
4007            })
4008        ));
4009        assert_eq!(
4010            error.diagnostic_facts()[0],
4011            (
4012                icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
4013                resource.raw(),
4014            ),
4015        );
4016    }
4017
4018    #[cfg(all(feature = "sql", feature = "diagnostics"))]
4019    fn assert_grouped_query_exhausts(
4020        session: &DbSession<TestCanister>,
4021        query: &crate::db::DynamicQuery,
4022        resource: icydb_diagnostic_code::DiagnosticExecutionBudgetResource,
4023    ) {
4024        let budget = HardExecutionBudget::uniform_for_tests(
4025            u64::MAX,
4026            HardExecutionFailureHeadroom::new(500, 256),
4027        )
4028        .with_limit_for_tests(resource, 0);
4029        let context = HardExecutionContext::new(
4030            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
4031            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
4032            0x6772_6f75_7065_642d,
4033        );
4034        let error = with_query_execution_budget_for_tests(budget, context, || {
4035            session.execute_trusted_dynamic_grouped_query(query)
4036        })
4037        .expect_err("the injected zero resource allowance should reject grouped execution");
4038
4039        assert!(matches!(
4040            error.diagnostic().detail(),
4041            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4042                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
4043            })
4044        ));
4045        assert_eq!(
4046            error.diagnostic_facts()[0],
4047            (
4048                icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
4049                resource.raw(),
4050            ),
4051        );
4052    }
4053
4054    #[cfg(all(feature = "sql", feature = "diagnostics"))]
4055    fn assert_sql_query_exhausts(
4056        session: &DbSession<TestCanister>,
4057        sql: &str,
4058        resource: icydb_diagnostic_code::DiagnosticExecutionBudgetResource,
4059    ) {
4060        let budget = HardExecutionBudget::uniform_for_tests(
4061            u64::MAX,
4062            HardExecutionFailureHeadroom::new(500, 256),
4063        )
4064        .with_limit_for_tests(resource, 0);
4065        let context = HardExecutionContext::new(
4066            icydb_diagnostic_code::DiagnosticExecutionBudgetScope::Execution,
4067            icydb_diagnostic_code::DiagnosticExecutionLane::TrustedRead,
4068            0x7371_6c2d_736f_7274,
4069        );
4070        let error = with_query_execution_budget_for_tests(budget, context, || {
4071            session.execute_trusted_sql_query(sql)
4072        })
4073        .expect_err("the injected zero resource allowance should reject SQL execution");
4074
4075        assert!(matches!(
4076            error.diagnostic().detail(),
4077            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4078                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::ExecutionBudgetExceeded,
4079            })
4080        ));
4081        assert_eq!(
4082            error.diagnostic_facts()[0],
4083            (
4084                icydb_diagnostic_code::DiagnosticFactTag::BudgetResource,
4085                resource.raw(),
4086            ),
4087        );
4088    }
4089
4090    #[cfg(all(feature = "sql", feature = "diagnostics"))]
4091    #[test]
4092    fn planned_read_routes_share_physical_resource_accounting() {
4093        let session = initialize();
4094        let first = insert_exact_key_fixture(&session, 41);
4095        insert_exact_key_fixture(&session, 42);
4096
4097        let fallback = crate::db::DynamicQuery::new(ENTITY_NAME)
4098            .filter(crate::db::FieldRef::new("id").eq(first))
4099            .select(["id", "payload"])
4100            .order_by(crate::db::asc("id"))
4101            .limit(1);
4102        assert_eq!(
4103            session
4104                .execute_trusted_live_page(&fallback, None)
4105                .expect("bounded fallback execution should preserve its result")
4106                .row_count,
4107            1,
4108        );
4109        assert_planned_query_exhausts(
4110            &session,
4111            &fallback,
4112            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::RowsVisited,
4113        );
4114
4115        let covering = crate::db::DynamicQuery::new(ENTITY_NAME)
4116            .filter(crate::db::FieldRef::new("payload").eq(41_u64))
4117            .select(["payload"])
4118            .order_by(crate::db::asc("payload"))
4119            .limit(1);
4120        assert_eq!(
4121            session
4122                .execute_trusted_live_page(&covering, None)
4123                .expect("bounded covering execution should preserve its result")
4124                .row_count,
4125            1,
4126        );
4127        assert_planned_query_exhausts(
4128            &session,
4129            &covering,
4130            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::KeyIndexEntriesVisited,
4131        );
4132
4133        let residual = crate::db::DynamicQuery::new(ENTITY_NAME)
4134            .filter(crate::db::FieldRef::new("payload").eq_field("id"))
4135            .select(["id"])
4136            .order_by(crate::db::asc("id"))
4137            .limit(1);
4138        assert_eq!(
4139            session
4140                .execute_trusted_live_page(&residual, None)
4141                .expect("bounded residual execution should preserve its result")
4142                .row_count,
4143            0,
4144        );
4145        assert_planned_query_exhausts(
4146            &session,
4147            &residual,
4148            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::PredicateExpressionSteps,
4149        );
4150
4151        assert_planned_query_exhausts(
4152            &session,
4153            &fallback,
4154            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::ResultBytes,
4155        );
4156
4157        let grouped = crate::db::DynamicQuery::new(ENTITY_NAME)
4158            .group_by("payload")
4159            .aggregate(crate::db::count())
4160            .order_by(crate::db::asc("payload"))
4161            .grouped_limits(10, 16 * 1_024)
4162            .limit(1);
4163        let grouped_result = session
4164            .execute_trusted_dynamic_grouped_query(&grouped)
4165            .expect("bounded grouped execution should preserve its result");
4166        assert_eq!(grouped_result.row_count, 1);
4167        assert!(grouped_result.next_cursor.is_some());
4168        assert_grouped_query_exhausts(
4169            &session,
4170            &grouped,
4171            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::GroupDistinctEntries,
4172        );
4173        assert_grouped_query_exhausts(
4174            &session,
4175            &grouped,
4176            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::CursorSteps,
4177        );
4178
4179        assert_sql_query_exhausts(
4180            &session,
4181            "SELECT payload, COUNT(*) AS row_count FROM IdentityRow \
4182             GROUP BY payload ORDER BY row_count DESC, payload ASC LIMIT 1",
4183            icydb_diagnostic_code::DiagnosticExecutionBudgetResource::SortEntries,
4184        );
4185    }
4186
4187    fn assert_dynamic_payload<C: CanisterKind>(
4188        session: &DbSession<C>,
4189        key: u64,
4190        expected_payload: u64,
4191    ) {
4192        let unchanged = session
4193            .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
4194                entity: ENTITY_NAME.to_string(),
4195                key: InputValue::Nat64(key),
4196                patch: dynamic_payload_patch(expected_payload),
4197            })
4198            .expect("the expected row should remain readable through a no-op update");
4199        assert_eq!(unchanged.affected_rows, 0);
4200        assert_eq!(
4201            unchanged.rows,
4202            vec![expected_dynamic_row(key, expected_payload)],
4203        );
4204    }
4205
4206    fn assert_exact_batch_backlog_pressure(
4207        pressure: &InternalError,
4208        before: JournalTailControl,
4209        next_sequence: u64,
4210    ) {
4211        assert_eq!(
4212            pressure.diagnostic().error_code(),
4213            icydb_diagnostic_code::ErrorCode::RUNTIME_BOUNDARY_CONVERGENCE_BACKLOG_PRESSURE,
4214        );
4215        assert_eq!(
4216            pressure.diagnostic_facts(),
4217            vec![
4218                (
4219                    icydb_diagnostic_code::DiagnosticFactTag::BacklogResource,
4220                    icydb_diagnostic_code::DiagnosticBacklogResource::Batches.raw(),
4221                ),
4222                (icydb_diagnostic_code::DiagnosticFactTag::CurrentCount, 38),
4223                (icydb_diagnostic_code::DiagnosticFactTag::ProposedCount, 1),
4224                (icydb_diagnostic_code::DiagnosticFactTag::Limit, 38),
4225            ],
4226        );
4227        assert_eq!(
4228            crate::db::commit::next_database_commit_sequence()
4229                .expect("pressure must leave the database sequence readable"),
4230            next_sequence,
4231        );
4232        assert!(matches!(
4233            crate::db::commit::observe_commit_control()
4234                .expect("pressure must leave commit control observable"),
4235            crate::db::commit::CommitControlObservation::Present {
4236                marker_present: false,
4237                ..
4238            },
4239        ));
4240        assert_eq!(
4241            JOURNALED_TAIL_STORE.with(|tail| {
4242                tail.borrow()
4243                    .current_tail_control()
4244                    .expect("pressure must preserve the exact tail control")
4245            }),
4246            before,
4247        );
4248    }
4249
4250    fn batch(values: &[u64]) -> Vec<AcceptedStructuralMutation> {
4251        values
4252            .iter()
4253            .map(|value| {
4254                AcceptedStructuralMutation::save(
4255                    MutationMode::Insert,
4256                    AcceptedStructuralMutationTarget::ResolveFromAfterImage,
4257                    payload_patch(*value),
4258                )
4259            })
4260            .collect()
4261    }
4262
4263    fn atomic_progress_fixture(
4264        identity_byte: u8,
4265    ) -> (
4266        MutationJobRecord,
4267        MutationJobRecord,
4268        MutationProgressRecordOp,
4269    ) {
4270        let job_id = MutationJobId::try_from_bytes([identity_byte; 32])
4271            .expect("nonzero atomic progress job id should admit");
4272        let before = MutationJobRecord::new(job_id, vec![1, identity_byte], vec![2])
4273            .expect("atomic progress predecessor should admit");
4274        let request = MutationJobAdvanceRequest::new(
4275            job_id,
4276            0,
4277            MutationJobIdempotencyKey::new(format!("atomic-{identity_byte}"))
4278                .expect("atomic progress replay key should admit"),
4279        );
4280        let (after, _) = before
4281            .apply_transition(
4282                &request,
4283                MutationJobTransition::new(
4284                    MutationJobStatus::Active,
4285                    MutationJobPhase::Forward,
4286                    vec![3],
4287                    1,
4288                    1,
4289                    0,
4290                ),
4291            )
4292            .expect("atomic progress successor should admit");
4293        let operation = MutationProgressRecordOp::replace(&before, &after)
4294            .expect("atomic progress replacement should admit");
4295        (before, after, operation)
4296    }
4297
4298    fn assert_identity_boundary(error: &InternalError) {
4299        assert_eq!(error.class(), ErrorClass::Unsupported);
4300        assert_eq!(error.origin(), ErrorOrigin::Identity);
4301    }
4302
4303    #[test]
4304    fn generated_candidate_collision_is_identity_corruption_before_generic_uniqueness() {
4305        let generated = insert_key_exists_after_generation(true);
4306        assert_eq!(generated.class(), ErrorClass::Corruption);
4307        assert_eq!(generated.origin(), ErrorOrigin::Identity);
4308
4309        let ordinary = insert_key_exists_after_generation(false);
4310        assert_ne!(ordinary.origin(), ErrorOrigin::Identity);
4311    }
4312
4313    #[cfg(target_pointer_width = "64")]
4314    #[test]
4315    fn pre_key_candidate_count_rejects_values_beyond_the_persisted_u32_bound() {
4316        let error = checked_pre_key_candidate_count(
4317            usize::try_from(u64::from(u32::MAX) + 1).expect("64-bit usize should hold u32 + 1"),
4318        )
4319        .expect_err("candidate counts beyond u32 must reject");
4320        assert_identity_boundary(&error);
4321    }
4322
4323    #[test]
4324    #[expect(
4325        clippy::too_many_lines,
4326        reason = "one holding lifecycle proves split, merge, transfer, late-failure neutrality, result order, and Identity state"
4327    )]
4328    fn mixed_structural_batch_preserves_holding_conservation_and_failure_atomicity() {
4329        let session = initialize();
4330        let seeded = session
4331            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, vec![dynamic_payload_patch(100)])
4332            .expect("seed rows should commit");
4333        assert_eq!(seeded.affected_rows, 1);
4334
4335        let split = session
4336            .execute_trusted_dynamic_mutation_batch(vec![
4337                DynamicMutation::Update {
4338                    entity: ENTITY_NAME.to_string(),
4339                    key: InputValue::Nat64(1),
4340                    patch: dynamic_payload_patch(60),
4341                },
4342                DynamicMutation::Insert {
4343                    entity: ENTITY_NAME.to_string(),
4344                    patch: dynamic_payload_patch(40),
4345                },
4346            ])
4347            .expect("one holding should split atomically");
4348        assert_eq!(split.affected_rows, 2);
4349        assert_eq!(
4350            split.rows,
4351            vec![expected_dynamic_row(1, 60), expected_dynamic_row(2, 40),],
4352            "split after-images must retain input order and exact quantity",
4353        );
4354
4355        let rejected_split = session
4356            .execute_trusted_dynamic_mutation_batch(vec![
4357                DynamicMutation::Update {
4358                    entity: ENTITY_NAME.to_string(),
4359                    key: InputValue::Nat64(1),
4360                    patch: dynamic_payload_patch(50),
4361                },
4362                DynamicMutation::Insert {
4363                    entity: ENTITY_NAME.to_string(),
4364                    patch: DynamicStructuralPatch::new(Vec::new()),
4365                },
4366            ])
4367            .expect_err("an invalid split output must reject the staged source update");
4368        assert_eq!(rejected_split.class(), ErrorClass::Unsupported);
4369        assert_eq!(rejected_split.origin(), ErrorOrigin::Executor);
4370        assert_eq!(
4371            rejected_split.diagnostic_facts(),
4372            vec![
4373                (
4374                    icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
4375                    ENTITY_TAG.value(),
4376                ),
4377                (icydb_diagnostic_code::DiagnosticFactTag::FieldId, 2),
4378                (
4379                    icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
4380                    icydb_diagnostic_code::DiagnosticMutationOperation::Insert.raw(),
4381                ),
4382                (icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 1,),
4383            ],
4384        );
4385        assert_dynamic_payload(&session, 1, 60);
4386        assert_dynamic_payload(&session, 2, 40);
4387
4388        let transfer = session
4389            .execute_trusted_dynamic_mutation_batch(vec![
4390                DynamicMutation::Update {
4391                    entity: ENTITY_NAME.to_string(),
4392                    key: InputValue::Nat64(1),
4393                    patch: dynamic_payload_patch(70),
4394                },
4395                DynamicMutation::Update {
4396                    entity: ENTITY_NAME.to_string(),
4397                    key: InputValue::Nat64(2),
4398                    patch: dynamic_payload_patch(30),
4399                },
4400            ])
4401            .expect("distinct transfer patches should share one atomic batch");
4402        assert_eq!(
4403            transfer.rows,
4404            vec![expected_dynamic_row(1, 70), expected_dynamic_row(2, 30),],
4405            "the transfer must preserve the exact total quantity",
4406        );
4407
4408        let merge = session
4409            .execute_trusted_dynamic_mutation_batch(vec![
4410                DynamicMutation::Delete {
4411                    entity: ENTITY_NAME.to_string(),
4412                    key: InputValue::Nat64(2),
4413                },
4414                DynamicMutation::Update {
4415                    entity: ENTITY_NAME.to_string(),
4416                    key: InputValue::Nat64(1),
4417                    patch: dynamic_payload_patch(100),
4418                },
4419            ])
4420            .expect("two holdings should merge atomically");
4421        assert_eq!(
4422            merge.rows,
4423            vec![expected_dynamic_row(2, 30), expected_dynamic_row(1, 100),],
4424            "delete before-images and update after-images must retain input order",
4425        );
4426
4427        let resplit = session
4428            .execute_trusted_dynamic_mutation_batch(vec![
4429                DynamicMutation::Update {
4430                    entity: ENTITY_NAME.to_string(),
4431                    key: InputValue::Nat64(1),
4432                    patch: dynamic_payload_patch(60),
4433                },
4434                DynamicMutation::Insert {
4435                    entity: ENTITY_NAME.to_string(),
4436                    patch: dynamic_payload_patch(40),
4437                },
4438            ])
4439            .expect("the merged holding should split again");
4440        assert_eq!(
4441            resplit.rows,
4442            vec![expected_dynamic_row(1, 60), expected_dynamic_row(3, 40),],
4443        );
4444
4445        let rejected_merge = session
4446            .execute_trusted_dynamic_mutation_batch(vec![
4447                DynamicMutation::Delete {
4448                    entity: ENTITY_NAME.to_string(),
4449                    key: InputValue::Nat64(3),
4450                },
4451                DynamicMutation::Update {
4452                    entity: ENTITY_NAME.to_string(),
4453                    key: InputValue::Nat64(99),
4454                    patch: dynamic_payload_patch(100),
4455                },
4456            ])
4457            .expect_err("a late missing merge target must preserve the earlier staged delete");
4458        assert_eq!(rejected_merge.class(), ErrorClass::NotFound);
4459        assert_dynamic_payload(&session, 1, 60);
4460        assert_dynamic_payload(&session, 3, 40);
4461
4462        SCHEMA_STORE.with(|store| {
4463            let cursor = store
4464                .borrow()
4465                .identity_statement_cursor(
4466                    database_incarnation_id().expect("database incarnation should remain readable"),
4467                    ENTITY_TAG,
4468                    FieldId::new(1),
4469                    &AcceptedFieldKind::Nat64,
4470                )
4471                .expect("mixed Identity state should remain readable");
4472            assert_eq!(cursor.expected_high_water(), 3);
4473            assert!(!cursor.has_allocations());
4474        });
4475    }
4476
4477    #[test]
4478    fn mixed_structural_batch_rejects_duplicate_holding_targets_without_mutation() {
4479        let session = initialize();
4480        session
4481            .execute_trusted_dynamic_insert_batch(ENTITY_NAME, vec![dynamic_payload_patch(100)])
4482            .expect("the holding fixture should initialize");
4483
4484        let duplicate = session
4485            .execute_trusted_dynamic_mutation_batch(vec![
4486                DynamicMutation::Update {
4487                    entity: ENTITY_NAME.to_string(),
4488                    key: InputValue::Nat64(1),
4489                    patch: dynamic_payload_patch(60),
4490                },
4491                DynamicMutation::Delete {
4492                    entity: ENTITY_NAME.to_string(),
4493                    key: InputValue::Nat64(1),
4494                },
4495            ])
4496            .expect_err("duplicate targets across operation kinds must reject");
4497        assert!(matches!(
4498            duplicate.diagnostic().detail(),
4499            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4500                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchDuplicateKey,
4501            }),
4502        ));
4503        assert_eq!(
4504            duplicate.diagnostic_facts(),
4505            vec![
4506                (
4507                    icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
4508                    ENTITY_TAG.value(),
4509                ),
4510                (
4511                    icydb_diagnostic_code::DiagnosticFactTag::FirstBatchPosition,
4512                    0,
4513                ),
4514                (
4515                    icydb_diagnostic_code::DiagnosticFactTag::DuplicateBatchPosition,
4516                    1,
4517                ),
4518            ],
4519        );
4520        assert_dynamic_payload(&session, 1, 100);
4521    }
4522
4523    #[test]
4524    fn mixed_structural_batch_rejects_empty_and_over_bound_before_resolution() {
4525        let session = initialize();
4526        let empty = session
4527            .execute_trusted_dynamic_mutation_batch(Vec::new())
4528            .expect_err("an empty public batch must reject");
4529        assert!(matches!(
4530            empty.diagnostic().detail(),
4531            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4532                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchEmpty,
4533            }),
4534        ));
4535        assert_eq!(
4536            empty.diagnostic_facts(),
4537            vec![(icydb_diagnostic_code::DiagnosticFactTag::ActualCount, 0,)],
4538        );
4539
4540        let requests = (0..=MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS)
4541            .map(|_| DynamicMutation::Delete {
4542                entity: ENTITY_NAME.to_string(),
4543                key: InputValue::Nat64(1),
4544            })
4545            .collect();
4546        let over_bound = session
4547            .execute_trusted_dynamic_mutation_batch(requests)
4548            .expect_err("operation cap plus one must reject before row resolution");
4549        assert!(matches!(
4550            over_bound.diagnostic().detail(),
4551            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4552                boundary: icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchTooManyItems,
4553            }),
4554        ));
4555        assert_eq!(
4556            over_bound.diagnostic_facts(),
4557            vec![
4558                (
4559                    icydb_diagnostic_code::DiagnosticFactTag::ActualCount,
4560                    (MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS + 1) as u64,
4561                ),
4562                (
4563                    icydb_diagnostic_code::DiagnosticFactTag::Limit,
4564                    MAX_STRUCTURAL_MUTATION_BATCH_OPERATIONS as u64,
4565                ),
4566            ],
4567        );
4568    }
4569
4570    #[test]
4571    fn mixed_structural_batch_staged_byte_bound_uses_checked_exact_boundary() {
4572        let mut exact = 0;
4573        add_structural_mutation_staged_bytes(
4574            &mut exact,
4575            [MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES],
4576        )
4577        .expect("the exact staged-byte boundary should admit");
4578        assert_eq!(exact, MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES);
4579
4580        let error = add_structural_mutation_staged_bytes(&mut exact, [1])
4581            .expect_err("one byte above the staged-byte boundary must reject");
4582        assert!(matches!(
4583            error.diagnostic().detail(),
4584            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4585                boundary:
4586                    icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchStagedBytesExceeded,
4587            }),
4588        ));
4589        assert_eq!(
4590            error.diagnostic_facts(),
4591            vec![
4592                (
4593                    icydb_diagnostic_code::DiagnosticFactTag::ActualLength,
4594                    (MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES + 1) as u64,
4595                ),
4596                (
4597                    icydb_diagnostic_code::DiagnosticFactTag::Limit,
4598                    MAX_STRUCTURAL_MUTATION_BATCH_STAGED_BYTES as u64,
4599                ),
4600            ],
4601        );
4602
4603        validate_structural_mutation_result_bytes(MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES)
4604            .expect("the exact result-byte boundary should admit");
4605        let error = validate_structural_mutation_result_bytes(
4606            MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES + 1,
4607        )
4608        .expect_err("one byte above the result-byte boundary must reject");
4609        assert!(matches!(
4610            error.diagnostic().detail(),
4611            Some(icydb_diagnostic_code::DiagnosticDetail::RuntimeBoundary {
4612                boundary:
4613                    icydb_diagnostic_code::RuntimeBoundaryCode::MutationBatchResultBytesExceeded,
4614            }),
4615        ));
4616        assert_eq!(
4617            error.diagnostic_facts(),
4618            vec![
4619                (
4620                    icydb_diagnostic_code::DiagnosticFactTag::ActualLength,
4621                    (MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES + 1) as u64,
4622                ),
4623                (
4624                    icydb_diagnostic_code::DiagnosticFactTag::Limit,
4625                    MAX_STRUCTURAL_MUTATION_BATCH_RESULT_BYTES as u64,
4626                ),
4627            ],
4628        );
4629    }
4630
4631    #[expect(
4632        clippy::too_many_lines,
4633        reason = "one lifecycle proves shared materialization and every maintained frontend against the same zero-state owner"
4634    )]
4635    #[test]
4636    fn identity_insert_frontends_share_one_committed_range_without_rejected_consumption() {
4637        let session = initialize();
4638        let catalog = session
4639            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
4640            .expect("identity catalog should resolve");
4641        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
4642            .expect("identity row layout should build");
4643        let initial_description = session
4644            .try_describe_entity_by_name(ENTITY_NAME)
4645            .expect("accepted Identity description should resolve");
4646        assert_eq!(
4647            initial_description.entity_tag(),
4648            catalog.identity().entity_tag().value()
4649        );
4650        assert_eq!(
4651            initial_description.accepted_schema_fingerprint_method(),
4652            catalog.fingerprint_method_version()
4653        );
4654        assert_eq!(
4655            initial_description.accepted_schema_fingerprint(),
4656            catalog.fingerprint()
4657        );
4658        let initial_identity = initial_description
4659            .identity()
4660            .expect("accepted Identity policy should be described");
4661        assert_eq!(initial_identity.field(), "id");
4662        assert_eq!(initial_identity.generator(), "Identity::next");
4663        assert_eq!(initial_identity.accepted_kind(), "nat64");
4664        assert_eq!(initial_identity.minimum(), 1);
4665        assert_eq!(initial_identity.maximum(), u128::from(u64::MAX));
4666        assert_eq!(initial_identity.high_water(), 0);
4667        assert_eq!(initial_identity.remaining(), u128::from(u64::MAX));
4668        assert!(!initial_identity.exhausted());
4669
4670        let rejected = session
4671            .execute_accepted_structural_save_batch(
4672                &catalog,
4673                &descriptor,
4674                batch(&[1_000, 2_000]),
4675                Timestamp::from_millis(6),
4676                |_| Err::<(), _>(InternalError::executor_unsupported()),
4677            )
4678            .expect_err("a rejected precommit result must not publish its tentative range");
4679        assert_eq!(rejected.class(), ErrorClass::Unsupported);
4680        assert_eq!(DATA_STORE.with(|store| store.borrow().len()), 0);
4681
4682        let rows = session
4683            .execute_accepted_structural_save_batch(
4684                &catalog,
4685                &descriptor,
4686                batch(&[10, 20, 30]),
4687                Timestamp::from_millis(7),
4688                Ok,
4689            )
4690            .expect("one accepted batch should commit rows and one identity range");
4691        assert_eq!(
4692            rows.into_iter().map(|row| row.values).collect::<Vec<_>>(),
4693            vec![
4694                vec![Value::Nat64(1), Value::Nat64(10)],
4695                vec![Value::Nat64(2), Value::Nat64(20)],
4696                vec![Value::Nat64(3), Value::Nat64(30)],
4697            ],
4698        );
4699
4700        let dynamic = session
4701            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
4702                entity: ENTITY_NAME.to_string(),
4703                patch: DynamicStructuralPatch::new(vec![(
4704                    "payload".to_string(),
4705                    DynamicWriteCell::Value(InputValue::Nat64(40)),
4706                )]),
4707            })
4708            .expect("dynamic omission should commit through shared Identity generation");
4709        assert_eq!(dynamic.affected_rows, 1);
4710
4711        for (request, operation) in [
4712            (
4713                DynamicMutation::Insert {
4714                    entity: ENTITY_NAME.to_string(),
4715                    patch: DynamicStructuralPatch::new(vec![
4716                        (
4717                            "id".to_string(),
4718                            DynamicWriteCell::Value(InputValue::Nat64(41)),
4719                        ),
4720                        (
4721                            "payload".to_string(),
4722                            DynamicWriteCell::Value(InputValue::Nat64(42)),
4723                        ),
4724                    ]),
4725                },
4726                icydb_diagnostic_code::DiagnosticMutationOperation::Insert,
4727            ),
4728            (
4729                DynamicMutation::Update {
4730                    entity: ENTITY_NAME.to_string(),
4731                    key: InputValue::Nat64(1),
4732                    patch: DynamicStructuralPatch::new(vec![(
4733                        "id".to_string(),
4734                        DynamicWriteCell::Default,
4735                    )]),
4736                },
4737                icydb_diagnostic_code::DiagnosticMutationOperation::Update,
4738            ),
4739        ] {
4740            let error = session
4741                .execute_trusted_dynamic_mutation(&request)
4742                .expect_err("structural Identity authorship and regeneration must reject");
4743            assert_eq!(error.class(), ErrorClass::Unsupported);
4744            assert_eq!(error.origin(), ErrorOrigin::Executor);
4745            assert_eq!(
4746                error.diagnostic_facts(),
4747                vec![
4748                    (
4749                        icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
4750                        ENTITY_TAG.value(),
4751                    ),
4752                    (icydb_diagnostic_code::DiagnosticFactTag::FieldId, 1),
4753                    (
4754                        icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
4755                        operation.raw(),
4756                    ),
4757                    (icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 0,),
4758                ],
4759            );
4760        }
4761
4762        let binding = session
4763            .issue_typed_entity_binding(
4764                ENTITY_SOURCE,
4765                &[
4766                    DynamicTypedFieldBindingRequest::new(
4767                        ID_SOURCE.to_string(),
4768                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
4769                        false,
4770                    ),
4771                    DynamicTypedFieldBindingRequest::new(
4772                        PAYLOAD_SOURCE.to_string(),
4773                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
4774                        false,
4775                    ),
4776                ],
4777            )
4778            .expect("typed output should bind the Identity field");
4779        let typed_patch = binding
4780            .bind_write_fields(vec![(
4781                PAYLOAD_SOURCE.to_string(),
4782                DynamicWriteCell::Value(InputValue::Nat64(50)),
4783            )])
4784            .expect("typed payload should lower");
4785        let typed = session
4786            .execute_trusted_typed_mutation(
4787                &binding,
4788                &DynamicTypedMutation::Insert { patch: typed_patch },
4789            )
4790            .expect("typed omission should commit through shared Identity generation");
4791        assert_eq!(
4792            typed
4793                .expect("typed insert should return one mutation result")
4794                .affected_rows,
4795            1,
4796        );
4797        let explicit_typed_patch = binding
4798            .bind_write_fields(vec![
4799                (
4800                    ID_SOURCE.to_string(),
4801                    DynamicWriteCell::Value(InputValue::Nat64(51)),
4802                ),
4803                (
4804                    PAYLOAD_SOURCE.to_string(),
4805                    DynamicWriteCell::Value(InputValue::Nat64(52)),
4806                ),
4807            ])
4808            .expect("the low-level binding should retain exact authored intent");
4809        let explicit_typed_error = session
4810            .execute_trusted_typed_mutation(
4811                &binding,
4812                &DynamicTypedMutation::Insert {
4813                    patch: explicit_typed_patch,
4814                },
4815            )
4816            .expect_err("typed Identity authorship must reject before allocation");
4817        assert_eq!(explicit_typed_error.class(), ErrorClass::Unsupported);
4818        assert_eq!(explicit_typed_error.origin(), ErrorOrigin::Executor);
4819        assert_eq!(
4820            explicit_typed_error.diagnostic_facts(),
4821            vec![
4822                (
4823                    icydb_diagnostic_code::DiagnosticFactTag::EntityTag,
4824                    ENTITY_TAG.value(),
4825                ),
4826                (icydb_diagnostic_code::DiagnosticFactTag::FieldId, 1),
4827                (
4828                    icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
4829                    icydb_diagnostic_code::DiagnosticMutationOperation::Insert.raw(),
4830                ),
4831                (icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 0,),
4832            ],
4833        );
4834
4835        let replace_error = session
4836            .execute_trusted_dynamic_mutation(&DynamicMutation::Replace {
4837                entity: ENTITY_NAME.to_string(),
4838                key: InputValue::Nat64(99),
4839                patch: DynamicStructuralPatch::new(vec![(
4840                    "payload".to_string(),
4841                    DynamicWriteCell::Value(InputValue::Nat64(60)),
4842                )]),
4843            })
4844            .expect_err("save-as-insert with a chosen Identity must reject");
4845        assert_eq!(replace_error.class(), ErrorClass::Unsupported);
4846        assert_eq!(replace_error.origin(), ErrorOrigin::Executor);
4847
4848        #[cfg(feature = "sql")]
4849        {
4850            for sql in [
4851                "INSERT INTO IdentityRow (payload) VALUES (70) RETURNING id, payload",
4852                "INSERT INTO IdentityRow (id, payload) VALUES (DEFAULT, 80) RETURNING id",
4853            ] {
4854                let _result = session
4855                    .execute_trusted_sql_mutation(sql)
4856                    .expect("SQL omission and DEFAULT should commit Identity generation");
4857            }
4858
4859            let error = session
4860                .execute_trusted_sql_mutation(
4861                    "INSERT INTO IdentityRow (id, payload) VALUES (42, 90)",
4862                )
4863                .expect_err("an explicit SQL Identity value must reject before allocation");
4864            let diagnostic = error.diagnostic();
4865            assert_eq!(
4866                diagnostic.code(),
4867                icydb_diagnostic_code::DiagnosticCode::QuerySqlWriteBoundary,
4868            );
4869            assert!(matches!(
4870                diagnostic.detail(),
4871                Some(icydb_diagnostic_code::DiagnosticDetail::SqlWriteBoundary {
4872                    boundary: icydb_diagnostic_code::SqlWriteBoundaryCode::ExplicitGeneratedField,
4873                }),
4874            ));
4875        }
4876
4877        let expected_committed = if cfg!(feature = "sql") { 7 } else { 5 };
4878        assert_eq!(
4879            DATA_STORE.with(|store| store.borrow().len()),
4880            expected_committed
4881        );
4882        SCHEMA_STORE.with(|store| {
4883            let cursor = store
4884                .borrow()
4885                .identity_statement_cursor(
4886                    database_incarnation_id().expect("database incarnation should remain readable"),
4887                    ENTITY_TAG,
4888                    FieldId::new(1),
4889                    &AcceptedFieldKind::Nat64,
4890                )
4891                .expect("committed writes must leave active state readable");
4892            assert_eq!(cursor.expected_high_water(), u128::from(expected_committed),);
4893            assert!(!cursor.has_allocations());
4894        });
4895        let committed_description = session
4896            .try_describe_entity_by_name(ENTITY_NAME)
4897            .expect("committed Identity description should resolve");
4898        let committed_identity = committed_description
4899            .identity()
4900            .expect("accepted Identity policy should remain described");
4901        assert_eq!(
4902            committed_identity.high_water(),
4903            u128::from(expected_committed),
4904        );
4905        assert_eq!(
4906            committed_identity.remaining(),
4907            u128::from(u64::MAX - expected_committed),
4908        );
4909        assert!(!committed_identity.exhausted());
4910    }
4911
4912    #[test]
4913    #[expect(
4914        clippy::too_many_lines,
4915        reason = "one ordered scenario proves target/progress atomicity, every interruption wake-up, state-only admission, and successful no-op wake-up behavior"
4916    )]
4917    fn mutation_progress_and_target_rows_recover_as_one_marker_transition() {
4918        let session = initialize_journaled();
4919        install_startup_recovery_wakeup(record_startup_wakeup);
4920        let catalog = session
4921            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
4922            .expect("journaled atomic-progress catalog should resolve");
4923        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
4924            .expect("journaled atomic-progress row layout should build");
4925
4926        for (ordinal, interruption) in [
4927            MutationCommitInterruption::MarkerPersisted,
4928            MutationCommitInterruption::JournalPublished,
4929            MutationCommitInterruption::RowsPublished,
4930            MutationCommitInterruption::ProgressReplaced,
4931        ]
4932        .into_iter()
4933        .enumerate()
4934        {
4935            let identity_byte = 31 + u8::try_from(ordinal).expect("small ordinal should fit");
4936            let (before, after, operation) = atomic_progress_fixture(identity_byte);
4937            with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
4938                match store.insert_mutation(&before)? {
4939                    InsertMutationJobResult::Inserted => Ok(()),
4940                    InsertMutationJobResult::Occupied(_) => {
4941                        Err(crate::db::MutationJobError::IdentityConflict)
4942                    }
4943                }
4944            })
4945            .expect("atomic predecessor should insert once");
4946
4947            let wakeups_before = STARTUP_WAKEUPS.with(Cell::get);
4948            interrupt_next_mutation_commit_for_tests(interruption);
4949            let interrupted = session.execute_accepted_structural_update_with_mutation_progress(
4950                &catalog,
4951                &descriptor,
4952                batch(&[700 + u64::try_from(ordinal).expect("small ordinal should fit")]),
4953                Timestamp::from_millis(17),
4954                operation,
4955            );
4956            assert!(
4957                interrupted.is_err(),
4958                "selected atomic boundary should interrupt"
4959            );
4960            assert_eq!(
4961                STARTUP_WAKEUPS.with(Cell::get),
4962                wakeups_before.saturating_add(1),
4963                "a normally returned retained-marker error must register its wake-up",
4964            );
4965
4966            forget_recovered_domain_for_tests(&session.db)
4967                .expect("interruption should reset volatile recovery ownership");
4968            let retained_before =
4969                with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
4970                    store.load_mutation(before.state().job_id)
4971                })
4972                .expect("pre-driver progress should load");
4973            let row_count_before = JOURNALED_DATA_STORE.with(|store| store.borrow().len());
4974            let pending = session
4975                .db
4976                .ensure_recovered_state()
4977                .expect_err("ordinary admission must not drive retained-marker recovery");
4978            assert_eq!(
4979                pending.diagnostic().error_code(),
4980                icydb_diagnostic_code::ErrorCode::RUNTIME_BOUNDARY_DATABASE_STARTUP_RECOVERY_PENDING,
4981            );
4982            assert_eq!(
4983                with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
4984                    store.load_mutation(before.state().job_id)
4985                })
4986                .expect("post-admission progress should load"),
4987                retained_before,
4988            );
4989            assert_eq!(
4990                JOURNALED_DATA_STORE.with(|store| store.borrow().len()),
4991                row_count_before,
4992                "state-only admission must not mutate target rows",
4993            );
4994            assert!(
4995                session
4996                    .db
4997                    .drive_startup_recovery_page()
4998                    .expect("dedicated driver should finish target and progress together"),
4999            );
5000            let retained = with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
5001                store.load_mutation(before.state().job_id)
5002            })
5003            .expect("recovered successor should load");
5004            assert_eq!(retained, after);
5005            assert_eq!(
5006                JOURNALED_DATA_STORE.with(|store| store.borrow().len()),
5007                u64::try_from(ordinal + 1).expect("small row count should fit"),
5008            );
5009        }
5010
5011        let (before, after, operation) = atomic_progress_fixture(39);
5012        with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
5013            match store.insert_mutation(&before)? {
5014                InsertMutationJobResult::Inserted => Ok(()),
5015                InsertMutationJobResult::Occupied(_) => {
5016                    Err(crate::db::MutationJobError::IdentityConflict)
5017                }
5018            }
5019        })
5020        .expect("final predecessor should insert once");
5021        let wakeups_before_success = STARTUP_WAKEUPS.with(Cell::get);
5022        session
5023            .execute_accepted_structural_update_with_mutation_progress(
5024                &catalog,
5025                &descriptor,
5026                batch(&[799]),
5027                Timestamp::from_millis(18),
5028                operation,
5029            )
5030            .expect("uninterrupted atomic transition should clear its marker");
5031        assert_eq!(
5032            STARTUP_WAKEUPS.with(Cell::get),
5033            wakeups_before_success.saturating_add(1),
5034            "a successful retained commit must request online convergence",
5035        );
5036        let retained = with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
5037            store.load_mutation(before.state().job_id)
5038        })
5039        .expect("final successor should load");
5040        assert_eq!(retained, after);
5041        forget_recovered_domain_for_tests(&session.db)
5042            .expect("post-clear recovery ownership should reset");
5043        let pending = session
5044            .db
5045            .ensure_recovered_state()
5046            .expect_err("an upgrade epoch must remain gated until its driver runs");
5047        assert_eq!(
5048            pending.diagnostic().error_code(),
5049            icydb_diagnostic_code::ErrorCode::RUNTIME_BOUNDARY_DATABASE_STARTUP_RECOVERY_PENDING,
5050        );
5051        assert!(
5052            session
5053                .db
5054                .drive_startup_recovery_page()
5055                .expect("post-clear driver recovery should fold the retained batch"),
5056        );
5057    }
5058
5059    #[test]
5060    fn mutation_progress_neither_side_mismatch_blocks_recovery() {
5061        let session = initialize_journaled();
5062        let catalog = session
5063            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
5064            .expect("journaled corruption catalog should resolve");
5065        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
5066            .expect("journaled corruption row layout should build");
5067        let (before, _after, operation) = atomic_progress_fixture(41);
5068        with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
5069            match store.insert_mutation(&before)? {
5070                InsertMutationJobResult::Inserted => Ok(()),
5071                InsertMutationJobResult::Occupied(_) => {
5072                    Err(crate::db::MutationJobError::IdentityConflict)
5073                }
5074            }
5075        })
5076        .expect("corruption predecessor should insert once");
5077
5078        interrupt_next_mutation_commit_for_tests(MutationCommitInterruption::MarkerPersisted);
5079        assert!(
5080            session
5081                .execute_accepted_structural_update_with_mutation_progress(
5082                    &catalog,
5083                    &descriptor,
5084                    batch(&[811]),
5085                    Timestamp::from_millis(19),
5086                    operation,
5087                )
5088                .is_err(),
5089            "marker interruption should retain recovery authority",
5090        );
5091        let (unexpected, _) = before
5092            .apply_transition(
5093                &MutationJobAdvanceRequest::new(
5094                    before.state().job_id,
5095                    0,
5096                    MutationJobIdempotencyKey::new("unexpected-third-state")
5097                        .expect("unexpected replay key should admit"),
5098                ),
5099                MutationJobTransition::new(
5100                    MutationJobStatus::Active,
5101                    MutationJobPhase::Forward,
5102                    vec![99],
5103                    2,
5104                    0,
5105                    0,
5106                ),
5107            )
5108            .expect("unexpected but valid progress state should admit");
5109        with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
5110            store.replace_mutation(&unexpected)
5111        })
5112        .expect("test should install the neither-side state");
5113
5114        forget_recovered_domain_for_tests(&session.db)
5115            .expect("corrupt recovery ownership should reset");
5116        let error = session
5117            .db
5118            .drive_startup_recovery_page()
5119            .expect_err("neither-side progress must block recovery");
5120        assert_eq!(error.class(), ErrorClass::Corruption);
5121        assert_eq!(error.origin(), ErrorOrigin::Recovery);
5122        assert_eq!(
5123            with_mutation_progress_store::<JournaledTestCanister, _>(|store| {
5124                store.load_mutation(before.state().job_id)
5125            })
5126            .expect("unexpected state should remain inspectable to the test"),
5127            unexpected,
5128        );
5129        assert!(
5130            session.db.drive_startup_recovery_page().is_err(),
5131            "a retained corrupt marker must continue blocking database access",
5132        );
5133    }
5134
5135    #[test]
5136    #[expect(
5137        clippy::too_many_lines,
5138        reason = "one ordered scenario exercises every durable interruption boundary, guarded recovery, derived rebuild, and both integrity tiers"
5139    )]
5140    fn journaled_identity_recovery_quiesces_every_publication_interruption_before_reallocation() {
5141        let session = initialize_journaled();
5142        let catalog = session
5143            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
5144            .expect("journaled identity catalog should resolve");
5145        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
5146            .expect("journaled identity row layout should build");
5147
5148        for (ordinal, interruption) in [
5149            MutationCommitInterruption::MarkerPersisted,
5150            MutationCommitInterruption::JournalPublished,
5151            MutationCommitInterruption::RowsPublished,
5152            MutationCommitInterruption::StateMaterialized,
5153        ]
5154        .into_iter()
5155        .enumerate()
5156        {
5157            interrupt_next_mutation_commit_for_tests(interruption);
5158            let interrupted = session.execute_accepted_structural_save_batch(
5159                &catalog,
5160                &descriptor,
5161                batch(&[u64::try_from(ordinal).expect("ordinal should fit")]),
5162                Timestamp::from_millis(8),
5163                Ok,
5164            );
5165            assert!(
5166                interrupted.is_err(),
5167                "the selected durable boundary should interrupt",
5168            );
5169
5170            let Err(pending) = session.execute_accepted_structural_save_batch(
5171                &catalog,
5172                &descriptor,
5173                batch(&[100 + u64::try_from(ordinal).expect("ordinal should fit")]),
5174                Timestamp::from_millis(9),
5175                Ok,
5176            ) else {
5177                panic!("ordinary mutation must not drive retained-marker recovery");
5178            };
5179            assert_eq!(
5180                pending.diagnostic().error_code(),
5181                icydb_diagnostic_code::ErrorCode::RUNTIME_BOUNDARY_DATABASE_STARTUP_RECOVERY_PENDING,
5182            );
5183            drive_journaled_recovery_to_completion(&session);
5184
5185            let committed = session
5186                .execute_accepted_structural_save_batch(
5187                    &catalog,
5188                    &descriptor,
5189                    batch(&[100 + u64::try_from(ordinal).expect("ordinal should fit")]),
5190                    Timestamp::from_millis(9),
5191                    Ok,
5192                )
5193                .expect("the next mutation must recover before allocating");
5194            let expected_high_water =
5195                u64::try_from((ordinal + 1) * 2).expect("small test high-water should fit");
5196            assert_eq!(
5197                committed
5198                    .into_iter()
5199                    .map(|row| row.values)
5200                    .collect::<Vec<_>>(),
5201                vec![vec![
5202                    Value::Nat64(expected_high_water),
5203                    Value::Nat64(100 + u64::try_from(ordinal).expect("ordinal should fit")),
5204                ]],
5205            );
5206            assert_eq!(
5207                JOURNALED_DATA_STORE.with(|store| store.borrow().len()),
5208                expected_high_water,
5209            );
5210            JOURNALED_SCHEMA_STORE.with(|store| {
5211                let cursor = store
5212                    .borrow()
5213                    .identity_statement_cursor(
5214                        database_incarnation_id()
5215                            .expect("database incarnation should remain readable"),
5216                        ENTITY_TAG,
5217                        FieldId::new(1),
5218                        &AcceptedFieldKind::Nat64,
5219                    )
5220                    .expect("guarded recovery must leave quiescent active state");
5221                assert_eq!(
5222                    cursor.expected_high_water(),
5223                    u128::from(expected_high_water),
5224                );
5225                assert!(!cursor.has_allocations());
5226            });
5227        }
5228
5229        for (ordinal, (interruption, deleted_key)) in [
5230            (MutationCommitInterruption::MarkerPersisted, 2),
5231            (MutationCommitInterruption::JournalPublished, 4),
5232            (MutationCommitInterruption::RowPrefixPublished, 6),
5233            (MutationCommitInterruption::RowsPublished, 8),
5234            (MutationCommitInterruption::StateMaterialized, 7),
5235        ]
5236        .into_iter()
5237        .enumerate()
5238        {
5239            let expected_payload =
5240                501 + u64::try_from(ordinal).expect("small interruption ordinal should fit");
5241            interrupt_next_mutation_commit_for_tests(interruption);
5242            let interrupted = session.execute_trusted_dynamic_mutation_batch(vec![
5243                DynamicMutation::Update {
5244                    entity: ENTITY_NAME.to_string(),
5245                    key: InputValue::Nat64(1),
5246                    patch: dynamic_payload_patch(expected_payload),
5247                },
5248                DynamicMutation::Delete {
5249                    entity: ENTITY_NAME.to_string(),
5250                    key: InputValue::Nat64(deleted_key),
5251                },
5252            ]);
5253            assert!(
5254                interrupted.is_err(),
5255                "the selected caller-key mixed publication boundary should interrupt",
5256            );
5257            let pending = session
5258                .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
5259                    entity: ENTITY_NAME.to_string(),
5260                    key: InputValue::Nat64(1),
5261                    patch: dynamic_payload_patch(expected_payload),
5262                })
5263                .expect_err("ordinary update must not drive retained-marker recovery");
5264            assert_eq!(
5265                pending.diagnostic().error_code(),
5266                icydb_diagnostic_code::ErrorCode::RUNTIME_BOUNDARY_DATABASE_STARTUP_RECOVERY_PENDING,
5267            );
5268            drive_journaled_recovery_to_completion(&session);
5269            let recovered_update = session
5270                .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
5271                    entity: ENTITY_NAME.to_string(),
5272                    key: InputValue::Nat64(1),
5273                    patch: dynamic_payload_patch(expected_payload),
5274                })
5275                .expect("guarded reentry should complete the marker-authorized mixed batch");
5276            assert_eq!(
5277                recovered_update.affected_rows, 0,
5278                "the recovered update must already expose its admitted final image",
5279            );
5280            let recovered_delete = session
5281                .execute_trusted_dynamic_mutation(&DynamicMutation::Delete {
5282                    entity: ENTITY_NAME.to_string(),
5283                    key: InputValue::Nat64(deleted_key),
5284                })
5285                .expect_err("the recovered delete must already be materialized");
5286            assert_eq!(recovered_delete.class(), ErrorClass::NotFound);
5287            JOURNALED_SCHEMA_STORE.with(|store| {
5288                let cursor = store
5289                    .borrow()
5290                    .identity_statement_cursor(
5291                        database_incarnation_id()
5292                            .expect("database incarnation should remain readable"),
5293                        ENTITY_TAG,
5294                        FieldId::new(1),
5295                        &AcceptedFieldKind::Nat64,
5296                    )
5297                    .expect("caller-key recovery must preserve active Identity state");
5298                assert_eq!(cursor.expected_high_water(), 8);
5299                assert!(!cursor.has_allocations());
5300            });
5301        }
5302
5303        forget_recovered_domain_for_tests(&session.db)
5304            .expect("the final journal tail should remain recoverable");
5305        session
5306            .db
5307            .drive_startup_recovery_page()
5308            .expect("derived rebuild must not allocate another identity");
5309
5310        let data_generation = JOURNALED_DATA_STORE.with(|store| store.borrow().generation());
5311        let index_generation = JOURNALED_INDEX_STORE.with(|store| store.borrow().generation());
5312        forget_recovered_domain_for_tests(&session.db)
5313            .expect("an empty-tail upgrade should reset recovery ownership");
5314        session
5315            .db
5316            .drive_startup_recovery_page()
5317            .expect("an empty-tail upgrade should admit without rebuilding stored rows or indexes");
5318        assert_eq!(
5319            JOURNALED_DATA_STORE.with(|store| store.borrow().generation()),
5320            data_generation,
5321            "empty-tail recovery must not traverse or rewrite authoritative rows",
5322        );
5323        assert_eq!(
5324            JOURNALED_INDEX_STORE.with(|store| store.borrow().generation()),
5325            index_generation,
5326            "empty-tail recovery must not clear or rebuild secondary indexes",
5327        );
5328
5329        let quick = execute_quick_integrity(
5330            &session.db,
5331            catalog.inspection_plan(),
5332            catalog.runtime_root_identity().database_incarnation(),
5333        )
5334        .expect("quiescent Identity control inventory should be inspectable");
5335        assert_eq!(quick.status(), &QuickIntegrityStatus::CompleteClean);
5336        let row_page = execute_row_integrity_page(
5337            &session.db,
5338            catalog.inspection_plan(),
5339            PhysicalUnitCheckpoint::BeforeFirst,
5340            RowInspectionLimits::standard(),
5341        )
5342        .expect("Identity rows should remain within committed high-water");
5343        assert!(row_page.exhausted());
5344        assert!(row_page.findings().is_empty());
5345
5346        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 3);
5347        assert!(
5348            JOURNALED_INDEX_STORE.with(|store| !store.borrow().is_empty()),
5349            "derived index rebuild should restore witnesses without allocating identities",
5350        );
5351        assert!(!JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
5352        JOURNALED_SCHEMA_STORE.with(|store| {
5353            let cursor = store
5354                .borrow()
5355                .identity_statement_cursor(
5356                    database_incarnation_id().expect("database incarnation should remain readable"),
5357                    ENTITY_TAG,
5358                    FieldId::new(1),
5359                    &AcceptedFieldKind::Nat64,
5360                )
5361                .expect("folded identity state should reopen without allocating");
5362            assert_eq!(cursor.expected_high_water(), 8);
5363            assert!(!cursor.has_allocations());
5364        });
5365    }
5366
5367    #[test]
5368    fn journaled_online_convergence_drains_the_full_backlog_in_complete_batch_callbacks_without_reallocating_ids()
5369     {
5370        const SUBMISSION: &str = "generated/8899aabbccddeeff";
5371        let session = initialize_journaled();
5372        let catalog = session
5373            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
5374            .expect("journaled identity catalog should resolve");
5375        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
5376            .expect("journaled identity row layout should build");
5377
5378        for payload in 0_u64..38 {
5379            session
5380                .execute_accepted_structural_save_batch(
5381                    &catalog,
5382                    &descriptor,
5383                    batch(&[payload]),
5384                    Timestamp::from_millis(8),
5385                    Ok,
5386                )
5387                .unwrap_or_else(|error| {
5388                    panic!("journaled identity fixture row {payload} should commit: {error:?}")
5389                });
5390        }
5391
5392        let before = JOURNALED_TAIL_STORE.with(|tail| {
5393            tail.borrow()
5394                .current_tail_control()
5395                .expect("online backlog control should remain valid")
5396        });
5397        assert_eq!(before.batch_count(), 38);
5398        let next_sequence = crate::db::commit::next_database_commit_sequence()
5399            .expect("database sequence preview should remain readable");
5400        let Err(pressure) = session.execute_accepted_structural_save_batch(
5401            &catalog,
5402            &descriptor,
5403            batch(&[38]),
5404            Timestamp::from_millis(8),
5405            Ok,
5406        ) else {
5407            panic!("the exact cumulative batch ceiling should reject one more batch")
5408        };
5409        assert_exact_batch_backlog_pressure(&pressure, before, next_sequence);
5410
5411        for folded_batches in 1..=38 {
5412            let complete = session
5413                .db
5414                .drive_startup_recovery_page()
5415                .expect("online complete-batch callback should commit");
5416            assert_eq!(complete, folded_batches == 38);
5417        }
5418
5419        assert!(!JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
5420        session
5421            .execute_accepted_structural_save_batch(
5422                &catalog,
5423                &descriptor,
5424                batch(&[38]),
5425                Timestamp::from_millis(8),
5426                Ok,
5427            )
5428            .expect("drain should make the rejected mutation retryable");
5429        assert!(
5430            session
5431                .db
5432                .drive_startup_recovery_page()
5433                .expect("the retry tail should converge"),
5434        );
5435
5436        assert_eq!(
5437            drive_generated_startup_recovery_page(&session, &JOURNALED_STORE_REGISTRY, SUBMISSION,)
5438                .expect("online convergence should commit"),
5439            GeneratedStartupDriverStep::Terminal,
5440            "the quiescent generated driver should stop",
5441        );
5442
5443        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 39);
5444        assert!(!JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
5445        assert_dynamic_payload(&session, 1, 0);
5446        assert_dynamic_payload(&session, 39, 38);
5447        JOURNALED_SCHEMA_STORE.with(|store| {
5448            let cursor = store
5449                .borrow()
5450                .identity_statement_cursor(
5451                    database_incarnation_id().expect("database incarnation should remain readable"),
5452                    ENTITY_TAG,
5453                    FieldId::new(1),
5454                    &AcceptedFieldKind::Nat64,
5455                )
5456                .expect("online convergence must preserve active Identity state");
5457            assert_eq!(cursor.expected_high_water(), 39);
5458            assert!(!cursor.has_allocations());
5459        });
5460    }
5461
5462    #[test]
5463    fn journaled_online_convergence_reconstructs_same_key_batches_from_canonical_predecessors() {
5464        let session = initialize_journaled();
5465        session
5466            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
5467                entity: ENTITY_NAME.to_string(),
5468                patch: dynamic_payload_patch(10),
5469            })
5470            .expect("the initial positioned row should commit");
5471        for payload in [20, 30] {
5472            session
5473                .execute_trusted_dynamic_mutation(&DynamicMutation::Update {
5474                    entity: ENTITY_NAME.to_string(),
5475                    key: InputValue::Nat64(1),
5476                    patch: dynamic_payload_patch(payload),
5477                })
5478                .unwrap_or_else(|error| {
5479                    panic!("the positioned same-key update should commit: {error:?}")
5480                });
5481        }
5482
5483        assert_dynamic_payload(&session, 1, 30);
5484        assert_eq!(
5485            JOURNALED_INDEX_STORE.with(|store| store.borrow().len()),
5486            1,
5487            "the newest live index effect should hide every predecessor",
5488        );
5489        for folded_batches in 1..=3 {
5490            let complete = session
5491                .db
5492                .drive_startup_recovery_page()
5493                .expect("the positioned same-key batch should converge");
5494            assert_eq!(complete, folded_batches == 3);
5495        }
5496
5497        assert_dynamic_payload(&session, 1, 30);
5498        assert_eq!(
5499            JOURNALED_INDEX_STORE.with(|store| store.borrow().len()),
5500            1,
5501            "canonical derived state must contain only the newest membership",
5502        );
5503        assert!(!JOURNALED_TAIL_STORE.with(|tail| tail.borrow().has_stored_batch()));
5504    }
5505
5506    #[test]
5507    fn journaled_convergence_uses_final_batch_rows_for_unique_release() {
5508        let session = initialize_journaled_with_unique_payload();
5509        let inserted = session
5510            .execute_trusted_dynamic_insert_batch(
5511                ENTITY_NAME,
5512                vec![dynamic_payload_patch(10), dynamic_payload_patch(20)],
5513            )
5514            .expect("the unique journal fixture should commit");
5515        assert_eq!(
5516            inserted.rows,
5517            vec![expected_dynamic_row(1, 10), expected_dynamic_row(2, 20)],
5518        );
5519        assert!(
5520            session
5521                .db
5522                .drive_startup_recovery_page()
5523                .expect("the unique fixture should become canonical"),
5524        );
5525
5526        let swapped = session
5527            .execute_trusted_dynamic_mutation_batch(vec![
5528                DynamicMutation::Update {
5529                    entity: ENTITY_NAME.to_string(),
5530                    key: InputValue::Nat64(1),
5531                    patch: dynamic_payload_patch(20),
5532                },
5533                DynamicMutation::Update {
5534                    entity: ENTITY_NAME.to_string(),
5535                    key: InputValue::Nat64(2),
5536                    patch: dynamic_payload_patch(10),
5537                },
5538            ])
5539            .expect("one journal batch should admit a final-row unique swap");
5540        assert_eq!(
5541            swapped.rows,
5542            vec![expected_dynamic_row(1, 20), expected_dynamic_row(2, 10)],
5543        );
5544        assert!(
5545            session
5546                .db
5547                .drive_startup_recovery_page()
5548                .expect("the unique swap should converge in one complete batch"),
5549        );
5550
5551        let released = session
5552            .execute_trusted_dynamic_mutation_batch(vec![
5553                DynamicMutation::Delete {
5554                    entity: ENTITY_NAME.to_string(),
5555                    key: InputValue::Nat64(1),
5556                },
5557                DynamicMutation::Insert {
5558                    entity: ENTITY_NAME.to_string(),
5559                    patch: dynamic_payload_patch(20),
5560                },
5561            ])
5562            .expect("a journaled delete should release its unique value to the final insert");
5563        assert_eq!(
5564            released.rows,
5565            vec![expected_dynamic_row(1, 20), expected_dynamic_row(3, 20)],
5566        );
5567        assert!(
5568            session
5569                .db
5570                .drive_startup_recovery_page()
5571                .expect("the delete and unique reuse should converge together"),
5572        );
5573
5574        assert_dynamic_payload(&session, 2, 10);
5575        assert_dynamic_payload(&session, 3, 20);
5576        assert_eq!(JOURNALED_INDEX_STORE.with(|store| store.borrow().len()), 2);
5577        assert!(
5578            session
5579                .execute_trusted_dynamic_insert_batch(ENTITY_NAME, vec![dynamic_payload_patch(20)],)
5580                .is_err(),
5581            "the converged unique index must remain authoritative",
5582        );
5583    }
5584
5585    #[test]
5586    fn journaled_startup_recovery_completes_one_large_batch_atomically() {
5587        let session = initialize_journaled();
5588        let catalog = session
5589            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
5590            .expect("journaled identity catalog should resolve");
5591        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
5592            .expect("journaled identity row layout should build");
5593        let payloads = (0_u64..129).collect::<Vec<_>>();
5594        session
5595            .execute_accepted_structural_save_batch(
5596                &catalog,
5597                &descriptor,
5598                batch(&payloads),
5599                Timestamp::from_millis(9),
5600                Ok,
5601            )
5602            .expect("one large journal batch should commit");
5603
5604        forget_recovered_domain_for_tests(&session.db)
5605            .expect("upgrade should reset recovery ownership");
5606        assert!(
5607            session
5608                .db
5609                .drive_startup_recovery_page()
5610                .expect("the complete batch recovery page should commit"),
5611        );
5612
5613        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 129);
5614        JOURNALED_TAIL_STORE.with(|tail| {
5615            let tail = tail.borrow();
5616            assert!(!tail.has_stored_batch());
5617        });
5618        assert_dynamic_payload(&session, 1, 0);
5619        assert_dynamic_payload(&session, 129, 128);
5620    }
5621
5622    #[test]
5623    fn complete_batch_validation_rejects_a_late_record_before_canonical_writes() {
5624        let session = initialize_journaled();
5625        let catalog = session
5626            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
5627            .expect("journaled identity catalog should resolve");
5628        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
5629            .expect("journaled identity row layout should build");
5630        session
5631            .execute_accepted_structural_save_batch(
5632                &catalog,
5633                &descriptor,
5634                batch(&[7]),
5635                Timestamp::from_millis(9),
5636                Ok,
5637            )
5638            .expect("journal batch predecessor should commit");
5639
5640        JOURNALED_TAIL_STORE.with(|tail| {
5641            let mut tail = tail.borrow_mut();
5642            let original = tail
5643                .next_batch_after(JournalSequence::new(0))
5644                .expect("journal batch should decode")
5645                .expect("journal batch should exist");
5646            let mut records = original.records().to_vec();
5647            records.push(
5648                JournalRecord::schema_put(JOURNALED_STORE_PATH, vec![0xff; 8])
5649                    .expect("bounded semantic corruption should build"),
5650            );
5651            let corrupted = JournalBatch::new_with_database_commit_sequence(
5652                original.batch_id(),
5653                original.commit_marker_id(),
5654                original.journal_sequence(),
5655                original.database_commit_sequence(),
5656                records,
5657            )
5658            .expect("current corrupt batch shape should build");
5659            let encoded = encode_journal_batch(&corrupted)
5660                .expect("current corrupt batch envelope should encode");
5661            tail.clear_batches_through(original.journal_sequence());
5662            tail.insert_raw_batch_for_tests(original.journal_sequence(), encoded)
5663                .expect("corrupt persisted batch should replace the predecessor");
5664        });
5665
5666        forget_recovered_domain_for_tests(&session.db)
5667            .expect("upgrade should reset recovery ownership");
5668        let error = session
5669            .db
5670            .drive_startup_recovery_page()
5671            .expect_err("late semantic corruption must fail before fold apply");
5672        assert_eq!(error.class(), ErrorClass::Corruption);
5673        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 0);
5674        JOURNALED_TAIL_STORE.with(|tail| {
5675            let tail = tail.borrow();
5676            assert_eq!(
5677                tail.fold_watermark()
5678                    .expect("watermark should remain readable")
5679                    .highest_folded_journal_sequence(),
5680                JournalSequence::new(0),
5681            );
5682            assert!(tail.has_stored_batch());
5683        });
5684    }
5685
5686    #[test]
5687    fn prepared_batch_row_evidence_rejects_a_late_malformed_row_before_canonical_writes() {
5688        let session = initialize_journaled();
5689        let catalog = session
5690            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
5691            .expect("journaled identity catalog should resolve");
5692        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
5693            .expect("journaled identity row layout should build");
5694        session
5695            .execute_accepted_structural_save_batch(
5696                &catalog,
5697                &descriptor,
5698                batch(&[7, 8]),
5699                Timestamp::from_millis(9),
5700                Ok,
5701            )
5702            .expect("two-row journal batch should commit");
5703
5704        JOURNALED_TAIL_STORE.with(|tail| {
5705            let mut tail = tail.borrow_mut();
5706            let original = tail
5707                .next_batch_after(JournalSequence::new(0))
5708                .expect("journal batch should decode")
5709                .expect("journal batch should exist");
5710            let mut records = original.records().to_vec();
5711            let mut row_ordinal = 0_u8;
5712            for record in &mut records {
5713                if let JournalRecord::RowPut { row_bytes, .. } = record {
5714                    row_ordinal = row_ordinal.saturating_add(1);
5715                    if row_ordinal == 2 {
5716                        *row_bytes = vec![0xff; 8];
5717                        break;
5718                    }
5719                }
5720            }
5721            assert_eq!(row_ordinal, 2, "the late row record should be present");
5722            let corrupted = JournalBatch::new_with_database_commit_sequence(
5723                original.batch_id(),
5724                original.commit_marker_id(),
5725                original.journal_sequence(),
5726                original.database_commit_sequence(),
5727                records,
5728            )
5729            .expect("current corrupt batch shape should build");
5730            let encoded = encode_journal_batch(&corrupted)
5731                .expect("current corrupt batch envelope should encode");
5732            tail.clear_batches_through(original.journal_sequence());
5733            tail.insert_raw_batch_for_tests(original.journal_sequence(), encoded)
5734                .expect("corrupt persisted batch should replace the predecessor");
5735        });
5736
5737        forget_recovered_domain_for_tests(&session.db)
5738            .expect("upgrade should reset recovery ownership");
5739        let error = session
5740            .db
5741            .drive_startup_recovery_page()
5742            .expect_err("late malformed row must fail during complete batch preparation");
5743        assert_eq!(error.class(), ErrorClass::Corruption);
5744        assert_eq!(JOURNALED_DATA_STORE.with(|store| store.borrow().len()), 0);
5745        JOURNALED_TAIL_STORE.with(|tail| {
5746            let tail = tail.borrow();
5747            assert_eq!(
5748                tail.fold_watermark()
5749                    .expect("watermark should remain readable")
5750                    .highest_folded_journal_sequence(),
5751                JournalSequence::new(0),
5752            );
5753            assert!(tail.has_stored_batch());
5754        });
5755    }
5756
5757    #[test]
5758    #[ignore = "release-closeout native timing probe for one marker-authorized driver recovery"]
5759    fn identity_recovery_closeout_reports_driver_time() {
5760        let session = initialize_journaled();
5761        let catalog = session
5762            .accepted_schema_catalog_context_for_entity_name(Some(ENTITY_NAME))
5763            .expect("journaled identity catalog should resolve");
5764        let descriptor = AcceptedRowLayoutRuntimeContract::from_accepted_schema(catalog.snapshot())
5765            .expect("journaled identity row layout should build");
5766
5767        interrupt_next_mutation_commit_for_tests(MutationCommitInterruption::RowsPublished);
5768        let interrupted = session.execute_accepted_structural_save_batch(
5769            &catalog,
5770            &descriptor,
5771            batch(&[1]),
5772            Timestamp::from_millis(10),
5773            Ok,
5774        );
5775        assert!(
5776            interrupted.is_err(),
5777            "the selected publication boundary should interrupt",
5778        );
5779
5780        let start = Instant::now();
5781        assert!(
5782            session
5783                .db
5784                .drive_startup_recovery_page()
5785                .expect("dedicated driver should recover before allocation"),
5786        );
5787        let committed = session
5788            .execute_accepted_structural_save_batch(
5789                &catalog,
5790                &descriptor,
5791                batch(&[2]),
5792                Timestamp::from_millis(11),
5793                Ok,
5794            )
5795            .expect("post-recovery allocation should commit");
5796        let elapsed = start.elapsed();
5797        assert_eq!(
5798            committed
5799                .into_iter()
5800                .map(|row| row.values)
5801                .collect::<Vec<_>>(),
5802            vec![vec![Value::Nat64(2), Value::Nat64(2)]],
5803        );
5804
5805        println!(
5806            "identity recovery closeout: driver_nanos={}",
5807            elapsed.as_nanos(),
5808        );
5809    }
5810}
5811
5812#[cfg(test)]
5813mod targeted_rule_mutation_tests {
5814    use super::{
5815        DbSession, DynamicMutation, DynamicStructuralPatch, DynamicTypedFieldBindingRequest,
5816        DynamicTypedFieldType, DynamicTypedMutation, DynamicWriteCell,
5817    };
5818    use crate::{
5819        db::{
5820            data::{DataStore, encode_input_value_for_candidate_field_contract},
5821            index::IndexStore,
5822            registry::{StoreAllocationIdentities, StoreRegistry, StoreRuntimeStorageCapabilities},
5823            schema::{
5824                AcceptedCheckLiteralV1, AcceptedCompositeCatalog, AcceptedFieldDecodeContract,
5825                AcceptedFieldKind, AcceptedNamedTypeIdentity, AcceptedRuleOperation,
5826                AcceptedRuleTarget, AcceptedSchemaRevision, AcceptedSourceBindingCatalog,
5827                ConstraintOrigin, FieldId, FieldStorageDecode, FieldWriteManagement, LeafCodec,
5828                PersistedFieldSnapshot, PersistedNestedLeafSnapshot, PersistedSchemaSnapshot,
5829                ScalarCodec, SchemaFieldSlot, SchemaFieldWritePolicy, SchemaInsertDefault,
5830                SchemaRowLayout, SchemaStore, SchemaVersion,
5831                accepted_schema_candidate_with_catalogs_for_tests,
5832                build_record_newtype_composite_catalog_for_tests,
5833                empty_accepted_enum_catalog_for_tests, enum_catalog::ValueAdmissionBudget,
5834            },
5835        },
5836        error::InternalError,
5837        traits::{CanisterKind, Path},
5838        types::EntityTag,
5839        value::InputValue,
5840    };
5841    use icydb_schema::{
5842        ConstraintSourceKey, EntitySourceKey, FieldSourceKey, ScalarType, TypeSourceKey,
5843    };
5844    use std::{cell::RefCell, collections::BTreeMap};
5845
5846    const STORE_PATH: &str = "session::write::targeted_rule_mutation_tests::Store";
5847    const ENTITY_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity";
5848    const ID_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity::id";
5849    const PROFILE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Entity::profile";
5850    const UPDATED_AT_SOURCE: &str =
5851        "session::write::targeted_rule_mutation_tests::Entity::updated_at";
5852    const PROFILE_TYPE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Profile";
5853    const DEGREE_TYPE_SOURCE: &str = "session::write::targeted_rule_mutation_tests::Degree";
5854    const DEGREE_MEMBER_SOURCE: &str =
5855        "session::write::targeted_rule_mutation_tests::Profile::degree";
5856    const DEGREE_RULE_SOURCE: &str =
5857        "session::write::targeted_rule_mutation_tests::Profile::degree_multiple";
5858
5859    struct TestCanister;
5860
5861    impl Path for TestCanister {
5862        const PATH: &'static str = "session::write::targeted_rule_mutation_tests::Canister";
5863    }
5864
5865    impl CanisterKind for TestCanister {
5866        const COMMIT_MEMORY_ID: u8 = 43;
5867        const COMMIT_STABLE_KEY: &'static str = "icydb.targeted_mutation_tests.commit.v1";
5868        const STARTUP_MEMORY_ID: u8 = 49;
5869        const STARTUP_STABLE_KEY: &'static str = "icydb.targeted_mutation_tests.startup.control.v1";
5870        const INTEGRITY_PROGRESS_MEMORY_ID: u8 = 44;
5871        const INTEGRITY_PROGRESS_STABLE_KEY: &'static str =
5872            "icydb.targeted_mutation_tests.integrity.progress.v1";
5873    }
5874
5875    thread_local! {
5876        static DATA_STORE: RefCell<DataStore> = const { RefCell::new(DataStore::init_heap()) };
5877        static INDEX_STORE: RefCell<IndexStore> = const { RefCell::new(IndexStore::init_heap()) };
5878        static SCHEMA_STORE: RefCell<SchemaStore> =
5879            const { RefCell::new(SchemaStore::init_heap()) };
5880        static STORE_REGISTRY: StoreRegistry = {
5881            let mut registry = StoreRegistry::new();
5882            registry.register_store(
5883                STORE_PATH,
5884                &DATA_STORE,
5885                &INDEX_STORE,
5886                &SCHEMA_STORE,
5887                StoreAllocationIdentities::absent(),
5888                StoreRuntimeStorageCapabilities::heap(),
5889            ).expect("targeted mutation test store should register");
5890            registry
5891        };
5892    }
5893
5894    fn source<T, E: std::fmt::Debug>(raw: &str, parse: impl FnOnce(String) -> Result<T, E>) -> T {
5895        parse(raw.to_string()).expect("test source identity should admit")
5896    }
5897
5898    fn profile_input(degree: u64) -> InputValue {
5899        InputValue::Map(vec![(
5900            InputValue::Text("degree".to_string()),
5901            InputValue::Nat64(degree),
5902        )])
5903    }
5904
5905    fn structural_patch(id: u64, degree: u64) -> DynamicStructuralPatch {
5906        DynamicStructuralPatch::new(vec![
5907            (
5908                "id".to_string(),
5909                DynamicWriteCell::Value(InputValue::Nat64(id)),
5910            ),
5911            (
5912                "profile".to_string(),
5913                DynamicWriteCell::Value(profile_input(degree)),
5914            ),
5915        ])
5916    }
5917
5918    fn encoded_value(
5919        enum_catalog: &crate::db::schema::AcceptedEnumCatalog,
5920        composite_catalog: &AcceptedCompositeCatalog,
5921        name: &str,
5922        kind: &AcceptedFieldKind,
5923        storage_decode: FieldStorageDecode,
5924        leaf_codec: LeafCodec,
5925        value: InputValue,
5926    ) -> Vec<u8> {
5927        let field = AcceptedFieldDecodeContract::new(name, kind, false, storage_decode, leaf_codec);
5928        encode_input_value_for_candidate_field_contract(
5929            enum_catalog,
5930            composite_catalog,
5931            field,
5932            value,
5933            &mut ValueAdmissionBudget::standard(),
5934        )
5935        .expect("test accepted value should encode")
5936    }
5937
5938    fn nat64_literal(
5939        enum_catalog: &crate::db::schema::AcceptedEnumCatalog,
5940        composite_catalog: &AcceptedCompositeCatalog,
5941        value: u64,
5942    ) -> AcceptedCheckLiteralV1 {
5943        let kind = AcceptedFieldKind::Nat64;
5944        AcceptedCheckLiteralV1::from_accepted_parts(
5945            kind.clone(),
5946            FieldStorageDecode::ByKind,
5947            LeafCodec::Scalar(ScalarCodec::Nat64),
5948            encoded_value(
5949                enum_catalog,
5950                composite_catalog,
5951                "degree_bound",
5952                &kind,
5953                FieldStorageDecode::ByKind,
5954                LeafCodec::Scalar(ScalarCodec::Nat64),
5955                InputValue::Nat64(value),
5956            ),
5957        )
5958    }
5959
5960    fn targeted_constraint_id(error: &InternalError) -> u32 {
5961        let facts = error.diagnostic_facts();
5962        assert!(facts.contains(&(
5963            icydb_diagnostic_code::DiagnosticFactTag::MutationOperation,
5964            icydb_diagnostic_code::DiagnosticMutationOperation::Insert.raw(),
5965        )));
5966        assert!(facts.contains(&(icydb_diagnostic_code::DiagnosticFactTag::BatchPosition, 0,)));
5967        assert!(facts.contains(&(
5968            icydb_diagnostic_code::DiagnosticFactTag::ConstraintKind,
5969            icydb_diagnostic_code::DiagnosticConstraintKind::TargetedRule.raw(),
5970        )));
5971        assert_eq!(
5972            facts
5973                .iter()
5974                .filter(|(tag, _)| matches!(
5975                    tag,
5976                    icydb_diagnostic_code::DiagnosticFactTag::RootField
5977                        | icydb_diagnostic_code::DiagnosticFactTag::RecordMember
5978                ))
5979                .copied()
5980                .collect::<Vec<_>>(),
5981            vec![
5982                (icydb_diagnostic_code::DiagnosticFactTag::RootField, 2),
5983                (
5984                    icydb_diagnostic_code::DiagnosticFactTag::RecordMember,
5985                    icydb_diagnostic_code::pack_u32_pair(1, 1),
5986                ),
5987            ]
5988        );
5989        let value = facts
5990            .iter()
5991            .find_map(|(tag, value)| {
5992                (*tag == icydb_diagnostic_code::DiagnosticFactTag::ConstraintId).then_some(*value)
5993            })
5994            .expect("targeted mutation should retain its accepted constraint ID");
5995        u32::try_from(value).expect("accepted constraint ID fits u32")
5996    }
5997
5998    #[expect(
5999        clippy::too_many_lines,
6000        reason = "one end-to-end fixture proves every maintained write frontend converges on the same accepted targeted-rule schedule"
6001    )]
6002    #[test]
6003    fn targeted_rules_converge_across_dynamic_typed_sql_default_timestamp_and_batch_writes() {
6004        DATA_STORE.with(|store| *store.borrow_mut() = DataStore::init_heap());
6005        INDEX_STORE.with(|store| *store.borrow_mut() = IndexStore::init_heap());
6006        SCHEMA_STORE.with(|store| *store.borrow_mut() = SchemaStore::init_heap());
6007
6008        let entity_tag = EntityTag::new(93);
6009        let enum_catalog = empty_accepted_enum_catalog_for_tests();
6010        let (composite_catalog, profile_type, degree_type, degree_member) =
6011            build_record_newtype_composite_catalog_for_tests(
6012                "tests::TargetedProfile".to_string(),
6013                "degree".to_string(),
6014                "tests::TargetedDegree".to_string(),
6015                AcceptedFieldKind::Nat64,
6016                &enum_catalog,
6017            )
6018            .expect("targeted mutation composites should close");
6019        let profile_kind = AcceptedFieldKind::Composite {
6020            type_id: profile_type,
6021        };
6022        let profile_default = encoded_value(
6023            &enum_catalog,
6024            &composite_catalog,
6025            "profile",
6026            &profile_kind,
6027            FieldStorageDecode::CatalogValue,
6028            LeafCodec::Structural,
6029            profile_input(12),
6030        );
6031        let fields = vec![
6032            PersistedFieldSnapshot::new_initial(
6033                FieldId::new(1),
6034                "id".to_string(),
6035                SchemaFieldSlot::new(0),
6036                AcceptedFieldKind::Nat64,
6037                Vec::new(),
6038                false,
6039                SchemaInsertDefault::None,
6040                FieldStorageDecode::ByKind,
6041                LeafCodec::Scalar(ScalarCodec::Nat64),
6042            ),
6043            PersistedFieldSnapshot::new_initial(
6044                FieldId::new(2),
6045                "profile".to_string(),
6046                SchemaFieldSlot::new(1),
6047                profile_kind,
6048                vec![PersistedNestedLeafSnapshot::new(
6049                    vec!["degree".to_string()],
6050                    AcceptedFieldKind::Composite {
6051                        type_id: degree_type,
6052                    },
6053                    false,
6054                )],
6055                false,
6056                SchemaInsertDefault::SlotPayload(profile_default),
6057                FieldStorageDecode::CatalogValue,
6058                LeafCodec::Structural,
6059            ),
6060            PersistedFieldSnapshot::new_initial_with_write_policy(
6061                FieldId::new(3),
6062                "updated_at".to_string(),
6063                SchemaFieldSlot::new(2),
6064                AcceptedFieldKind::Timestamp,
6065                Vec::new(),
6066                false,
6067                SchemaInsertDefault::None,
6068                SchemaFieldWritePolicy::from_model_policies(
6069                    None,
6070                    Some(FieldWriteManagement::UpdatedAt),
6071                ),
6072                FieldStorageDecode::ByKind,
6073                LeafCodec::Scalar(ScalarCodec::Timestamp),
6074            ),
6075        ];
6076        let mut snapshot = PersistedSchemaSnapshot::new(
6077            SchemaVersion::initial(),
6078            ENTITY_SOURCE.to_string(),
6079            "TargetedMutation".to_string(),
6080            FieldId::new(1),
6081            SchemaRowLayout::initial(
6082                fields
6083                    .iter()
6084                    .map(|field| (field.id(), field.slot()))
6085                    .collect(),
6086            ),
6087            fields,
6088        );
6089        let constraint_catalog = snapshot
6090            .constraint_catalog()
6091            .clone()
6092            .with_added_targeted_rule(
6093                "profile_degree_multiple".to_string(),
6094                ConstraintOrigin::Generated,
6095                AcceptedRuleTarget::new(
6096                    FieldId::new(2),
6097                    AcceptedNamedTypeIdentity::Composite(degree_type),
6098                ),
6099                AcceptedRuleOperation::MultipleOf {
6100                    divisor: nat64_literal(&enum_catalog, &composite_catalog, 5),
6101                },
6102            )
6103            .expect("targeted mutation rule should allocate");
6104        let targeted_rule_id = constraint_catalog
6105            .constraints()
6106            .last()
6107            .expect("targeted mutation rule should persist")
6108            .id();
6109        snapshot = snapshot.with_constraint_catalog(constraint_catalog);
6110
6111        let entity_source = source(ENTITY_SOURCE, EntitySourceKey::try_new);
6112        let id_source = source(ID_SOURCE, FieldSourceKey::try_new);
6113        let profile_source = source(PROFILE_SOURCE, FieldSourceKey::try_new);
6114        let updated_at_source = source(UPDATED_AT_SOURCE, FieldSourceKey::try_new);
6115        let profile_type_source = source(PROFILE_TYPE_SOURCE, TypeSourceKey::try_new);
6116        let degree_type_source = source(DEGREE_TYPE_SOURCE, TypeSourceKey::try_new);
6117        let degree_member_source = source(DEGREE_MEMBER_SOURCE, FieldSourceKey::try_new);
6118        let degree_rule_source = source(DEGREE_RULE_SOURCE, ConstraintSourceKey::try_new);
6119        let source_bindings = AcceptedSourceBindingCatalog::initial_for_tests(
6120            BTreeMap::from([(entity_source, entity_tag)]),
6121            BTreeMap::from([
6122                ((entity_tag, id_source), FieldId::new(1)),
6123                ((entity_tag, profile_source), FieldId::new(2)),
6124                ((entity_tag, updated_at_source), FieldId::new(3)),
6125            ]),
6126            BTreeMap::from([((entity_tag, degree_rule_source), targeted_rule_id)]),
6127            BTreeMap::new(),
6128            BTreeMap::new(),
6129        )
6130        .with_initial_named_types_for_tests(
6131            BTreeMap::from([
6132                (
6133                    profile_type_source,
6134                    AcceptedNamedTypeIdentity::Composite(profile_type),
6135                ),
6136                (
6137                    degree_type_source,
6138                    AcceptedNamedTypeIdentity::Composite(degree_type),
6139                ),
6140            ]),
6141            BTreeMap::new(),
6142            BTreeMap::from([((profile_type, degree_member_source), degree_member)]),
6143        );
6144        let candidate = accepted_schema_candidate_with_catalogs_for_tests(
6145            STORE_PATH,
6146            AcceptedSchemaRevision::INITIAL,
6147            enum_catalog,
6148            composite_catalog,
6149            source_bindings,
6150            BTreeMap::from([(entity_tag, snapshot)]),
6151        );
6152
6153        let session = DbSession::<TestCanister>::new(
6154            &STORE_REGISTRY,
6155            &crate::db::RequestExecutionRoot::__new_runtime_root(),
6156        );
6157        session
6158            .db
6159            .drive_startup_recovery_page()
6160            .expect("targeted mutation test database should initialize");
6161        let store = session
6162            .db
6163            .store_handle(STORE_PATH)
6164            .expect("targeted mutation test store should resolve");
6165        crate::db::commit::publish_accepted_schema_candidate(
6166            STORE_PATH,
6167            store,
6168            AcceptedSchemaRevision::NONE,
6169            &candidate,
6170        )
6171        .expect("targeted mutation candidate should publish");
6172
6173        let dynamic_error = session
6174            .execute_trusted_dynamic_mutation(&DynamicMutation::Insert {
6175                entity: "TargetedMutation".to_string(),
6176                patch: structural_patch(1, 12),
6177            })
6178            .expect_err("dynamic write must enforce the targeted rule");
6179        assert_eq!(
6180            targeted_constraint_id(&dynamic_error),
6181            targeted_rule_id.get()
6182        );
6183
6184        let binding = session
6185            .issue_typed_entity_binding(
6186                ENTITY_SOURCE,
6187                &[
6188                    DynamicTypedFieldBindingRequest::new(
6189                        ID_SOURCE.to_string(),
6190                        DynamicTypedFieldType::Scalar(ScalarType::Nat64),
6191                        false,
6192                    ),
6193                    DynamicTypedFieldBindingRequest::new(
6194                        PROFILE_SOURCE.to_string(),
6195                        DynamicTypedFieldType::Named(PROFILE_TYPE_SOURCE.to_string()),
6196                        false,
6197                    ),
6198                    DynamicTypedFieldBindingRequest::new(
6199                        UPDATED_AT_SOURCE.to_string(),
6200                        DynamicTypedFieldType::Scalar(ScalarType::Timestamp),
6201                        false,
6202                    ),
6203                ],
6204            )
6205            .expect("targeted typed binding should issue");
6206        let typed_patch = binding
6207            .bind_write_fields(vec![
6208                (
6209                    ID_SOURCE.to_string(),
6210                    DynamicWriteCell::Value(InputValue::Nat64(2)),
6211                ),
6212                (
6213                    PROFILE_SOURCE.to_string(),
6214                    DynamicWriteCell::Value(profile_input(12)),
6215                ),
6216            ])
6217            .expect("targeted typed patch should bind");
6218        let typed_error = session
6219            .execute_trusted_typed_mutation(
6220                &binding,
6221                &DynamicTypedMutation::Insert { patch: typed_patch },
6222            )
6223            .expect_err("typed write must enforce the targeted rule");
6224        assert_eq!(targeted_constraint_id(&typed_error), targeted_rule_id.get());
6225
6226        #[cfg(feature = "sql")]
6227        {
6228            let sql_error = session
6229                .execute_trusted_sql_mutation("INSERT INTO TargetedMutation (id) VALUES (3)")
6230                .expect_err("SQL default resolution must enforce the targeted rule");
6231            let crate::db::QueryError::Execute(execute) = sql_error else {
6232                panic!("targeted SQL write should fail at shared execution admission");
6233            };
6234            assert_eq!(
6235                targeted_constraint_id(execute.as_internal()),
6236                targeted_rule_id.get()
6237            );
6238        }
6239
6240        session
6241            .execute_trusted_dynamic_mutation_batch(vec![
6242                DynamicMutation::Insert {
6243                    entity: "TargetedMutation".to_string(),
6244                    patch: structural_patch(4, 5),
6245                },
6246                DynamicMutation::Insert {
6247                    entity: "TargetedMutation".to_string(),
6248                    patch: structural_patch(5, 12),
6249                },
6250            ])
6251            .expect_err("one invalid targeted value must reject the whole batch");
6252        assert_eq!(
6253            DATA_STORE.with(|store| store.borrow().exact_entity_count(entity_tag)),
6254            Some(0),
6255            "no frontend or earlier valid batch row may escape targeted admission",
6256        );
6257
6258        let admitted = session
6259            .execute_trusted_dynamic_mutation_batch(vec![
6260                DynamicMutation::Insert {
6261                    entity: "TargetedMutation".to_string(),
6262                    patch: structural_patch(6, 5),
6263                },
6264                DynamicMutation::Insert {
6265                    entity: "TargetedMutation".to_string(),
6266                    patch: structural_patch(7, 10),
6267                },
6268            ])
6269            .expect("compliant targeted values should share one accepted batch");
6270        let [first, second] = admitted.rows.as_slice() else {
6271            panic!("the mixed targeted batch should return two rows");
6272        };
6273        let first_timestamp = first
6274            .get(2)
6275            .expect("the first mixed row should contain its managed timestamp");
6276        assert!(matches!(
6277            first_timestamp,
6278            crate::value::OutputValue::Timestamp(_)
6279        ));
6280        assert_eq!(
6281            second.get(2),
6282            Some(first_timestamp),
6283            "one accepted mixed batch must materialize one managed timestamp",
6284        );
6285        assert_eq!(
6286            DATA_STORE.with(|store| store.borrow().exact_entity_count(entity_tag)),
6287            Some(2),
6288        );
6289    }
6290}