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