Skip to main content

icydb_core/db/data/persisted_row/
mod.rs

1//! Module: data::persisted_row
2//! Responsibility: slot-oriented persisted-row seams over runtime row bytes.
3//! Does not own: row envelope versions, typed entity materialization, or query semantics.
4//! Boundary: commit/index planning, row writes, and typed materialization all
5//! consume the canonical slot-oriented persisted-row boundary here.
6
7mod codec;
8
9use crate::{
10    db::{
11        codec::serialize_row_payload,
12        data::{
13            CanonicalRow, DataKey, RawRow, StructuralRowDecodeError, StructuralRowFieldBytes,
14            decode_storage_key_field_bytes, decode_structural_field_by_kind_bytes,
15            decode_structural_value_storage_bytes,
16        },
17        scalar_expr::compile_scalar_literal_expr_value,
18        schema::{field_type_from_model_kind, literal_matches_type},
19    },
20    error::InternalError,
21    model::{
22        entity::{EntityModel, resolve_field_slot, resolve_primary_key_slot},
23        field::{FieldKind, FieldModel, FieldStorageDecode, LeafCodec},
24    },
25    serialize::serialize,
26    traits::EntityKind,
27    value::{StorageKey, Value, ValueEnum},
28};
29use serde_cbor::{Value as CborValue, value::to_value as to_cbor_value};
30use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap};
31
32use self::codec::{decode_scalar_slot_value, encode_scalar_slot_value};
33
34pub use self::codec::{
35    PersistedScalar, ScalarSlotValueRef, ScalarValueRef, decode_persisted_custom_many_slot_payload,
36    decode_persisted_custom_slot_payload, decode_persisted_non_null_slot_payload,
37    decode_persisted_option_scalar_slot_payload, decode_persisted_option_slot_payload,
38    decode_persisted_scalar_slot_payload, decode_persisted_slot_payload,
39    encode_persisted_custom_many_slot_payload, encode_persisted_custom_slot_payload,
40    encode_persisted_option_scalar_slot_payload, encode_persisted_scalar_slot_payload,
41    encode_persisted_slot_payload,
42};
43
44///
45/// FieldSlot
46///
47/// FieldSlot
48///
49/// FieldSlot is the structural stable slot reference used by the `0.64`
50/// patching path.
51/// It intentionally carries only the model-local slot index so field-level
52/// mutation stays structural instead of reintroducing typed entity helpers.
53///
54
55#[allow(dead_code)]
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub(in crate::db) struct FieldSlot {
58    index: usize,
59}
60
61#[allow(dead_code)]
62impl FieldSlot {
63    /// Resolve one stable field slot by runtime field name.
64    #[must_use]
65    pub(in crate::db) fn resolve(model: &'static EntityModel, field_name: &str) -> Option<Self> {
66        resolve_field_slot(model, field_name).map(|index| Self { index })
67    }
68
69    /// Build one stable field slot from an already validated index.
70    pub(in crate::db) fn from_index(
71        model: &'static EntityModel,
72        index: usize,
73    ) -> Result<Self, InternalError> {
74        field_model_for_slot(model, index)?;
75
76        Ok(Self { index })
77    }
78
79    /// Return the stable slot index inside `EntityModel::fields`.
80    #[must_use]
81    pub(in crate::db) const fn index(self) -> usize {
82        self.index
83    }
84}
85
86///
87/// FieldUpdate
88///
89/// FieldUpdate
90///
91/// FieldUpdate carries one ordered field-level mutation over the structural
92/// persisted-row boundary.
93/// `UpdatePatch` applies these entries in order and last write wins for the
94/// same slot.
95///
96
97#[allow(dead_code)]
98#[derive(Clone, Debug, Eq, PartialEq)]
99pub(in crate::db) struct FieldUpdate {
100    slot: FieldSlot,
101    value: Value,
102}
103
104#[allow(dead_code)]
105impl FieldUpdate {
106    /// Build one field-level structural update.
107    #[must_use]
108    pub(in crate::db) const fn new(slot: FieldSlot, value: Value) -> Self {
109        Self { slot, value }
110    }
111
112    /// Return the stable target slot.
113    #[must_use]
114    pub(in crate::db) const fn slot(&self) -> FieldSlot {
115        self.slot
116    }
117
118    /// Return the runtime value payload for this update.
119    #[must_use]
120    pub(in crate::db) const fn value(&self) -> &Value {
121        &self.value
122    }
123}
124
125///
126/// UpdatePatch
127///
128/// UpdatePatch
129///
130/// UpdatePatch is the ordered structural mutation program applied to one
131/// persisted row.
132/// This is the phase-1 `0.64` patch container: it updates slot values
133/// structurally and then re-encodes the full row.
134///
135
136#[derive(Clone, Debug, Default, Eq, PartialEq)]
137pub struct UpdatePatch {
138    entries: Vec<FieldUpdate>,
139}
140
141impl UpdatePatch {
142    /// Build one empty patch.
143    #[must_use]
144    pub const fn new() -> Self {
145        Self {
146            entries: Vec::new(),
147        }
148    }
149
150    /// Append one structural field update in declaration order.
151    #[must_use]
152    pub(in crate::db) fn set(mut self, slot: FieldSlot, value: Value) -> Self {
153        self.entries.push(FieldUpdate::new(slot, value));
154        self
155    }
156
157    /// Resolve one field name and append its structural update.
158    pub fn set_field(
159        self,
160        model: &'static EntityModel,
161        field_name: &str,
162        value: Value,
163    ) -> Result<Self, InternalError> {
164        let Some(slot) = FieldSlot::resolve(model, field_name) else {
165            return Err(InternalError::mutation_structural_field_unknown(
166                model.path(),
167                field_name,
168            ));
169        };
170
171        Ok(self.set(slot, value))
172    }
173
174    /// Borrow the ordered field updates carried by this patch.
175    #[must_use]
176    pub(in crate::db) const fn entries(&self) -> &[FieldUpdate] {
177        self.entries.as_slice()
178    }
179
180    /// Return whether this patch carries no field updates.
181    #[must_use]
182    pub(in crate::db) const fn is_empty(&self) -> bool {
183        self.entries.is_empty()
184    }
185}
186
187///
188/// SerializedFieldUpdate
189///
190/// SerializedFieldUpdate
191///
192/// SerializedFieldUpdate carries one ordered field-level mutation after the
193/// owning persisted-row field codec has already lowered the runtime `Value`
194/// into canonical slot payload bytes.
195/// This lets later patch-application stages consume one mechanical slot-patch
196/// artifact instead of rebuilding per-field encode dispatch.
197///
198
199#[allow(dead_code)]
200#[derive(Clone, Debug, Eq, PartialEq)]
201pub(in crate::db) struct SerializedFieldUpdate {
202    slot: FieldSlot,
203    payload: Vec<u8>,
204}
205
206#[allow(dead_code)]
207impl SerializedFieldUpdate {
208    /// Build one serialized structural field update.
209    #[must_use]
210    pub(in crate::db) const fn new(slot: FieldSlot, payload: Vec<u8>) -> Self {
211        Self { slot, payload }
212    }
213
214    /// Return the stable target slot.
215    #[must_use]
216    pub(in crate::db) const fn slot(&self) -> FieldSlot {
217        self.slot
218    }
219
220    /// Borrow the canonical slot payload bytes for this update when present.
221    #[must_use]
222    pub(in crate::db) const fn payload(&self) -> &[u8] {
223        self.payload.as_slice()
224    }
225}
226
227///
228/// SerializedUpdatePatch
229///
230/// SerializedUpdatePatch
231///
232/// SerializedUpdatePatch is the canonical serialized form of `UpdatePatch`
233/// over persisted-row slot payload bytes.
234/// This is the structural patch artifact later write-path stages can stage or
235/// replay without re-entering field-contract encode logic.
236///
237
238#[allow(dead_code)]
239#[derive(Clone, Debug, Default, Eq, PartialEq)]
240pub(in crate::db) struct SerializedUpdatePatch {
241    entries: Vec<SerializedFieldUpdate>,
242}
243
244#[allow(dead_code)]
245impl SerializedUpdatePatch {
246    /// Build one serialized patch from already encoded slot payloads.
247    #[must_use]
248    pub(in crate::db) const fn new(entries: Vec<SerializedFieldUpdate>) -> Self {
249        Self { entries }
250    }
251
252    /// Borrow the ordered serialized field updates carried by this patch.
253    #[must_use]
254    pub(in crate::db) const fn entries(&self) -> &[SerializedFieldUpdate] {
255        self.entries.as_slice()
256    }
257
258    /// Return whether this serialized patch carries no field updates.
259    #[must_use]
260    pub(in crate::db) const fn is_empty(&self) -> bool {
261        self.entries.is_empty()
262    }
263}
264
265///
266/// SlotReader
267///
268/// SlotReader exposes one persisted row as stable slot-addressable fields.
269/// Callers may inspect field presence, borrow raw field bytes, or decode one
270/// field value on demand.
271///
272
273pub trait SlotReader {
274    /// Return the structural model that owns this slot mapping.
275    fn model(&self) -> &'static EntityModel;
276
277    /// Return whether the given slot is present in the persisted row.
278    fn has(&self, slot: usize) -> bool;
279
280    /// Borrow the raw persisted payload for one slot when present.
281    fn get_bytes(&self, slot: usize) -> Option<&[u8]>;
282
283    /// Decode one slot as a scalar leaf when the field model declares a scalar codec.
284    fn get_scalar(&self, slot: usize) -> Result<Option<ScalarSlotValueRef<'_>>, InternalError>;
285
286    /// Decode one slot value on demand using the field contract declared by the model.
287    fn get_value(&mut self, slot: usize) -> Result<Option<Value>, InternalError>;
288}
289
290///
291/// CanonicalSlotReader
292///
293/// CanonicalSlotReader
294///
295/// CanonicalSlotReader is the stricter structural row-reader contract used
296/// once `0.65` canonical-row invariants are in force.
297/// Declared slots must already exist, so callers can fail closed on missing
298/// payloads instead of carrying absent-slot fallback branches.
299///
300
301pub(in crate::db) trait CanonicalSlotReader: SlotReader {
302    /// Borrow one declared slot payload, erroring when the persisted row is not canonical.
303    fn required_bytes(&self, slot: usize) -> Result<&[u8], InternalError> {
304        let field = field_model_for_slot(self.model(), slot)?;
305
306        self.get_bytes(slot)
307            .ok_or_else(|| InternalError::persisted_row_declared_field_missing(field.name()))
308    }
309
310    /// Read one scalar slot through the structural fast path without allowing
311    /// declared-slot absence.
312    fn required_scalar(&self, slot: usize) -> Result<ScalarSlotValueRef<'_>, InternalError> {
313        let field = field_model_for_slot(self.model(), slot)?;
314        debug_assert!(matches!(field.leaf_codec(), LeafCodec::Scalar(_)));
315
316        self.get_scalar(slot)?
317            .ok_or_else(|| InternalError::persisted_row_declared_field_missing(field.name()))
318    }
319
320    /// Decode one declared slot through the owning field contract without
321    /// allowing absent payloads.
322    fn required_value_by_contract(&self, slot: usize) -> Result<Value, InternalError> {
323        decode_slot_value_from_bytes(self.model(), slot, self.required_bytes(slot)?)
324    }
325
326    /// Borrow one declared slot value when the concrete reader already owns a
327    /// validated decoded cache, while preserving the existing owned fallback
328    /// for reader implementations that still decode on demand.
329    fn required_value_by_contract_cow(&self, slot: usize) -> Result<Cow<'_, Value>, InternalError> {
330        Ok(Cow::Owned(self.required_value_by_contract(slot)?))
331    }
332}
333
334///
335/// SlotWriter
336///
337/// SlotWriter is the canonical row-container output seam used by persisted-row
338/// writers.
339///
340
341pub trait SlotWriter {
342    /// Record one slot payload for the current row.
343    fn write_slot(&mut self, slot: usize, payload: Option<&[u8]>) -> Result<(), InternalError>;
344
345    /// Record one scalar slot payload using the canonical scalar leaf envelope.
346    fn write_scalar(
347        &mut self,
348        slot: usize,
349        value: ScalarSlotValueRef<'_>,
350    ) -> Result<(), InternalError> {
351        let payload = encode_scalar_slot_value(value);
352
353        self.write_slot(slot, Some(payload.as_slice()))
354    }
355}
356
357// Resolve one staged slot cell by layout index before writer-specific payload handling.
358fn slot_cell_mut<T>(slots: &mut [T], slot: usize) -> Result<&mut T, InternalError> {
359    slots.get_mut(slot).ok_or_else(|| {
360        InternalError::persisted_row_encode_failed(
361            format!("slot {slot} is outside the row layout",),
362        )
363    })
364}
365
366// Reject slot clears at the canonical slot-image staging boundary while keeping
367// writer-specific error wording at the call site.
368fn required_slot_payload_bytes<'a>(
369    model: &'static EntityModel,
370    writer_label: &str,
371    slot: usize,
372    payload: Option<&'a [u8]>,
373) -> Result<&'a [u8], InternalError> {
374    payload.ok_or_else(|| {
375        InternalError::persisted_row_encode_failed(format!(
376            "{writer_label} cannot clear slot {slot} for entity '{}'",
377            model.path()
378        ))
379    })
380}
381
382// Encode one fixed-width slot table plus concatenated slot payload bytes into
383// the canonical row payload container.
384fn encode_slot_payload_from_parts(
385    slot_count: usize,
386    slot_table: &[(u32, u32)],
387    payload_bytes: &[u8],
388) -> Result<Vec<u8>, InternalError> {
389    let field_count = u16::try_from(slot_count).map_err(|_| {
390        InternalError::persisted_row_encode_failed(format!(
391            "field count {slot_count} exceeds u16 slot table capacity",
392        ))
393    })?;
394    let mut encoded = Vec::with_capacity(
395        usize::from(field_count) * (u32::BITS as usize / 4) + 2 + payload_bytes.len(),
396    );
397    encoded.extend_from_slice(&field_count.to_be_bytes());
398    for (start, len) in slot_table {
399        encoded.extend_from_slice(&start.to_be_bytes());
400        encoded.extend_from_slice(&len.to_be_bytes());
401    }
402    encoded.extend_from_slice(payload_bytes);
403
404    Ok(encoded)
405}
406
407///
408/// PersistedRow
409///
410/// PersistedRow is the derive-owned bridge between typed entities and
411/// slot-addressable persisted rows.
412/// It owns entity-specific materialization/default semantics while runtime
413/// paths stay structural at the row boundary.
414///
415
416pub trait PersistedRow: EntityKind + Sized {
417    /// Materialize one typed entity from one slot reader.
418    fn materialize_from_slots(slots: &mut dyn SlotReader) -> Result<Self, InternalError>;
419
420    /// Write one typed entity into one slot writer.
421    fn write_slots(&self, out: &mut dyn SlotWriter) -> Result<(), InternalError>;
422
423    /// Decode one slot value needed by structural planner/projection consumers.
424    fn project_slot(slots: &mut dyn SlotReader, slot: usize) -> Result<Option<Value>, InternalError>
425    where
426        Self: crate::traits::FieldProjection,
427    {
428        let entity = Self::materialize_from_slots(slots)?;
429
430        Ok(<Self as crate::traits::FieldProjection>::get_value_by_index(&entity, slot))
431    }
432}
433
434/// Decode one slot value through the declared field contract without routing
435/// through `SlotReader::get_value`.
436#[cfg(test)]
437pub(in crate::db) fn decode_slot_value_by_contract(
438    slots: &dyn SlotReader,
439    slot: usize,
440) -> Result<Option<Value>, InternalError> {
441    let Some(raw_value) = slots.get_bytes(slot) else {
442        return Ok(None);
443    };
444
445    decode_slot_value_from_bytes(slots.model(), slot, raw_value).map(Some)
446}
447
448/// Decode one structural slot payload using the owning model field contract.
449///
450/// This is the canonical field-level decode boundary for persisted-row bytes.
451/// Higher-level row readers may still cache decoded values, but they should not
452/// rebuild scalar-vs-CBOR field dispatch themselves.
453pub(in crate::db) fn decode_slot_value_from_bytes(
454    model: &'static EntityModel,
455    slot: usize,
456    raw_value: &[u8],
457) -> Result<Value, InternalError> {
458    let field = field_model_for_slot(model, slot)?;
459
460    decode_slot_value_for_field(field, raw_value)
461}
462
463// Decode one structural slot payload once the owning field contract has
464// already been resolved.
465fn decode_slot_value_for_field(
466    field: &FieldModel,
467    raw_value: &[u8],
468) -> Result<Value, InternalError> {
469    match field.leaf_codec() {
470        LeafCodec::Scalar(codec) => match decode_scalar_slot_value(raw_value, codec, field.name())?
471        {
472            ScalarSlotValueRef::Null => Ok(Value::Null),
473            ScalarSlotValueRef::Value(value) => Ok(value.into_value()),
474        },
475        LeafCodec::CborFallback => decode_non_scalar_slot_value(raw_value, field),
476    }
477}
478
479/// Encode one structural slot value using the owning model field contract.
480///
481/// This is the initial `0.64` write-side field-codec boundary. It currently
482/// covers:
483/// - scalar leaf slots
484/// - `FieldStorageDecode::Value` slots
485///
486/// Composite `ByKind` field encoding remains a follow-up slice so the runtime
487/// can add one structural encoder owner instead of quietly rebuilding typed
488/// per-field branches.
489#[allow(dead_code)]
490pub(in crate::db) fn encode_slot_value_from_value(
491    model: &'static EntityModel,
492    slot: usize,
493    value: &Value,
494) -> Result<Vec<u8>, InternalError> {
495    let field = field_model_for_slot(model, slot)?;
496    ensure_slot_value_matches_field_contract(field, value)?;
497
498    match field.storage_decode() {
499        FieldStorageDecode::Value => serialize(value)
500            .map_err(|err| InternalError::persisted_row_field_encode_failed(field.name(), err)),
501        FieldStorageDecode::ByKind => match field.leaf_codec() {
502            LeafCodec::Scalar(_) => {
503                let scalar = compile_scalar_literal_expr_value(value).ok_or_else(|| {
504                    InternalError::persisted_row_field_encode_failed(
505                        field.name(),
506                        format!(
507                            "field kind {:?} requires a scalar runtime value, found {value:?}",
508                            field.kind()
509                        ),
510                    )
511                })?;
512
513                Ok(encode_scalar_slot_value(scalar.as_slot_value_ref()))
514            }
515            LeafCodec::CborFallback => {
516                encode_structural_field_bytes_by_kind(field.kind(), value, field.name())
517            }
518        },
519    }
520}
521
522// Decode one slot payload and immediately re-encode it through the current
523// field contract so every row-emission path normalizes bytes at the boundary.
524fn canonicalize_slot_payload(
525    model: &'static EntityModel,
526    slot: usize,
527    raw_value: &[u8],
528) -> Result<Vec<u8>, InternalError> {
529    let value = decode_slot_value_from_bytes(model, slot, raw_value)?;
530
531    encode_slot_value_from_value(model, slot, &value)
532}
533
534// Build one dense canonical slot image from any slot-addressable payload source.
535// Callers keep ownership of missing-slot policy while this helper centralizes
536// the slot-by-slot canonicalization loop.
537fn dense_canonical_slot_image_from_payload_source<'a, F>(
538    model: &'static EntityModel,
539    mut payload_for_slot: F,
540) -> Result<Vec<Vec<u8>>, InternalError>
541where
542    F: FnMut(usize) -> Result<&'a [u8], InternalError>,
543{
544    let mut slot_payloads = Vec::with_capacity(model.fields().len());
545
546    for slot in 0..model.fields().len() {
547        let payload = payload_for_slot(slot)?;
548        slot_payloads.push(canonicalize_slot_payload(model, slot, payload)?);
549    }
550
551    Ok(slot_payloads)
552}
553
554// Emit one raw row from a dense canonical slot image.
555fn emit_raw_row_from_slot_payloads(
556    model: &'static EntityModel,
557    slot_payloads: &[Vec<u8>],
558) -> Result<CanonicalRow, InternalError> {
559    if slot_payloads.len() != model.fields().len() {
560        return Err(InternalError::persisted_row_encode_failed(format!(
561            "canonical slot image expected {} slots for entity '{}', found {}",
562            model.fields().len(),
563            model.path(),
564            slot_payloads.len()
565        )));
566    }
567
568    let mut writer = SlotBufferWriter::for_model(model);
569
570    // Phase 1: write the already canonicalized dense slot image through the
571    // single row-container authority.
572    for (slot, payload) in slot_payloads.iter().enumerate() {
573        writer.write_slot(slot, Some(payload.as_slice()))?;
574    }
575
576    // Phase 2: wrap the canonical slot container in the shared row envelope.
577    let encoded = serialize_row_payload(writer.finish()?)?;
578    let raw_row = RawRow::from_untrusted_bytes(encoded).map_err(InternalError::from)?;
579
580    Ok(CanonicalRow::from_canonical_raw_row(raw_row))
581}
582
583// Build one dense canonical slot image from a serialized patch, failing closed
584// when any declared slot is missing or any payload is non-canonical.
585fn dense_canonical_slot_image_from_serialized_patch(
586    model: &'static EntityModel,
587    patch: &SerializedUpdatePatch,
588) -> Result<Vec<Vec<u8>>, InternalError> {
589    let patch_payloads = serialized_patch_payload_by_slot(model, patch)?;
590
591    dense_canonical_slot_image_from_payload_source(model, |slot| {
592        patch_payloads[slot].ok_or_else(|| {
593            InternalError::persisted_row_encode_failed(format!(
594                "serialized patch did not emit slot {slot} for entity '{}'",
595                model.path()
596            ))
597        })
598    })
599}
600
601/// Build one canonical row from one serialized structural patch that already
602/// describes a full logical row image.
603pub(in crate::db) fn canonical_row_from_serialized_update_patch(
604    model: &'static EntityModel,
605    patch: &SerializedUpdatePatch,
606) -> Result<CanonicalRow, InternalError> {
607    let slot_payloads = dense_canonical_slot_image_from_serialized_patch(model, patch)?;
608
609    emit_raw_row_from_slot_payloads(model, slot_payloads.as_slice())
610}
611
612/// Build one canonical row directly from one typed entity slot writer.
613pub(in crate::db) fn canonical_row_from_entity<E>(entity: &E) -> Result<CanonicalRow, InternalError>
614where
615    E: PersistedRow,
616{
617    let mut writer = SlotBufferWriter::for_model(E::MODEL);
618
619    // Phase 1: let the derive-owned slot writer emit the complete typed row image.
620    entity.write_slots(&mut writer)?;
621
622    // Phase 2: wrap the canonical slot container in the shared row envelope.
623    let encoded = serialize_row_payload(writer.finish()?)?;
624    let raw_row = RawRow::from_untrusted_bytes(encoded).map_err(InternalError::from)?;
625
626    Ok(CanonicalRow::from_canonical_raw_row(raw_row))
627}
628
629/// Build one canonical row from one already-decoded structural slot reader.
630pub(in crate::db) fn canonical_row_from_structural_slot_reader(
631    row_fields: &StructuralSlotReader<'_>,
632) -> Result<CanonicalRow, InternalError> {
633    // Phase 1: canonicalize every declared slot from the already-decoded row image.
634    let slot_payloads = dense_canonical_slot_image_from_payload_source(row_fields.model, |slot| {
635        row_fields.field_bytes.field(slot).ok_or_else(|| {
636            InternalError::persisted_row_encode_failed(format!(
637                "slot {slot} is missing from the baseline row for entity '{}'",
638                row_fields.model.path()
639            ))
640        })
641    })?;
642
643    // Phase 2: re-emit the full image through the single row-emission owner.
644    emit_raw_row_from_slot_payloads(row_fields.model, slot_payloads.as_slice())
645}
646
647// Rebuild one full canonical row image from an existing raw row before it
648// crosses a storage write boundary.
649pub(in crate::db) fn canonical_row_from_raw_row(
650    model: &'static EntityModel,
651    raw_row: &RawRow,
652) -> Result<CanonicalRow, InternalError> {
653    let field_bytes = StructuralRowFieldBytes::from_raw_row(raw_row, model)
654        .map_err(StructuralRowDecodeError::into_internal_error)?;
655
656    // Phase 1: canonicalize every declared slot from the existing row image.
657    let slot_payloads = dense_canonical_slot_image_from_payload_source(model, |slot| {
658        field_bytes.field(slot).ok_or_else(|| {
659            InternalError::persisted_row_encode_failed(format!(
660                "slot {slot} is missing from the baseline row for entity '{}'",
661                model.path()
662            ))
663        })
664    })?;
665
666    // Phase 2: re-emit the full image through the single row-emission owner.
667    emit_raw_row_from_slot_payloads(model, slot_payloads.as_slice())
668}
669
670// Rewrap one row already loaded from storage as a canonical write token.
671pub(in crate::db) const fn canonical_row_from_stored_raw_row(raw_row: RawRow) -> CanonicalRow {
672    CanonicalRow::from_canonical_raw_row(raw_row)
673}
674
675/// Apply one ordered structural patch to one raw row using the current
676/// persisted-row field codec authority.
677#[allow(dead_code)]
678pub(in crate::db) fn apply_update_patch_to_raw_row(
679    model: &'static EntityModel,
680    raw_row: &RawRow,
681    patch: &UpdatePatch,
682) -> Result<CanonicalRow, InternalError> {
683    let serialized_patch = serialize_update_patch_fields(model, patch)?;
684
685    apply_serialized_update_patch_to_raw_row(model, raw_row, &serialized_patch)
686}
687
688/// Serialize one ordered structural patch into canonical slot payload bytes.
689///
690/// This is the phase-1 partial-serialization seam for `0.64`: later mutation
691/// stages can stage or replay one field patch without rebuilding the runtime
692/// value-to-bytes contract per consumer.
693#[allow(dead_code)]
694pub(in crate::db) fn serialize_update_patch_fields(
695    model: &'static EntityModel,
696    patch: &UpdatePatch,
697) -> Result<SerializedUpdatePatch, InternalError> {
698    if patch.is_empty() {
699        return Ok(SerializedUpdatePatch::default());
700    }
701
702    let mut entries = Vec::with_capacity(patch.entries().len());
703
704    // Phase 1: validate and encode each ordered field update through the
705    // canonical slot codec owner.
706    for entry in patch.entries() {
707        let slot = entry.slot();
708        let payload = encode_slot_value_from_value(model, slot.index(), entry.value())?;
709        entries.push(SerializedFieldUpdate::new(slot, payload));
710    }
711
712    Ok(SerializedUpdatePatch::new(entries))
713}
714
715/// Serialize one full typed entity image into the canonical serialized patch
716/// artifact used by row-boundary patch replay.
717///
718/// This keeps typed save/update APIs on the existing surface while moving the
719/// actual after-image staging onto the structural slot-patch boundary.
720#[allow(dead_code)]
721pub(in crate::db) fn serialize_entity_slots_as_update_patch<E>(
722    entity: &E,
723) -> Result<SerializedUpdatePatch, InternalError>
724where
725    E: PersistedRow,
726{
727    let mut writer = SerializedPatchWriter::for_model(E::MODEL);
728
729    // Phase 1: let the derive-owned persisted-row writer emit the complete
730    // structural slot image for this entity.
731    entity.write_slots(&mut writer)?;
732
733    // Phase 2: require a dense slot image so save/update replay remains
734    // equivalent to the existing full-row write semantics.
735    writer.finish_complete()
736}
737
738/// Apply one serialized structural patch to one raw row.
739///
740/// This mechanical replay step no longer owns any `Value -> bytes` dispatch.
741/// It only replays already encoded slot payloads over the current row layout.
742#[allow(dead_code)]
743pub(in crate::db) fn apply_serialized_update_patch_to_raw_row(
744    model: &'static EntityModel,
745    raw_row: &RawRow,
746    patch: &SerializedUpdatePatch,
747) -> Result<CanonicalRow, InternalError> {
748    if patch.is_empty() {
749        return canonical_row_from_raw_row(model, raw_row);
750    }
751
752    let field_bytes = StructuralRowFieldBytes::from_raw_row(raw_row, model)
753        .map_err(StructuralRowDecodeError::into_internal_error)?;
754    let patch_payloads = serialized_patch_payload_by_slot(model, patch)?;
755
756    // Phase 1: replay the current row layout slot-by-slot.
757    // Both patch and baseline bytes are normalized through the field contract
758    // so no opaque payload can cross into the emitted row image.
759    let slot_payloads = dense_canonical_slot_image_from_payload_source(model, |slot| {
760        if let Some(payload) = patch_payloads[slot] {
761            Ok(payload)
762        } else {
763            field_bytes.field(slot).ok_or_else(|| {
764                InternalError::persisted_row_encode_failed(format!(
765                    "slot {slot} is missing from the baseline row for entity '{}'",
766                    model.path()
767                ))
768            })
769        }
770    })?;
771
772    // Phase 2: emit the rebuilt row through the single row-construction owner.
773    emit_raw_row_from_slot_payloads(model, slot_payloads.as_slice())
774}
775
776// Decode one non-scalar slot through the exact persisted contract declared by
777// the field model.
778fn decode_non_scalar_slot_value(
779    raw_value: &[u8],
780    field: &FieldModel,
781) -> Result<Value, InternalError> {
782    let decoded = match field.storage_decode() {
783        crate::model::field::FieldStorageDecode::ByKind => {
784            decode_structural_field_by_kind_bytes(raw_value, field.kind())
785        }
786        crate::model::field::FieldStorageDecode::Value => {
787            decode_structural_value_storage_bytes(raw_value)
788        }
789    };
790
791    decoded.map_err(|err| {
792        InternalError::persisted_row_field_kind_decode_failed(field.name(), field.kind(), err)
793    })
794}
795
796// Validate one runtime value against the persisted field contract before field-
797// level structural encoding writes bytes into a row slot.
798#[allow(dead_code)]
799fn ensure_slot_value_matches_field_contract(
800    field: &FieldModel,
801    value: &Value,
802) -> Result<(), InternalError> {
803    if matches!(value, Value::Null) {
804        if field.nullable() {
805            return Ok(());
806        }
807
808        return Err(InternalError::persisted_row_field_encode_failed(
809            field.name(),
810            "required field cannot store null",
811        ));
812    }
813
814    // `FieldStorageDecode::Value` fields persist the generic `Value` envelope
815    // directly, so storage-side validation must accept structured leaves nested
816    // under collection contracts instead of reusing the predicate literal gate.
817    if matches!(field.storage_decode(), FieldStorageDecode::Value) {
818        if !storage_value_matches_field_kind(field.kind(), value) {
819            return Err(InternalError::persisted_row_field_encode_failed(
820                field.name(),
821                format!(
822                    "field kind {:?} does not accept runtime value {value:?}",
823                    field.kind()
824                ),
825            ));
826        }
827
828        ensure_decimal_scale_matches(field.name(), field.kind(), value)?;
829
830        return ensure_value_is_deterministic_for_storage(field.name(), field.kind(), value);
831    }
832
833    let field_type = field_type_from_model_kind(&field.kind());
834    if !literal_matches_type(value, &field_type) {
835        return Err(InternalError::persisted_row_field_encode_failed(
836            field.name(),
837            format!(
838                "field kind {:?} does not accept runtime value {value:?}",
839                field.kind()
840            ),
841        ));
842    }
843
844    ensure_decimal_scale_matches(field.name(), field.kind(), value)?;
845    ensure_value_is_deterministic_for_storage(field.name(), field.kind(), value)
846}
847
848// Match one runtime value against the semantic field kind used by value-backed
849// storage. Unlike predicate literals, non-queryable structured leaves are valid
850// persisted payloads when they arrive as canonical `Value::List` / `Value::Map`
851// shapes.
852fn storage_value_matches_field_kind(kind: FieldKind, value: &Value) -> bool {
853    match (kind, value) {
854        (FieldKind::Account, Value::Account(_))
855        | (FieldKind::Blob, Value::Blob(_))
856        | (FieldKind::Bool, Value::Bool(_))
857        | (FieldKind::Date, Value::Date(_))
858        | (FieldKind::Decimal { .. }, Value::Decimal(_))
859        | (FieldKind::Duration, Value::Duration(_))
860        | (FieldKind::Enum { .. }, Value::Enum(_))
861        | (FieldKind::Float32, Value::Float32(_))
862        | (FieldKind::Float64, Value::Float64(_))
863        | (FieldKind::Int, Value::Int(_))
864        | (FieldKind::Int128, Value::Int128(_))
865        | (FieldKind::IntBig, Value::IntBig(_))
866        | (FieldKind::Principal, Value::Principal(_))
867        | (FieldKind::Subaccount, Value::Subaccount(_))
868        | (FieldKind::Text, Value::Text(_))
869        | (FieldKind::Timestamp, Value::Timestamp(_))
870        | (FieldKind::Uint, Value::Uint(_))
871        | (FieldKind::Uint128, Value::Uint128(_))
872        | (FieldKind::UintBig, Value::UintBig(_))
873        | (FieldKind::Ulid, Value::Ulid(_))
874        | (FieldKind::Unit, Value::Unit)
875        | (FieldKind::Structured { .. }, Value::List(_) | Value::Map(_)) => true,
876        (FieldKind::Relation { key_kind, .. }, value) => {
877            storage_value_matches_field_kind(*key_kind, value)
878        }
879        (FieldKind::List(inner) | FieldKind::Set(inner), Value::List(items)) => items
880            .iter()
881            .all(|item| storage_value_matches_field_kind(*inner, item)),
882        (FieldKind::Map { key, value }, Value::Map(entries)) => {
883            if Value::validate_map_entries(entries.as_slice()).is_err() {
884                return false;
885            }
886
887            entries.iter().all(|(entry_key, entry_value)| {
888                storage_value_matches_field_kind(*key, entry_key)
889                    && storage_value_matches_field_kind(*value, entry_value)
890            })
891        }
892        _ => false,
893    }
894}
895
896// Enforce fixed decimal scales through nested collection/map shapes before a
897// field-level patch value is persisted.
898#[allow(dead_code)]
899fn ensure_decimal_scale_matches(
900    field_name: &str,
901    kind: FieldKind,
902    value: &Value,
903) -> Result<(), InternalError> {
904    if matches!(value, Value::Null) {
905        return Ok(());
906    }
907
908    match (kind, value) {
909        (FieldKind::Decimal { scale }, Value::Decimal(decimal)) => {
910            if decimal.scale() != scale {
911                return Err(InternalError::persisted_row_field_encode_failed(
912                    field_name,
913                    format!(
914                        "decimal scale mismatch: expected {scale}, found {}",
915                        decimal.scale()
916                    ),
917                ));
918            }
919
920            Ok(())
921        }
922        (FieldKind::Relation { key_kind, .. }, value) => {
923            ensure_decimal_scale_matches(field_name, *key_kind, value)
924        }
925        (FieldKind::List(inner) | FieldKind::Set(inner), Value::List(items)) => {
926            for item in items {
927                ensure_decimal_scale_matches(field_name, *inner, item)?;
928            }
929
930            Ok(())
931        }
932        (
933            FieldKind::Map {
934                key,
935                value: map_value,
936            },
937            Value::Map(entries),
938        ) => {
939            for (entry_key, entry_value) in entries {
940                ensure_decimal_scale_matches(field_name, *key, entry_key)?;
941                ensure_decimal_scale_matches(field_name, *map_value, entry_value)?;
942            }
943
944            Ok(())
945        }
946        _ => Ok(()),
947    }
948}
949
950// Enforce the canonical persisted ordering rules for set/map shapes before one
951// field-level patch value becomes row bytes.
952#[allow(dead_code)]
953fn ensure_value_is_deterministic_for_storage(
954    field_name: &str,
955    kind: FieldKind,
956    value: &Value,
957) -> Result<(), InternalError> {
958    match (kind, value) {
959        (FieldKind::Set(_), Value::List(items)) => {
960            for pair in items.windows(2) {
961                let [left, right] = pair else {
962                    continue;
963                };
964                if Value::canonical_cmp(left, right) != Ordering::Less {
965                    return Err(InternalError::persisted_row_field_encode_failed(
966                        field_name,
967                        "set payload must already be canonical and deduplicated",
968                    ));
969                }
970            }
971
972            Ok(())
973        }
974        (FieldKind::Map { .. }, Value::Map(entries)) => {
975            Value::validate_map_entries(entries.as_slice())
976                .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))?;
977
978            if !Value::map_entries_are_strictly_canonical(entries.as_slice()) {
979                return Err(InternalError::persisted_row_field_encode_failed(
980                    field_name,
981                    "map payload must already be canonical and deduplicated",
982                ));
983            }
984
985            Ok(())
986        }
987        _ => Ok(()),
988    }
989}
990
991// Materialize the last-write-wins serialized patch view indexed by stable slot.
992fn serialized_patch_payload_by_slot<'a>(
993    model: &'static EntityModel,
994    patch: &'a SerializedUpdatePatch,
995) -> Result<Vec<Option<&'a [u8]>>, InternalError> {
996    let mut payloads = vec![None; model.fields().len()];
997
998    for entry in patch.entries() {
999        let slot = entry.slot().index();
1000        field_model_for_slot(model, slot)?;
1001        payloads[slot] = Some(entry.payload());
1002    }
1003
1004    Ok(payloads)
1005}
1006
1007// Encode one `ByKind` field payload into the raw CBOR shape expected by the
1008// structural field decoder.
1009fn encode_structural_field_bytes_by_kind(
1010    kind: FieldKind,
1011    value: &Value,
1012    field_name: &str,
1013) -> Result<Vec<u8>, InternalError> {
1014    let cbor_value = encode_structural_field_cbor_by_kind(kind, value, field_name)?;
1015
1016    serialize(&cbor_value)
1017        .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))
1018}
1019
1020// Encode one `ByKind` field payload into its raw CBOR value form.
1021fn encode_structural_field_cbor_by_kind(
1022    kind: FieldKind,
1023    value: &Value,
1024    field_name: &str,
1025) -> Result<CborValue, InternalError> {
1026    match (kind, value) {
1027        (_, Value::Null) => Ok(CborValue::Null),
1028        (FieldKind::Blob, Value::Blob(value)) => Ok(CborValue::Bytes(value.clone())),
1029        (FieldKind::Bool, Value::Bool(value)) => Ok(CborValue::Bool(*value)),
1030        (FieldKind::Text, Value::Text(value)) => Ok(CborValue::Text(value.clone())),
1031        (FieldKind::Int, Value::Int(value)) => Ok(CborValue::Integer(i128::from(*value))),
1032        (FieldKind::Uint, Value::Uint(value)) => Ok(CborValue::Integer(i128::from(*value))),
1033        (FieldKind::Float32, Value::Float32(value)) => to_cbor_value(value)
1034            .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1035        (FieldKind::Float64, Value::Float64(value)) => to_cbor_value(value)
1036            .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1037        (FieldKind::Int128, Value::Int128(value)) => to_cbor_value(value)
1038            .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1039        (FieldKind::Uint128, Value::Uint128(value)) => to_cbor_value(value)
1040            .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err)),
1041        (FieldKind::Ulid, Value::Ulid(value)) => Ok(CborValue::Text(value.to_string())),
1042        (FieldKind::Account, Value::Account(value)) => encode_leaf_cbor_value(value, field_name),
1043        (FieldKind::Date, Value::Date(value)) => encode_leaf_cbor_value(value, field_name),
1044        (FieldKind::Decimal { .. }, Value::Decimal(value)) => {
1045            encode_leaf_cbor_value(value, field_name)
1046        }
1047        (FieldKind::Duration, Value::Duration(value)) => encode_leaf_cbor_value(value, field_name),
1048        (FieldKind::IntBig, Value::IntBig(value)) => encode_leaf_cbor_value(value, field_name),
1049        (FieldKind::Principal, Value::Principal(value)) => {
1050            encode_leaf_cbor_value(value, field_name)
1051        }
1052        (FieldKind::Subaccount, Value::Subaccount(value)) => {
1053            encode_leaf_cbor_value(value, field_name)
1054        }
1055        (FieldKind::Timestamp, Value::Timestamp(value)) => {
1056            encode_leaf_cbor_value(value, field_name)
1057        }
1058        (FieldKind::UintBig, Value::UintBig(value)) => encode_leaf_cbor_value(value, field_name),
1059        (FieldKind::Unit, Value::Unit) => encode_leaf_cbor_value(&(), field_name),
1060        (FieldKind::Relation { key_kind, .. }, value) => {
1061            encode_structural_field_cbor_by_kind(*key_kind, value, field_name)
1062        }
1063        (FieldKind::List(inner) | FieldKind::Set(inner), Value::List(items)) => {
1064            Ok(CborValue::Array(
1065                items
1066                    .iter()
1067                    .map(|item| encode_structural_field_cbor_by_kind(*inner, item, field_name))
1068                    .collect::<Result<Vec<_>, _>>()?,
1069            ))
1070        }
1071        (FieldKind::Map { key, value }, Value::Map(entries)) => {
1072            let mut encoded = BTreeMap::new();
1073            for (entry_key, entry_value) in entries {
1074                encoded.insert(
1075                    encode_structural_field_cbor_by_kind(*key, entry_key, field_name)?,
1076                    encode_structural_field_cbor_by_kind(*value, entry_value, field_name)?,
1077                );
1078            }
1079
1080            Ok(CborValue::Map(encoded))
1081        }
1082        (FieldKind::Enum { path, variants }, Value::Enum(value)) => {
1083            encode_enum_cbor_value(path, variants, value, field_name)
1084        }
1085        (FieldKind::Structured { .. }, _) => Err(InternalError::persisted_row_field_encode_failed(
1086            field_name,
1087            "structured ByKind field encoding is unsupported",
1088        )),
1089        _ => Err(InternalError::persisted_row_field_encode_failed(
1090            field_name,
1091            format!("field kind {kind:?} does not accept runtime value {value:?}"),
1092        )),
1093    }
1094}
1095
1096// Encode one typed leaf wrapper into its raw CBOR value form.
1097fn encode_leaf_cbor_value<T>(value: &T, field_name: &str) -> Result<CborValue, InternalError>
1098where
1099    T: serde::Serialize,
1100{
1101    to_cbor_value(value)
1102        .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))
1103}
1104
1105// Encode one enum field using the same unit-vs-one-entry-map envelope expected
1106// by structural enum decode.
1107fn encode_enum_cbor_value(
1108    path: &'static str,
1109    variants: &'static [crate::model::field::EnumVariantModel],
1110    value: &ValueEnum,
1111    field_name: &str,
1112) -> Result<CborValue, InternalError> {
1113    if let Some(actual_path) = value.path()
1114        && actual_path != path
1115    {
1116        return Err(InternalError::persisted_row_field_encode_failed(
1117            field_name,
1118            format!("enum path mismatch: expected '{path}', found '{actual_path}'"),
1119        ));
1120    }
1121
1122    let Some(payload) = value.payload() else {
1123        return Ok(CborValue::Text(value.variant().to_string()));
1124    };
1125
1126    let Some(variant_model) = variants.iter().find(|item| item.ident() == value.variant()) else {
1127        return Err(InternalError::persisted_row_field_encode_failed(
1128            field_name,
1129            format!(
1130                "unknown enum variant '{}' for path '{path}'",
1131                value.variant()
1132            ),
1133        ));
1134    };
1135    let Some(payload_kind) = variant_model.payload_kind() else {
1136        return Err(InternalError::persisted_row_field_encode_failed(
1137            field_name,
1138            format!(
1139                "enum variant '{}' does not accept a payload",
1140                value.variant()
1141            ),
1142        ));
1143    };
1144
1145    let payload_value = match variant_model.payload_storage_decode() {
1146        FieldStorageDecode::ByKind => {
1147            encode_structural_field_cbor_by_kind(*payload_kind, payload, field_name)?
1148        }
1149        FieldStorageDecode::Value => to_cbor_value(payload)
1150            .map_err(|err| InternalError::persisted_row_field_encode_failed(field_name, err))?,
1151    };
1152
1153    let mut encoded = BTreeMap::new();
1154    encoded.insert(CborValue::Text(value.variant().to_string()), payload_value);
1155
1156    Ok(CborValue::Map(encoded))
1157}
1158
1159// Resolve one field model entry by stable slot index.
1160fn field_model_for_slot(
1161    model: &'static EntityModel,
1162    slot: usize,
1163) -> Result<&'static FieldModel, InternalError> {
1164    model
1165        .fields()
1166        .get(slot)
1167        .ok_or_else(|| InternalError::persisted_row_slot_lookup_out_of_bounds(model.path(), slot))
1168}
1169
1170///
1171/// SlotBufferWriter
1172///
1173/// SlotBufferWriter captures one dense canonical row worth of slot payloads
1174/// before they are encoded into the canonical slot container.
1175///
1176
1177pub(in crate::db) struct SlotBufferWriter {
1178    model: &'static EntityModel,
1179    slots: Vec<SlotBufferSlot>,
1180}
1181
1182impl SlotBufferWriter {
1183    /// Build one empty slot buffer for one entity model.
1184    pub(in crate::db) fn for_model(model: &'static EntityModel) -> Self {
1185        Self {
1186            model,
1187            slots: vec![SlotBufferSlot::Missing; model.fields().len()],
1188        }
1189    }
1190
1191    /// Encode the buffered slots into the canonical row payload.
1192    pub(in crate::db) fn finish(self) -> Result<Vec<u8>, InternalError> {
1193        let slot_count = self.slots.len();
1194        let mut payload_bytes = Vec::new();
1195        let mut slot_table = Vec::with_capacity(slot_count);
1196
1197        // Phase 1: require one payload for every declared slot before the row
1198        // can cross the canonical persisted-row boundary.
1199        for (slot, slot_payload) in self.slots.into_iter().enumerate() {
1200            match slot_payload {
1201                SlotBufferSlot::Set(bytes) => {
1202                    let start = u32::try_from(payload_bytes.len()).map_err(|_| {
1203                        InternalError::persisted_row_encode_failed(
1204                            "slot payload start exceeds u32 range",
1205                        )
1206                    })?;
1207                    let len = u32::try_from(bytes.len()).map_err(|_| {
1208                        InternalError::persisted_row_encode_failed(
1209                            "slot payload length exceeds u32 range",
1210                        )
1211                    })?;
1212                    payload_bytes.extend_from_slice(&bytes);
1213                    slot_table.push((start, len));
1214                }
1215                SlotBufferSlot::Missing => {
1216                    return Err(InternalError::persisted_row_encode_failed(format!(
1217                        "slot buffer writer did not emit slot {slot} for entity '{}'",
1218                        self.model.path()
1219                    )));
1220                }
1221            }
1222        }
1223
1224        // Phase 2: flatten the slot table plus payload bytes into the canonical row image.
1225        encode_slot_payload_from_parts(slot_count, slot_table.as_slice(), payload_bytes.as_slice())
1226    }
1227}
1228
1229impl SlotWriter for SlotBufferWriter {
1230    fn write_slot(&mut self, slot: usize, payload: Option<&[u8]>) -> Result<(), InternalError> {
1231        let entry = slot_cell_mut(self.slots.as_mut_slice(), slot)?;
1232        let payload = required_slot_payload_bytes(self.model, "slot buffer writer", slot, payload)?;
1233        *entry = SlotBufferSlot::Set(payload.to_vec());
1234
1235        Ok(())
1236    }
1237}
1238
1239///
1240/// SlotBufferSlot
1241///
1242/// SlotBufferSlot tracks whether one canonical row encoder has emitted a
1243/// payload for every declared slot before flattening the row payload.
1244///
1245
1246#[derive(Clone, Debug, Eq, PartialEq)]
1247enum SlotBufferSlot {
1248    Missing,
1249    Set(Vec<u8>),
1250}
1251
1252///
1253/// SerializedPatchWriter
1254///
1255/// SerializedPatchWriter
1256///
1257/// SerializedPatchWriter captures a dense typed entity slot image into the
1258/// serialized patch artifact used by `0.64` mutation staging.
1259/// Unlike `SlotBufferWriter`, this writer does not flatten into one row payload;
1260/// it preserves slot-level ownership so later stages can replay the row through
1261/// the structural patch boundary.
1262///
1263
1264struct SerializedPatchWriter {
1265    model: &'static EntityModel,
1266    slots: Vec<PatchWriterSlot>,
1267}
1268
1269impl SerializedPatchWriter {
1270    /// Build one empty serialized patch writer for one entity model.
1271    fn for_model(model: &'static EntityModel) -> Self {
1272        Self {
1273            model,
1274            slots: vec![PatchWriterSlot::Missing; model.fields().len()],
1275        }
1276    }
1277
1278    /// Materialize one dense serialized patch, erroring if the writer failed
1279    /// to emit any declared slot.
1280    fn finish_complete(self) -> Result<SerializedUpdatePatch, InternalError> {
1281        let mut entries = Vec::with_capacity(self.slots.len());
1282
1283        // Phase 1: require a complete slot image so typed save/update staging
1284        // stays equivalent to the existing full-row encoder.
1285        for (slot, payload) in self.slots.into_iter().enumerate() {
1286            let field_slot = FieldSlot::from_index(self.model, slot)?;
1287            let serialized = match payload {
1288                PatchWriterSlot::Set(payload) => SerializedFieldUpdate::new(field_slot, payload),
1289                PatchWriterSlot::Missing => {
1290                    return Err(InternalError::persisted_row_encode_failed(format!(
1291                        "serialized patch writer did not emit slot {slot} for entity '{}'",
1292                        self.model.path()
1293                    )));
1294                }
1295            };
1296            entries.push(serialized);
1297        }
1298
1299        Ok(SerializedUpdatePatch::new(entries))
1300    }
1301}
1302
1303impl SlotWriter for SerializedPatchWriter {
1304    fn write_slot(&mut self, slot: usize, payload: Option<&[u8]>) -> Result<(), InternalError> {
1305        let entry = slot_cell_mut(self.slots.as_mut_slice(), slot)?;
1306        let payload =
1307            required_slot_payload_bytes(self.model, "serialized patch writer", slot, payload)?;
1308        *entry = PatchWriterSlot::Set(payload.to_vec());
1309
1310        Ok(())
1311    }
1312}
1313
1314///
1315/// PatchWriterSlot
1316///
1317/// PatchWriterSlot
1318///
1319/// PatchWriterSlot tracks whether one dense slot-image writer has emitted a
1320/// payload or failed to visit the slot at all.
1321/// That lets the typed save/update bridge reject incomplete writers instead of
1322/// silently leaving stale bytes in the baseline row.
1323///
1324
1325#[derive(Clone, Debug, Eq, PartialEq)]
1326enum PatchWriterSlot {
1327    Missing,
1328    Set(Vec<u8>),
1329}
1330
1331///
1332/// StructuralSlotReader
1333///
1334/// StructuralSlotReader adapts the current persisted-row bytes into the
1335/// canonical slot-reader seam.
1336/// It validates row shape and fully decodes every declared field before any
1337/// consumer can observe the row, then keeps those decoded values cached so
1338/// later index/predicate reads do not re-run field decoders.
1339///
1340
1341pub(in crate::db) struct StructuralSlotReader<'a> {
1342    model: &'static EntityModel,
1343    field_bytes: StructuralRowFieldBytes<'a>,
1344    cached_values: Vec<CachedSlotValue>,
1345}
1346
1347impl<'a> StructuralSlotReader<'a> {
1348    /// Build one slot reader over one persisted row using the current structural row scanner.
1349    pub(in crate::db) fn from_raw_row(
1350        raw_row: &'a RawRow,
1351        model: &'static EntityModel,
1352    ) -> Result<Self, InternalError> {
1353        let field_bytes = StructuralRowFieldBytes::from_raw_row(raw_row, model)
1354            .map_err(StructuralRowDecodeError::into_internal_error)?;
1355        let cached_values = std::iter::repeat_with(|| CachedSlotValue::Pending)
1356            .take(model.fields().len())
1357            .collect();
1358        let mut reader = Self {
1359            model,
1360            field_bytes,
1361            cached_values,
1362        };
1363
1364        // Phase 1: force every declared slot through the field decode contract
1365        // once so malformed persisted bytes cannot stay latent behind later
1366        // hot-path reads.
1367        reader.decode_all_declared_slots()?;
1368
1369        Ok(reader)
1370    }
1371
1372    /// Validate the decoded primary-key slot against the authoritative row key.
1373    pub(in crate::db) fn validate_storage_key(
1374        &self,
1375        data_key: &DataKey,
1376    ) -> Result<(), InternalError> {
1377        self.validate_storage_key_value(data_key.storage_key())
1378    }
1379
1380    // Validate the decoded primary-key slot against one authoritative storage
1381    // key without rebuilding a full `DataKey` wrapper at the call site.
1382    pub(in crate::db) fn validate_storage_key_value(
1383        &self,
1384        expected_key: StorageKey,
1385    ) -> Result<(), InternalError> {
1386        let Some(primary_key_slot) = resolve_primary_key_slot(self.model) else {
1387            return Err(InternalError::persisted_row_primary_key_field_missing(
1388                self.model.path(),
1389            ));
1390        };
1391        let field = self.field_model(primary_key_slot)?;
1392        let decoded_key = match self.get_scalar(primary_key_slot)? {
1393            Some(ScalarSlotValueRef::Null) => None,
1394            Some(ScalarSlotValueRef::Value(value)) => storage_key_from_scalar_ref(value),
1395            None => Some(
1396                decode_storage_key_field_bytes(
1397                    self.required_field_bytes(primary_key_slot, field.name())?,
1398                    field.kind,
1399                )
1400                .map_err(|err| {
1401                    InternalError::persisted_row_primary_key_not_storage_encodable(
1402                        expected_key,
1403                        err,
1404                    )
1405                })?,
1406            ),
1407        };
1408        let Some(decoded_key) = decoded_key else {
1409            return Err(InternalError::persisted_row_primary_key_slot_missing(
1410                expected_key,
1411            ));
1412        };
1413
1414        if decoded_key != expected_key {
1415            return Err(InternalError::persisted_row_key_mismatch(
1416                expected_key,
1417                decoded_key,
1418            ));
1419        }
1420
1421        Ok(())
1422    }
1423
1424    // Resolve one field model entry by stable slot index.
1425    fn field_model(&self, slot: usize) -> Result<&FieldModel, InternalError> {
1426        field_model_for_slot(self.model, slot)
1427    }
1428
1429    // Decode every declared slot exactly once at the structural row boundary so
1430    // later consumers inherit one globally enforced canonical-row contract.
1431    fn decode_all_declared_slots(&mut self) -> Result<(), InternalError> {
1432        for slot in 0..self.model.fields().len() {
1433            self.decode_slot_into_cache(slot)?;
1434        }
1435
1436        Ok(())
1437    }
1438
1439    // Decode one declared slot directly into the owned cache without cloning
1440    // the decoded value back out through `get_value` when the caller only
1441    // needs eager validation of canonical row shape.
1442    fn decode_slot_into_cache(&mut self, slot: usize) -> Result<(), InternalError> {
1443        if matches!(
1444            self.cached_values.get(slot),
1445            Some(CachedSlotValue::Decoded(_))
1446        ) {
1447            return Ok(());
1448        }
1449
1450        let field = self.field_model(slot)?;
1451        let value =
1452            decode_slot_value_for_field(field, self.required_field_bytes(slot, field.name())?)?;
1453        self.cached_values[slot] = CachedSlotValue::Decoded(value);
1454
1455        Ok(())
1456    }
1457
1458    // Consume the structural slot reader into one slot-indexed decoded-value
1459    // vector once the canonical row boundary has already forced every slot
1460    // through decode. This lets hot row-decode callers reuse that validated
1461    // cache instead of decoding the same declared fields a second time.
1462    pub(in crate::db) fn into_decoded_values(self) -> Result<Vec<Option<Value>>, InternalError> {
1463        let mut values = Vec::with_capacity(self.cached_values.len());
1464
1465        for (slot, cached) in self.cached_values.into_iter().enumerate() {
1466            match cached {
1467                CachedSlotValue::Decoded(value) => values.push(Some(value)),
1468                CachedSlotValue::Pending => {
1469                    return Err(InternalError::persisted_row_decode_failed(format!(
1470                        "structural slot cache was not fully decoded before consumption: slot={slot}",
1471                    )));
1472                }
1473            }
1474        }
1475
1476        Ok(values)
1477    }
1478
1479    // Borrow one already-decoded slot value from the eager structural cache so
1480    // callers that only need the canonical field value do not re-enter raw
1481    // field-byte decoding after `from_raw_row` has already validated the row.
1482    fn required_cached_value(&self, slot: usize) -> Result<&Value, InternalError> {
1483        let cached = self.cached_values.get(slot).ok_or_else(|| {
1484            InternalError::persisted_row_slot_cache_lookup_out_of_bounds(self.model.path(), slot)
1485        })?;
1486
1487        match cached {
1488            CachedSlotValue::Decoded(value) => Ok(value),
1489            CachedSlotValue::Pending => Err(InternalError::persisted_row_decode_failed(format!(
1490                "structural slot cache missing decoded value after eager decode: slot={slot}",
1491            ))),
1492        }
1493    }
1494
1495    // Borrow one declared slot payload, treating absence as a persisted-row
1496    // invariant violation instead of a normal structural branch.
1497    pub(in crate::db) fn required_field_bytes(
1498        &self,
1499        slot: usize,
1500        field_name: &str,
1501    ) -> Result<&[u8], InternalError> {
1502        self.field_bytes
1503            .field(slot)
1504            .ok_or_else(|| InternalError::persisted_row_declared_field_missing(field_name))
1505    }
1506}
1507
1508// Convert one scalar slot fast-path value into its storage-key form when the
1509// field kind is storage-key-compatible.
1510const fn storage_key_from_scalar_ref(value: ScalarValueRef<'_>) -> Option<StorageKey> {
1511    match value {
1512        ScalarValueRef::Int(value) => Some(StorageKey::Int(value)),
1513        ScalarValueRef::Principal(value) => Some(StorageKey::Principal(value)),
1514        ScalarValueRef::Subaccount(value) => Some(StorageKey::Subaccount(value)),
1515        ScalarValueRef::Timestamp(value) => Some(StorageKey::Timestamp(value)),
1516        ScalarValueRef::Uint(value) => Some(StorageKey::Uint(value)),
1517        ScalarValueRef::Ulid(value) => Some(StorageKey::Ulid(value)),
1518        ScalarValueRef::Unit => Some(StorageKey::Unit),
1519        _ => None,
1520    }
1521}
1522
1523// Borrow one scalar-slot view directly from one already-decoded runtime value.
1524fn scalar_slot_value_ref_from_cached_value(
1525    value: &Value,
1526) -> Result<ScalarSlotValueRef<'_>, InternalError> {
1527    let scalar = match value {
1528        Value::Null => return Ok(ScalarSlotValueRef::Null),
1529        Value::Blob(value) => ScalarValueRef::Blob(value.as_slice()),
1530        Value::Bool(value) => ScalarValueRef::Bool(*value),
1531        Value::Date(value) => ScalarValueRef::Date(*value),
1532        Value::Duration(value) => ScalarValueRef::Duration(*value),
1533        Value::Float32(value) => ScalarValueRef::Float32(*value),
1534        Value::Float64(value) => ScalarValueRef::Float64(*value),
1535        Value::Int(value) => ScalarValueRef::Int(*value),
1536        Value::Principal(value) => ScalarValueRef::Principal(*value),
1537        Value::Subaccount(value) => ScalarValueRef::Subaccount(*value),
1538        Value::Text(value) => ScalarValueRef::Text(value.as_str()),
1539        Value::Timestamp(value) => ScalarValueRef::Timestamp(*value),
1540        Value::Uint(value) => ScalarValueRef::Uint(*value),
1541        Value::Ulid(value) => ScalarValueRef::Ulid(*value),
1542        Value::Unit => ScalarValueRef::Unit,
1543        _ => {
1544            return Err(InternalError::persisted_row_decode_failed(format!(
1545                "cached structural scalar slot cannot borrow non-scalar value variant: {value:?}",
1546            )));
1547        }
1548    };
1549
1550    Ok(ScalarSlotValueRef::Value(scalar))
1551}
1552
1553impl SlotReader for StructuralSlotReader<'_> {
1554    fn model(&self) -> &'static EntityModel {
1555        self.model
1556    }
1557
1558    fn has(&self, slot: usize) -> bool {
1559        self.field_bytes.field(slot).is_some()
1560    }
1561
1562    fn get_bytes(&self, slot: usize) -> Option<&[u8]> {
1563        self.field_bytes.field(slot)
1564    }
1565
1566    fn get_scalar(&self, slot: usize) -> Result<Option<ScalarSlotValueRef<'_>>, InternalError> {
1567        let field = self.field_model(slot)?;
1568
1569        match field.leaf_codec() {
1570            LeafCodec::Scalar(_codec) => {
1571                scalar_slot_value_ref_from_cached_value(self.required_cached_value(slot)?).map(Some)
1572            }
1573            LeafCodec::CborFallback => Ok(None),
1574        }
1575    }
1576
1577    fn get_value(&mut self, slot: usize) -> Result<Option<Value>, InternalError> {
1578        self.decode_slot_into_cache(slot)?;
1579        Ok(Some(self.required_cached_value(slot)?.clone()))
1580    }
1581}
1582
1583impl CanonicalSlotReader for StructuralSlotReader<'_> {
1584    fn required_value_by_contract(&self, slot: usize) -> Result<Value, InternalError> {
1585        Ok(self.required_cached_value(slot)?.clone())
1586    }
1587
1588    fn required_value_by_contract_cow(&self, slot: usize) -> Result<Cow<'_, Value>, InternalError> {
1589        Ok(Cow::Borrowed(self.required_cached_value(slot)?))
1590    }
1591}
1592
1593///
1594/// CachedSlotValue
1595///
1596/// CachedSlotValue tracks whether one slot has already been decoded during the
1597/// current structural row access pass.
1598///
1599
1600enum CachedSlotValue {
1601    Pending,
1602    Decoded(Value),
1603}
1604
1605///
1606/// TESTS
1607///
1608
1609#[cfg(test)]
1610mod tests {
1611    use super::{
1612        FieldSlot, ScalarSlotValueRef, ScalarValueRef, SerializedFieldUpdate,
1613        SerializedPatchWriter, SerializedUpdatePatch, SlotBufferWriter, SlotReader, SlotWriter,
1614        UpdatePatch, apply_serialized_update_patch_to_raw_row, apply_update_patch_to_raw_row,
1615        decode_persisted_custom_many_slot_payload, decode_persisted_custom_slot_payload,
1616        decode_persisted_non_null_slot_payload, decode_persisted_option_slot_payload,
1617        decode_persisted_slot_payload, decode_slot_value_by_contract, decode_slot_value_from_bytes,
1618        encode_persisted_custom_many_slot_payload, encode_persisted_custom_slot_payload,
1619        encode_scalar_slot_value, encode_slot_payload_from_parts, encode_slot_value_from_value,
1620        serialize_entity_slots_as_update_patch, serialize_update_patch_fields,
1621    };
1622    use crate::{
1623        db::{
1624            codec::serialize_row_payload,
1625            data::{RawRow, StructuralSlotReader, decode_structural_value_storage_bytes},
1626        },
1627        error::InternalError,
1628        model::{
1629            EntityModel,
1630            field::{EnumVariantModel, FieldKind, FieldModel, FieldStorageDecode},
1631        },
1632        serialize::serialize,
1633        testing::SIMPLE_ENTITY_TAG,
1634        traits::{EntitySchema, FieldValue},
1635        types::{
1636            Account, Date, Decimal, Duration, Float32, Float64, Int, Int128, Nat, Nat128,
1637            Principal, Subaccount, Timestamp, Ulid,
1638        },
1639        value::{Value, ValueEnum},
1640    };
1641    use icydb_derive::{FieldProjection, PersistedRow};
1642    use serde::{Deserialize, Serialize};
1643
1644    crate::test_canister! {
1645        ident = PersistedRowPatchBridgeCanister,
1646        commit_memory_id = crate::testing::test_commit_memory_id(),
1647    }
1648
1649    crate::test_store! {
1650        ident = PersistedRowPatchBridgeStore,
1651        canister = PersistedRowPatchBridgeCanister,
1652    }
1653
1654    ///
1655    /// PersistedRowPatchBridgeEntity
1656    ///
1657    /// PersistedRowPatchBridgeEntity
1658    ///
1659    /// PersistedRowPatchBridgeEntity is the smallest derive-owned entity used
1660    /// to validate the typed-entity -> serialized-patch bridge.
1661    /// It lets the persisted-row tests exercise the same dense slot writer the
1662    /// save/update path now uses.
1663    ///
1664
1665    #[derive(
1666        Clone, Debug, Default, Deserialize, FieldProjection, PartialEq, PersistedRow, Serialize,
1667    )]
1668    struct PersistedRowPatchBridgeEntity {
1669        id: crate::types::Ulid,
1670        name: String,
1671    }
1672
1673    #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
1674    struct PersistedRowProfileValue {
1675        bio: String,
1676    }
1677
1678    impl FieldValue for PersistedRowProfileValue {
1679        fn kind() -> crate::traits::FieldValueKind {
1680            crate::traits::FieldValueKind::Structured { queryable: false }
1681        }
1682
1683        fn to_value(&self) -> Value {
1684            Value::from_map(vec![(
1685                Value::Text("bio".to_string()),
1686                Value::Text(self.bio.clone()),
1687            )])
1688            .expect("profile test value should encode as canonical map")
1689        }
1690
1691        fn from_value(value: &Value) -> Option<Self> {
1692            let Value::Map(entries) = value else {
1693                return None;
1694            };
1695            let normalized = Value::normalize_map_entries(entries.clone()).ok()?;
1696            let bio = normalized
1697                .iter()
1698                .find_map(|(entry_key, entry_value)| match entry_key {
1699                    Value::Text(entry_key) if entry_key == "bio" => match entry_value {
1700                        Value::Text(bio) => Some(bio.clone()),
1701                        _ => None,
1702                    },
1703                    _ => None,
1704                })?;
1705
1706            if normalized.len() != 1 {
1707                return None;
1708            }
1709
1710            Some(Self { bio })
1711        }
1712    }
1713
1714    crate::test_entity_schema! {
1715        ident = PersistedRowPatchBridgeEntity,
1716        id = crate::types::Ulid,
1717        id_field = id,
1718        entity_name = "PersistedRowPatchBridgeEntity",
1719        entity_tag = SIMPLE_ENTITY_TAG,
1720        pk_index = 0,
1721        fields = [
1722            ("id", FieldKind::Ulid),
1723            ("name", FieldKind::Text),
1724        ],
1725        indexes = [],
1726        store = PersistedRowPatchBridgeStore,
1727        canister = PersistedRowPatchBridgeCanister,
1728    }
1729
1730    static STATE_VARIANTS: &[EnumVariantModel] = &[EnumVariantModel::new(
1731        "Loaded",
1732        Some(&FieldKind::Uint),
1733        FieldStorageDecode::ByKind,
1734    )];
1735    static FIELD_MODELS: [FieldModel; 2] = [
1736        FieldModel::new("name", FieldKind::Text),
1737        FieldModel::new_with_storage_decode("payload", FieldKind::Text, FieldStorageDecode::Value),
1738    ];
1739    static LIST_FIELD_MODELS: [FieldModel; 1] =
1740        [FieldModel::new("tags", FieldKind::List(&FieldKind::Text))];
1741    static MAP_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new(
1742        "props",
1743        FieldKind::Map {
1744            key: &FieldKind::Text,
1745            value: &FieldKind::Uint,
1746        },
1747    )];
1748    static ENUM_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new(
1749        "state",
1750        FieldKind::Enum {
1751            path: "tests::State",
1752            variants: STATE_VARIANTS,
1753        },
1754    )];
1755    static ACCOUNT_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new("owner", FieldKind::Account)];
1756    static REQUIRED_STRUCTURED_FIELD_MODELS: [FieldModel; 1] = [FieldModel::new(
1757        "profile",
1758        FieldKind::Structured { queryable: false },
1759    )];
1760    static OPTIONAL_STRUCTURED_FIELD_MODELS: [FieldModel; 1] =
1761        [FieldModel::new_with_storage_decode_and_nullability(
1762            "profile",
1763            FieldKind::Structured { queryable: false },
1764            FieldStorageDecode::ByKind,
1765            true,
1766        )];
1767    static VALUE_STORAGE_STRUCTURED_FIELD_MODELS: [FieldModel; 1] =
1768        [FieldModel::new_with_storage_decode(
1769            "manifest",
1770            FieldKind::Structured { queryable: false },
1771            FieldStorageDecode::Value,
1772        )];
1773    static STRUCTURED_MAP_VALUE_KIND: FieldKind = FieldKind::Structured { queryable: false };
1774    static STRUCTURED_MAP_VALUE_STORAGE_FIELD_MODELS: [FieldModel; 1] =
1775        [FieldModel::new_with_storage_decode(
1776            "projects",
1777            FieldKind::Map {
1778                key: &FieldKind::Principal,
1779                value: &STRUCTURED_MAP_VALUE_KIND,
1780            },
1781            FieldStorageDecode::Value,
1782        )];
1783    static INDEX_MODELS: [&crate::model::index::IndexModel; 0] = [];
1784    static TEST_MODEL: EntityModel = EntityModel::new(
1785        "tests::PersistedRowFieldCodecEntity",
1786        "persisted_row_field_codec_entity",
1787        &FIELD_MODELS[0],
1788        &FIELD_MODELS,
1789        &INDEX_MODELS,
1790    );
1791    static LIST_MODEL: EntityModel = EntityModel::new(
1792        "tests::PersistedRowListFieldCodecEntity",
1793        "persisted_row_list_field_codec_entity",
1794        &LIST_FIELD_MODELS[0],
1795        &LIST_FIELD_MODELS,
1796        &INDEX_MODELS,
1797    );
1798    static MAP_MODEL: EntityModel = EntityModel::new(
1799        "tests::PersistedRowMapFieldCodecEntity",
1800        "persisted_row_map_field_codec_entity",
1801        &MAP_FIELD_MODELS[0],
1802        &MAP_FIELD_MODELS,
1803        &INDEX_MODELS,
1804    );
1805    static ENUM_MODEL: EntityModel = EntityModel::new(
1806        "tests::PersistedRowEnumFieldCodecEntity",
1807        "persisted_row_enum_field_codec_entity",
1808        &ENUM_FIELD_MODELS[0],
1809        &ENUM_FIELD_MODELS,
1810        &INDEX_MODELS,
1811    );
1812    static ACCOUNT_MODEL: EntityModel = EntityModel::new(
1813        "tests::PersistedRowAccountFieldCodecEntity",
1814        "persisted_row_account_field_codec_entity",
1815        &ACCOUNT_FIELD_MODELS[0],
1816        &ACCOUNT_FIELD_MODELS,
1817        &INDEX_MODELS,
1818    );
1819    static REQUIRED_STRUCTURED_MODEL: EntityModel = EntityModel::new(
1820        "tests::PersistedRowRequiredStructuredFieldCodecEntity",
1821        "persisted_row_required_structured_field_codec_entity",
1822        &REQUIRED_STRUCTURED_FIELD_MODELS[0],
1823        &REQUIRED_STRUCTURED_FIELD_MODELS,
1824        &INDEX_MODELS,
1825    );
1826    static OPTIONAL_STRUCTURED_MODEL: EntityModel = EntityModel::new(
1827        "tests::PersistedRowOptionalStructuredFieldCodecEntity",
1828        "persisted_row_optional_structured_field_codec_entity",
1829        &OPTIONAL_STRUCTURED_FIELD_MODELS[0],
1830        &OPTIONAL_STRUCTURED_FIELD_MODELS,
1831        &INDEX_MODELS,
1832    );
1833    static VALUE_STORAGE_STRUCTURED_MODEL: EntityModel = EntityModel::new(
1834        "tests::PersistedRowValueStorageStructuredFieldCodecEntity",
1835        "persisted_row_value_storage_structured_field_codec_entity",
1836        &VALUE_STORAGE_STRUCTURED_FIELD_MODELS[0],
1837        &VALUE_STORAGE_STRUCTURED_FIELD_MODELS,
1838        &INDEX_MODELS,
1839    );
1840    static STRUCTURED_MAP_VALUE_STORAGE_MODEL: EntityModel = EntityModel::new(
1841        "tests::PersistedRowStructuredMapValueStorageEntity",
1842        "persisted_row_structured_map_value_storage_entity",
1843        &STRUCTURED_MAP_VALUE_STORAGE_FIELD_MODELS[0],
1844        &STRUCTURED_MAP_VALUE_STORAGE_FIELD_MODELS,
1845        &INDEX_MODELS,
1846    );
1847
1848    fn representative_value_storage_cases() -> Vec<Value> {
1849        let nested = Value::from_map(vec![
1850            (
1851                Value::Text("blob".to_string()),
1852                Value::Blob(vec![0x10, 0x20, 0x30]),
1853            ),
1854            (
1855                Value::Text("i128".to_string()),
1856                Value::Int128(Int128::from(-123i128)),
1857            ),
1858            (
1859                Value::Text("u128".to_string()),
1860                Value::Uint128(Nat128::from(456u128)),
1861            ),
1862            (
1863                Value::Text("enum".to_string()),
1864                Value::Enum(
1865                    ValueEnum::new("Loaded", Some("tests::PersistedRowManifest"))
1866                        .with_payload(Value::Blob(vec![0xAA, 0xBB])),
1867                ),
1868            ),
1869        ])
1870        .expect("nested value storage case should normalize");
1871
1872        vec![
1873            Value::Account(Account::dummy(7)),
1874            Value::Blob(vec![1u8, 2u8, 3u8]),
1875            Value::Bool(true),
1876            Value::Date(Date::new(2024, 1, 2)),
1877            Value::Decimal(Decimal::new(123, 2)),
1878            Value::Duration(Duration::from_secs(1)),
1879            Value::Enum(
1880                ValueEnum::new("Ready", Some("tests::PersistedRowState"))
1881                    .with_payload(nested.clone()),
1882            ),
1883            Value::Float32(Float32::try_new(1.25).expect("float32 sample should be finite")),
1884            Value::Float64(Float64::try_new(2.5).expect("float64 sample should be finite")),
1885            Value::Int(-7),
1886            Value::Int128(Int128::from(123i128)),
1887            Value::IntBig(Int::from(99i32)),
1888            Value::List(vec![
1889                Value::Blob(vec![0xCC, 0xDD]),
1890                Value::Text("nested".to_string()),
1891                nested.clone(),
1892            ]),
1893            nested,
1894            Value::Null,
1895            Value::Principal(Principal::dummy(9)),
1896            Value::Subaccount(Subaccount::new([7u8; 32])),
1897            Value::Text("example".to_string()),
1898            Value::Timestamp(Timestamp::from_secs(1)),
1899            Value::Uint(7),
1900            Value::Uint128(Nat128::from(9u128)),
1901            Value::UintBig(Nat::from(11u64)),
1902            Value::Ulid(Ulid::from_u128(42)),
1903            Value::Unit,
1904        ]
1905    }
1906
1907    fn representative_structured_value_storage_cases() -> Vec<Value> {
1908        let nested_map = Value::from_map(vec![
1909            (
1910                Value::Text("account".to_string()),
1911                Value::Account(Account::dummy(7)),
1912            ),
1913            (
1914                Value::Text("blob".to_string()),
1915                Value::Blob(vec![1u8, 2u8, 3u8]),
1916            ),
1917            (Value::Text("bool".to_string()), Value::Bool(true)),
1918            (
1919                Value::Text("date".to_string()),
1920                Value::Date(Date::new(2024, 1, 2)),
1921            ),
1922            (
1923                Value::Text("decimal".to_string()),
1924                Value::Decimal(Decimal::new(123, 2)),
1925            ),
1926            (
1927                Value::Text("duration".to_string()),
1928                Value::Duration(Duration::from_secs(1)),
1929            ),
1930            (
1931                Value::Text("enum".to_string()),
1932                Value::Enum(
1933                    ValueEnum::new("Loaded", Some("tests::PersistedRowManifest"))
1934                        .with_payload(Value::Blob(vec![0xAA, 0xBB])),
1935                ),
1936            ),
1937            (
1938                Value::Text("f32".to_string()),
1939                Value::Float32(Float32::try_new(1.25).expect("float32 sample should be finite")),
1940            ),
1941            (
1942                Value::Text("f64".to_string()),
1943                Value::Float64(Float64::try_new(2.5).expect("float64 sample should be finite")),
1944            ),
1945            (Value::Text("i64".to_string()), Value::Int(-7)),
1946            (
1947                Value::Text("i128".to_string()),
1948                Value::Int128(Int128::from(123i128)),
1949            ),
1950            (
1951                Value::Text("ibig".to_string()),
1952                Value::IntBig(Int::from(99i32)),
1953            ),
1954            (Value::Text("null".to_string()), Value::Null),
1955            (
1956                Value::Text("principal".to_string()),
1957                Value::Principal(Principal::dummy(9)),
1958            ),
1959            (
1960                Value::Text("subaccount".to_string()),
1961                Value::Subaccount(Subaccount::new([7u8; 32])),
1962            ),
1963            (
1964                Value::Text("text".to_string()),
1965                Value::Text("example".to_string()),
1966            ),
1967            (
1968                Value::Text("timestamp".to_string()),
1969                Value::Timestamp(Timestamp::from_secs(1)),
1970            ),
1971            (Value::Text("u64".to_string()), Value::Uint(7)),
1972            (
1973                Value::Text("u128".to_string()),
1974                Value::Uint128(Nat128::from(9u128)),
1975            ),
1976            (
1977                Value::Text("ubig".to_string()),
1978                Value::UintBig(Nat::from(11u64)),
1979            ),
1980            (
1981                Value::Text("ulid".to_string()),
1982                Value::Ulid(Ulid::from_u128(42)),
1983            ),
1984            (Value::Text("unit".to_string()), Value::Unit),
1985        ])
1986        .expect("structured value-storage map should normalize");
1987
1988        vec![
1989            nested_map.clone(),
1990            Value::List(vec![
1991                Value::Blob(vec![0xCC, 0xDD]),
1992                Value::Text("nested".to_string()),
1993                nested_map,
1994            ]),
1995        ]
1996    }
1997
1998    fn encode_slot_payload_allowing_missing_for_tests(
1999        model: &'static EntityModel,
2000        slots: &[Option<&[u8]>],
2001    ) -> Result<Vec<u8>, InternalError> {
2002        if slots.len() != model.fields().len() {
2003            return Err(InternalError::persisted_row_encode_failed(format!(
2004                "noncanonical slot payload test helper expected {} slots for entity '{}', found {}",
2005                model.fields().len(),
2006                model.path(),
2007                slots.len()
2008            )));
2009        }
2010        let mut payload_bytes = Vec::new();
2011        let mut slot_table = Vec::with_capacity(slots.len());
2012
2013        for slot_payload in slots {
2014            match slot_payload {
2015                Some(bytes) => {
2016                    let start = u32::try_from(payload_bytes.len()).map_err(|_| {
2017                        InternalError::persisted_row_encode_failed(
2018                            "slot payload start exceeds u32 range",
2019                        )
2020                    })?;
2021                    let len = u32::try_from(bytes.len()).map_err(|_| {
2022                        InternalError::persisted_row_encode_failed(
2023                            "slot payload length exceeds u32 range",
2024                        )
2025                    })?;
2026                    payload_bytes.extend_from_slice(bytes);
2027                    slot_table.push((start, len));
2028                }
2029                None => slot_table.push((0_u32, 0_u32)),
2030            }
2031        }
2032
2033        encode_slot_payload_from_parts(slots.len(), slot_table.as_slice(), payload_bytes.as_slice())
2034    }
2035
2036    #[test]
2037    fn decode_slot_value_from_bytes_decodes_scalar_slots_through_one_owner() {
2038        let payload =
2039            encode_scalar_slot_value(ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")));
2040        let value =
2041            decode_slot_value_from_bytes(&TEST_MODEL, 0, payload.as_slice()).expect("decode slot");
2042
2043        assert_eq!(value, Value::Text("Ada".to_string()));
2044    }
2045
2046    #[test]
2047    fn decode_slot_value_from_bytes_reports_scalar_prefix_bytes() {
2048        let err = decode_slot_value_from_bytes(&TEST_MODEL, 0, &[0x00, 1])
2049            .expect_err("invalid scalar slot prefix should fail closed");
2050
2051        assert!(
2052            err.message
2053                .contains("expected slot envelope prefix byte 0xFF, found 0x00"),
2054            "unexpected error: {err:?}"
2055        );
2056    }
2057
2058    #[test]
2059    fn decode_slot_value_from_bytes_respects_value_storage_decode_contract() {
2060        let payload = crate::serialize::serialize(&Value::Text("Ada".to_string()))
2061            .expect("encode value-storage payload");
2062
2063        let value =
2064            decode_slot_value_from_bytes(&TEST_MODEL, 1, payload.as_slice()).expect("decode slot");
2065
2066        assert_eq!(value, Value::Text("Ada".to_string()));
2067    }
2068
2069    #[test]
2070    fn encode_slot_value_from_value_roundtrips_scalar_slots() {
2071        let payload = encode_slot_value_from_value(&TEST_MODEL, 0, &Value::Text("Ada".to_string()))
2072            .expect("encode slot");
2073        let decoded =
2074            decode_slot_value_from_bytes(&TEST_MODEL, 0, payload.as_slice()).expect("decode slot");
2075
2076        assert_eq!(decoded, Value::Text("Ada".to_string()));
2077    }
2078
2079    #[test]
2080    fn encode_slot_value_from_value_roundtrips_value_storage_slots() {
2081        let payload = encode_slot_value_from_value(&TEST_MODEL, 1, &Value::Text("Ada".to_string()))
2082            .expect("encode slot");
2083        let decoded =
2084            decode_slot_value_from_bytes(&TEST_MODEL, 1, payload.as_slice()).expect("decode slot");
2085
2086        assert_eq!(decoded, Value::Text("Ada".to_string()));
2087    }
2088
2089    #[test]
2090    fn encode_slot_value_from_value_roundtrips_structured_value_storage_slots_for_all_cases() {
2091        for value in representative_structured_value_storage_cases() {
2092            let payload = encode_slot_value_from_value(&VALUE_STORAGE_STRUCTURED_MODEL, 0, &value)
2093                .unwrap_or_else(|err| {
2094                    panic!(
2095                        "structured value-storage slot should encode for value {value:?}: {err:?}"
2096                    )
2097                });
2098            let decoded = decode_slot_value_from_bytes(
2099                &VALUE_STORAGE_STRUCTURED_MODEL,
2100                0,
2101                payload.as_slice(),
2102            )
2103            .unwrap_or_else(|err| {
2104                panic!(
2105                    "structured value-storage slot should decode for value {value:?} with payload {payload:?}: {err:?}"
2106                )
2107            });
2108
2109            assert_eq!(decoded, value);
2110        }
2111    }
2112
2113    #[test]
2114    fn encode_slot_value_from_value_roundtrips_list_by_kind_slots() {
2115        let payload = encode_slot_value_from_value(
2116            &LIST_MODEL,
2117            0,
2118            &Value::List(vec![Value::Text("alpha".to_string())]),
2119        )
2120        .expect("encode list slot");
2121        let decoded =
2122            decode_slot_value_from_bytes(&LIST_MODEL, 0, payload.as_slice()).expect("decode slot");
2123
2124        assert_eq!(decoded, Value::List(vec![Value::Text("alpha".to_string())]),);
2125    }
2126
2127    #[test]
2128    fn encode_slot_value_from_value_roundtrips_map_by_kind_slots() {
2129        let payload = encode_slot_value_from_value(
2130            &MAP_MODEL,
2131            0,
2132            &Value::Map(vec![(Value::Text("alpha".to_string()), Value::Uint(7))]),
2133        )
2134        .expect("encode map slot");
2135        let decoded =
2136            decode_slot_value_from_bytes(&MAP_MODEL, 0, payload.as_slice()).expect("decode slot");
2137
2138        assert_eq!(
2139            decoded,
2140            Value::Map(vec![(Value::Text("alpha".to_string()), Value::Uint(7))]),
2141        );
2142    }
2143
2144    #[test]
2145    fn encode_slot_value_from_value_accepts_value_storage_maps_with_structured_values() {
2146        let principal = Principal::dummy(7);
2147        let project = Value::from_map(vec![
2148            (Value::Text("pid".to_string()), Value::Principal(principal)),
2149            (
2150                Value::Text("status".to_string()),
2151                Value::Enum(ValueEnum::new(
2152                    "Saved",
2153                    Some("design::app::user::customise::project::ProjectStatus"),
2154                )),
2155            ),
2156        ])
2157        .expect("project value should normalize into a canonical map");
2158        let projects = Value::from_map(vec![(Value::Principal(principal), project)])
2159            .expect("outer map should normalize into a canonical map");
2160
2161        let payload =
2162            encode_slot_value_from_value(&STRUCTURED_MAP_VALUE_STORAGE_MODEL, 0, &projects)
2163                .expect("encode structured map slot");
2164        let decoded = decode_slot_value_from_bytes(
2165            &STRUCTURED_MAP_VALUE_STORAGE_MODEL,
2166            0,
2167            payload.as_slice(),
2168        )
2169        .expect("decode structured map slot");
2170
2171        assert_eq!(decoded, projects);
2172    }
2173
2174    #[test]
2175    fn structured_value_storage_cases_decode_through_direct_value_storage_boundary() {
2176        for value in representative_value_storage_cases() {
2177            let payload = serialize(&value).unwrap_or_else(|err| {
2178                panic!(
2179                    "structured value-storage payload should serialize for value {value:?}: {err:?}"
2180                )
2181            });
2182            let decoded = decode_structural_value_storage_bytes(payload.as_slice()).unwrap_or_else(
2183                |err| {
2184                    panic!(
2185                        "structured value-storage payload should decode for value {value:?} with payload {payload:?}: {err:?}"
2186                    )
2187                },
2188            );
2189
2190            assert_eq!(decoded, value);
2191        }
2192    }
2193
2194    #[test]
2195    fn encode_slot_value_from_value_roundtrips_enum_by_kind_slots() {
2196        let payload = encode_slot_value_from_value(
2197            &ENUM_MODEL,
2198            0,
2199            &Value::Enum(
2200                ValueEnum::new("Loaded", Some("tests::State")).with_payload(Value::Uint(7)),
2201            ),
2202        )
2203        .expect("encode enum slot");
2204        let decoded =
2205            decode_slot_value_from_bytes(&ENUM_MODEL, 0, payload.as_slice()).expect("decode slot");
2206
2207        assert_eq!(
2208            decoded,
2209            Value::Enum(
2210                ValueEnum::new("Loaded", Some("tests::State")).with_payload(Value::Uint(7,))
2211            ),
2212        );
2213    }
2214
2215    #[test]
2216    fn encode_slot_value_from_value_roundtrips_leaf_by_kind_wrapper_slots() {
2217        let account = Account::from_parts(Principal::dummy(7), Some(Subaccount::from([7_u8; 32])));
2218        let payload = encode_slot_value_from_value(&ACCOUNT_MODEL, 0, &Value::Account(account))
2219            .expect("encode account slot");
2220        let decoded = decode_slot_value_from_bytes(&ACCOUNT_MODEL, 0, payload.as_slice())
2221            .expect("decode slot");
2222
2223        assert_eq!(decoded, Value::Account(account));
2224    }
2225
2226    #[test]
2227    fn custom_slot_payload_roundtrips_structured_field_value() {
2228        let profile = PersistedRowProfileValue {
2229            bio: "Ada".to_string(),
2230        };
2231        let payload = encode_persisted_custom_slot_payload(&profile, "profile")
2232            .expect("encode custom structured payload");
2233        let decoded = decode_persisted_custom_slot_payload::<PersistedRowProfileValue>(
2234            payload.as_slice(),
2235            "profile",
2236        )
2237        .expect("decode custom structured payload");
2238
2239        assert_eq!(decoded, profile);
2240        assert_eq!(
2241            decode_persisted_slot_payload::<Value>(payload.as_slice(), "profile")
2242                .expect("decode raw value payload"),
2243            profile.to_value(),
2244        );
2245    }
2246
2247    #[test]
2248    fn custom_many_slot_payload_roundtrips_structured_value_lists() {
2249        let profiles = vec![
2250            PersistedRowProfileValue {
2251                bio: "Ada".to_string(),
2252            },
2253            PersistedRowProfileValue {
2254                bio: "Grace".to_string(),
2255            },
2256        ];
2257        let payload = encode_persisted_custom_many_slot_payload(profiles.as_slice(), "profiles")
2258            .expect("encode custom structured list payload");
2259        let decoded = decode_persisted_custom_many_slot_payload::<PersistedRowProfileValue>(
2260            payload.as_slice(),
2261            "profiles",
2262        )
2263        .expect("decode custom structured list payload");
2264
2265        assert_eq!(decoded, profiles);
2266    }
2267
2268    #[test]
2269    fn decode_persisted_non_null_slot_payload_rejects_null_for_required_structured_fields() {
2270        let err =
2271            decode_persisted_non_null_slot_payload::<PersistedRowProfileValue>(&[0xF6], "profile")
2272                .expect_err("required structured payload must reject null");
2273
2274        assert!(
2275            err.message
2276                .contains("unexpected null for non-nullable field"),
2277            "unexpected error: {err:?}"
2278        );
2279    }
2280
2281    #[test]
2282    fn decode_persisted_option_slot_payload_treats_cbor_null_as_none() {
2283        let decoded =
2284            decode_persisted_option_slot_payload::<PersistedRowProfileValue>(&[0xF6], "profile")
2285                .expect("optional structured payload should decode");
2286
2287        assert_eq!(decoded, None);
2288    }
2289
2290    #[test]
2291    fn encode_slot_value_from_value_rejects_null_for_required_structured_slots() {
2292        let err = encode_slot_value_from_value(&REQUIRED_STRUCTURED_MODEL, 0, &Value::Null)
2293            .expect_err("required structured slot must reject null");
2294
2295        assert!(
2296            err.message.contains("required field cannot store null"),
2297            "unexpected error: {err:?}"
2298        );
2299    }
2300
2301    #[test]
2302    fn encode_slot_value_from_value_allows_null_for_optional_structured_slots() {
2303        let payload = encode_slot_value_from_value(&OPTIONAL_STRUCTURED_MODEL, 0, &Value::Null)
2304            .expect("optional structured slot should allow null");
2305        let decoded =
2306            decode_slot_value_from_bytes(&OPTIONAL_STRUCTURED_MODEL, 0, payload.as_slice())
2307                .expect("optional structured slot should decode");
2308
2309        assert_eq!(decoded, Value::Null);
2310    }
2311
2312    #[test]
2313    fn encode_slot_value_from_value_rejects_unknown_enum_payload_variants() {
2314        let err = encode_slot_value_from_value(
2315            &ENUM_MODEL,
2316            0,
2317            &Value::Enum(
2318                ValueEnum::new("Unknown", Some("tests::State")).with_payload(Value::Uint(7)),
2319            ),
2320        )
2321        .expect_err("unknown enum payload should fail closed");
2322
2323        assert!(err.message.contains("unknown enum variant"));
2324    }
2325
2326    #[test]
2327    fn structural_slot_reader_and_direct_decode_share_the_same_field_codec_boundary() {
2328        let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2329        let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2330            .expect("encode value-storage payload");
2331        writer
2332            .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2333            .expect("write scalar slot");
2334        writer
2335            .write_slot(1, Some(payload.as_slice()))
2336            .expect("write value-storage slot");
2337        let raw_row = RawRow::try_new(
2338            serialize_row_payload(writer.finish().expect("finish slot payload"))
2339                .expect("serialize row payload"),
2340        )
2341        .expect("build raw row");
2342
2343        let direct_slots =
2344            StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL).expect("decode row");
2345        let mut cached_slots =
2346            StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL).expect("decode row");
2347
2348        let direct_name = decode_slot_value_by_contract(&direct_slots, 0).expect("decode name");
2349        let direct_payload =
2350            decode_slot_value_by_contract(&direct_slots, 1).expect("decode payload");
2351        let cached_name = cached_slots.get_value(0).expect("cached name");
2352        let cached_payload = cached_slots.get_value(1).expect("cached payload");
2353
2354        assert_eq!(direct_name, cached_name);
2355        assert_eq!(direct_payload, cached_payload);
2356    }
2357
2358    #[test]
2359    fn apply_update_patch_to_raw_row_updates_only_targeted_slots() {
2360        let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2361        let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2362            .expect("encode value-storage payload");
2363        writer
2364            .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2365            .expect("write scalar slot");
2366        writer
2367            .write_slot(1, Some(payload.as_slice()))
2368            .expect("write value-storage slot");
2369        let raw_row = RawRow::try_new(
2370            serialize_row_payload(writer.finish().expect("finish slot payload"))
2371                .expect("serialize row payload"),
2372        )
2373        .expect("build raw row");
2374        let patch = UpdatePatch::new().set(
2375            FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2376            Value::Text("Grace".to_string()),
2377        );
2378
2379        let patched =
2380            apply_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &patch).expect("apply patch");
2381        let mut reader =
2382            StructuralSlotReader::from_raw_row(&patched, &TEST_MODEL).expect("decode row");
2383
2384        assert_eq!(
2385            reader.get_value(0).expect("decode slot"),
2386            Some(Value::Text("Grace".to_string()))
2387        );
2388        assert_eq!(
2389            reader.get_value(1).expect("decode slot"),
2390            Some(Value::Text("payload".to_string()))
2391        );
2392    }
2393
2394    #[test]
2395    fn serialize_update_patch_fields_encodes_canonical_slot_payloads() {
2396        let patch = UpdatePatch::new()
2397            .set(
2398                FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2399                Value::Text("Grace".to_string()),
2400            )
2401            .set(
2402                FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2403                Value::Text("payload".to_string()),
2404            );
2405
2406        let serialized =
2407            serialize_update_patch_fields(&TEST_MODEL, &patch).expect("serialize patch");
2408
2409        assert_eq!(serialized.entries().len(), 2);
2410        assert_eq!(
2411            decode_slot_value_from_bytes(
2412                &TEST_MODEL,
2413                serialized.entries()[0].slot().index(),
2414                serialized.entries()[0].payload(),
2415            )
2416            .expect("decode slot payload"),
2417            Value::Text("Grace".to_string())
2418        );
2419        assert_eq!(
2420            decode_slot_value_from_bytes(
2421                &TEST_MODEL,
2422                serialized.entries()[1].slot().index(),
2423                serialized.entries()[1].payload(),
2424            )
2425            .expect("decode slot payload"),
2426            Value::Text("payload".to_string())
2427        );
2428    }
2429
2430    #[test]
2431    fn serialized_patch_writer_rejects_clear_slots() {
2432        let mut writer = SerializedPatchWriter::for_model(&TEST_MODEL);
2433
2434        let err = writer
2435            .write_slot(0, None)
2436            .expect_err("0.65 patch staging must reject missing-slot clears");
2437
2438        assert!(
2439            err.message
2440                .contains("serialized patch writer cannot clear slot 0"),
2441            "unexpected error: {err:?}"
2442        );
2443        assert!(
2444            err.message.contains(TEST_MODEL.path()),
2445            "unexpected error: {err:?}"
2446        );
2447    }
2448
2449    #[test]
2450    fn slot_buffer_writer_rejects_clear_slots() {
2451        let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2452
2453        let err = writer
2454            .write_slot(0, None)
2455            .expect_err("canonical row staging must reject missing-slot clears");
2456
2457        assert!(
2458            err.message
2459                .contains("slot buffer writer cannot clear slot 0"),
2460            "unexpected error: {err:?}"
2461        );
2462        assert!(
2463            err.message.contains(TEST_MODEL.path()),
2464            "unexpected error: {err:?}"
2465        );
2466    }
2467
2468    #[test]
2469    fn apply_update_patch_to_raw_row_uses_last_write_wins() {
2470        let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2471        let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2472            .expect("encode value-storage payload");
2473        writer
2474            .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2475            .expect("write scalar slot");
2476        writer
2477            .write_slot(1, Some(payload.as_slice()))
2478            .expect("write value-storage slot");
2479        let raw_row = RawRow::try_new(
2480            serialize_row_payload(writer.finish().expect("finish slot payload"))
2481                .expect("serialize row payload"),
2482        )
2483        .expect("build raw row");
2484        let slot = FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot");
2485        let patch = UpdatePatch::new()
2486            .set(slot, Value::Text("Grace".to_string()))
2487            .set(slot, Value::Text("Lin".to_string()));
2488
2489        let patched =
2490            apply_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &patch).expect("apply patch");
2491        let mut reader =
2492            StructuralSlotReader::from_raw_row(&patched, &TEST_MODEL).expect("decode row");
2493
2494        assert_eq!(
2495            reader.get_value(0).expect("decode slot"),
2496            Some(Value::Text("Lin".to_string()))
2497        );
2498    }
2499
2500    #[test]
2501    fn apply_update_patch_to_raw_row_rejects_noncanonical_missing_slot_baseline() {
2502        let empty_slots = vec![None::<&[u8]>; TEST_MODEL.fields().len()];
2503        let raw_row = RawRow::try_new(
2504            serialize_row_payload(
2505                encode_slot_payload_allowing_missing_for_tests(&TEST_MODEL, empty_slots.as_slice())
2506                    .expect("encode malformed slot payload"),
2507            )
2508            .expect("serialize row payload"),
2509        )
2510        .expect("build raw row");
2511        let patch = UpdatePatch::new().set(
2512            FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2513            Value::Text("payload".to_string()),
2514        );
2515
2516        let err = apply_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &patch)
2517            .expect_err("noncanonical rows with missing slots must fail closed");
2518
2519        assert_eq!(err.message, "row decode: missing slot payload: slot=0");
2520    }
2521
2522    #[test]
2523    fn apply_serialized_update_patch_to_raw_row_rejects_noncanonical_scalar_baseline() {
2524        let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2525            .expect("encode value-storage payload");
2526        let malformed_slots = [Some([0xF6].as_slice()), Some(payload.as_slice())];
2527        let raw_row = RawRow::try_new(
2528            serialize_row_payload(
2529                encode_slot_payload_allowing_missing_for_tests(&TEST_MODEL, &malformed_slots)
2530                    .expect("encode malformed slot payload"),
2531            )
2532            .expect("serialize row payload"),
2533        )
2534        .expect("build raw row");
2535        let patch = UpdatePatch::new().set(
2536            FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2537            Value::Text("patched".to_string()),
2538        );
2539        let serialized =
2540            serialize_update_patch_fields(&TEST_MODEL, &patch).expect("serialize patch");
2541
2542        let err = apply_serialized_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &serialized)
2543            .expect_err("noncanonical scalar baseline must fail closed");
2544
2545        assert!(
2546            err.message.contains("field 'name'"),
2547            "unexpected error: {err:?}"
2548        );
2549        assert!(
2550            err.message
2551                .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
2552            "unexpected error: {err:?}"
2553        );
2554    }
2555
2556    #[test]
2557    fn apply_serialized_update_patch_to_raw_row_rejects_noncanonical_scalar_patch_payload() {
2558        let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2559        let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2560            .expect("encode value-storage payload");
2561        writer
2562            .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2563            .expect("write scalar slot");
2564        writer
2565            .write_slot(1, Some(payload.as_slice()))
2566            .expect("write value-storage slot");
2567        let raw_row = RawRow::try_new(
2568            serialize_row_payload(writer.finish().expect("finish slot payload"))
2569                .expect("serialize row payload"),
2570        )
2571        .expect("build raw row");
2572        let serialized = SerializedUpdatePatch::new(vec![SerializedFieldUpdate::new(
2573            FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2574            vec![0xF6],
2575        )]);
2576
2577        let err = apply_serialized_update_patch_to_raw_row(&TEST_MODEL, &raw_row, &serialized)
2578            .expect_err("noncanonical serialized patch payload must fail closed");
2579
2580        assert!(
2581            err.message.contains("field 'name'"),
2582            "unexpected error: {err:?}"
2583        );
2584        assert!(
2585            err.message
2586                .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
2587            "unexpected error: {err:?}"
2588        );
2589    }
2590
2591    #[test]
2592    fn structural_slot_reader_rejects_slot_count_mismatch() {
2593        let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2594        let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2595            .expect("encode payload");
2596        writer
2597            .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2598            .expect("write scalar slot");
2599        writer
2600            .write_slot(1, Some(payload.as_slice()))
2601            .expect("write payload slot");
2602        let mut payload = writer.finish().expect("finish slot payload");
2603        payload[..2].copy_from_slice(&1_u16.to_be_bytes());
2604        let raw_row =
2605            RawRow::try_new(serialize_row_payload(payload).expect("serialize row payload"))
2606                .expect("build raw row");
2607
2608        let err = StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL)
2609            .err()
2610            .expect("slot-count drift must fail closed");
2611
2612        assert_eq!(
2613            err.message,
2614            "row decode: slot count mismatch: expected 2, found 1"
2615        );
2616    }
2617
2618    #[test]
2619    fn structural_slot_reader_rejects_slot_span_exceeds_payload_length() {
2620        let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2621        let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2622            .expect("encode payload");
2623        writer
2624            .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2625            .expect("write scalar slot");
2626        writer
2627            .write_slot(1, Some(payload.as_slice()))
2628            .expect("write payload slot");
2629        let mut payload = writer.finish().expect("finish slot payload");
2630
2631        // Corrupt the second slot span so the payload table points past the
2632        // available data section.
2633        payload[14..18].copy_from_slice(&u32::MAX.to_be_bytes());
2634        let raw_row =
2635            RawRow::try_new(serialize_row_payload(payload).expect("serialize row payload"))
2636                .expect("build raw row");
2637
2638        let err = StructuralSlotReader::from_raw_row(&raw_row, &TEST_MODEL)
2639            .err()
2640            .expect("slot span drift must fail closed");
2641
2642        assert_eq!(err.message, "row decode: slot span exceeds payload length");
2643    }
2644
2645    #[test]
2646    fn apply_serialized_update_patch_to_raw_row_replays_preencoded_slots() {
2647        let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2648        let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2649            .expect("encode value-storage payload");
2650        writer
2651            .write_scalar(0, ScalarSlotValueRef::Value(ScalarValueRef::Text("Ada")))
2652            .expect("write scalar slot");
2653        writer
2654            .write_slot(1, Some(payload.as_slice()))
2655            .expect("write value-storage slot");
2656        let raw_row = RawRow::try_new(
2657            serialize_row_payload(writer.finish().expect("finish slot payload"))
2658                .expect("serialize row payload"),
2659        )
2660        .expect("build raw row");
2661        let patch = UpdatePatch::new().set(
2662            FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2663            Value::Text("Grace".to_string()),
2664        );
2665        let serialized =
2666            serialize_update_patch_fields(&TEST_MODEL, &patch).expect("serialize patch");
2667
2668        let patched = raw_row
2669            .apply_serialized_update_patch(&TEST_MODEL, &serialized)
2670            .expect("apply serialized patch");
2671        let mut reader =
2672            StructuralSlotReader::from_raw_row(&patched, &TEST_MODEL).expect("decode row");
2673
2674        assert_eq!(
2675            reader.get_value(0).expect("decode slot"),
2676            Some(Value::Text("Grace".to_string()))
2677        );
2678    }
2679
2680    #[test]
2681    fn serialize_entity_slots_as_update_patch_replays_full_typed_after_image() {
2682        let old_entity = PersistedRowPatchBridgeEntity {
2683            id: crate::types::Ulid::from_u128(7),
2684            name: "Ada".to_string(),
2685        };
2686        let new_entity = PersistedRowPatchBridgeEntity {
2687            id: crate::types::Ulid::from_u128(7),
2688            name: "Grace".to_string(),
2689        };
2690        let raw_row = RawRow::from_entity(&old_entity).expect("encode old row");
2691        let old_decoded = raw_row
2692            .try_decode::<PersistedRowPatchBridgeEntity>()
2693            .expect("decode old entity");
2694        let serialized =
2695            serialize_entity_slots_as_update_patch(&new_entity).expect("serialize entity patch");
2696        let direct =
2697            RawRow::from_serialized_update_patch(PersistedRowPatchBridgeEntity::MODEL, &serialized)
2698                .expect("direct row emission should succeed");
2699
2700        let patched = raw_row
2701            .apply_serialized_update_patch(PersistedRowPatchBridgeEntity::MODEL, &serialized)
2702            .expect("apply serialized patch");
2703        let decoded = patched
2704            .try_decode::<PersistedRowPatchBridgeEntity>()
2705            .expect("decode patched entity");
2706
2707        assert_eq!(
2708            direct, patched,
2709            "fresh row emission and replayed full-image patch must converge on identical bytes",
2710        );
2711        assert_eq!(old_decoded, old_entity);
2712        assert_eq!(decoded, new_entity);
2713    }
2714
2715    #[test]
2716    fn canonical_row_from_raw_row_replays_canonical_full_image_bytes() {
2717        let entity = PersistedRowPatchBridgeEntity {
2718            id: crate::types::Ulid::from_u128(11),
2719            name: "Ada".to_string(),
2720        };
2721        let raw_row = RawRow::from_entity(&entity).expect("encode canonical row");
2722        let canonical =
2723            super::canonical_row_from_raw_row(PersistedRowPatchBridgeEntity::MODEL, &raw_row)
2724                .expect("canonical re-emission should succeed");
2725
2726        assert_eq!(
2727            canonical.as_bytes(),
2728            raw_row.as_bytes(),
2729            "canonical raw-row rebuild must preserve already canonical row bytes",
2730        );
2731    }
2732
2733    #[test]
2734    fn canonical_row_from_raw_row_rejects_noncanonical_scalar_payload() {
2735        let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2736            .expect("encode value-storage payload");
2737        let mut writer = SlotBufferWriter::for_model(&TEST_MODEL);
2738        writer
2739            .write_slot(0, Some(&[0xF6]))
2740            .expect("write malformed scalar slot");
2741        writer
2742            .write_slot(1, Some(payload.as_slice()))
2743            .expect("write value-storage slot");
2744        let raw_row = RawRow::try_new(
2745            serialize_row_payload(writer.finish().expect("finish slot payload"))
2746                .expect("serialize malformed row"),
2747        )
2748        .expect("build malformed raw row");
2749
2750        let err = super::canonical_row_from_raw_row(&TEST_MODEL, &raw_row)
2751            .expect_err("canonical raw-row rebuild must reject malformed scalar payloads");
2752
2753        assert!(
2754            err.message.contains("field 'name'"),
2755            "unexpected error: {err:?}"
2756        );
2757        assert!(
2758            err.message
2759                .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
2760            "unexpected error: {err:?}"
2761        );
2762    }
2763
2764    #[test]
2765    fn raw_row_from_serialized_update_patch_rejects_noncanonical_scalar_payload() {
2766        let payload = crate::serialize::serialize(&Value::Text("payload".to_string()))
2767            .expect("encode value-storage payload");
2768        let serialized = SerializedUpdatePatch::new(vec![
2769            SerializedFieldUpdate::new(
2770                FieldSlot::from_index(&TEST_MODEL, 0).expect("resolve slot"),
2771                vec![0xF6],
2772            ),
2773            SerializedFieldUpdate::new(
2774                FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2775                payload,
2776            ),
2777        ]);
2778
2779        let err = RawRow::from_serialized_update_patch(&TEST_MODEL, &serialized)
2780            .expect_err("fresh row emission must reject noncanonical serialized patch payloads");
2781
2782        assert!(
2783            err.message.contains("field 'name'"),
2784            "unexpected error: {err:?}"
2785        );
2786        assert!(
2787            err.message
2788                .contains("expected slot envelope prefix byte 0xFF, found 0xF6"),
2789            "unexpected error: {err:?}"
2790        );
2791    }
2792
2793    #[test]
2794    fn raw_row_from_serialized_update_patch_rejects_incomplete_slot_image() {
2795        let serialized = SerializedUpdatePatch::new(vec![SerializedFieldUpdate::new(
2796            FieldSlot::from_index(&TEST_MODEL, 1).expect("resolve slot"),
2797            crate::serialize::serialize(&Value::Text("payload".to_string()))
2798                .expect("encode value-storage payload"),
2799        )]);
2800
2801        let err = RawRow::from_serialized_update_patch(&TEST_MODEL, &serialized)
2802            .expect_err("fresh row emission must reject missing declared slots");
2803
2804        assert!(
2805            err.message.contains("serialized patch did not emit slot 0"),
2806            "unexpected error: {err:?}"
2807        );
2808    }
2809}