Skip to main content

shape_value/
heap_value.rs

1//! Heap-allocated value types reachable through `HeapValue`.
2//!
3//! After the strict-typing Phase-2 bulldozer (option C — heterogeneous
4//! collections / dynamic single-value wrappers excised), `HeapValue` carries
5//! only typed payloads:
6//!
7//! - typed primitives (string, decimal, bigint, char, future-id),
8//! - typed handles (datatable, content, instant, io-handle, native scalars),
9//! - typed object slots (`TypedObject` with `Box<[ValueSlot]>`),
10//! - the typed-closure-raw block (`ClosureRaw`),
11//! - typed temporal data (`TemporalData`),
12//! - typed table views (`TableViewData`).
13//!
14//! V3-S5 ckpt-1..ckpt-4 (2026-05-15): the inline `TypedArrayData` enum +
15//! the outer `HeapValue::TypedArray(Arc<TypedArrayData>)` arm +
16//! `TypedBuffer<T>` / `AlignedTypedBuffer` wrapper layer were retired
17//! wholesale per W12-typed-array-data-deletion-audit §3.5 + §B + ADR-006
18//! §2.7.24 Q25.A SUPERSEDED. The canonical replacement is the v2-raw
19//! `TypedArray<T>` flat struct at `crate::v2::typed_array::TypedArray<T>`
20//! (per `docs/runtime-v2-spec.md`). The `HeapKind::TypedArray = 8`
21//! ordinal is vacated; do not reuse.
22//!
23//! Variants that previously held `ValueWord` (the deleted dynamic word) —
24//! `Some`/`Ok`/`Err`/`Range`/`TraitObject`/`FunctionRef`,
25//! `HashMap`/`Set`/`Deque`/`PriorityQueue`, `Iterator`/`Generator`/
26//! `ProjectedRef`, `Concurrency` (Mutex/Atomic/Lazy/Channel), `Rare`,
27//! `Enum`, `Array` (heterogeneous-element), `HostClosure` — were removed
28//! together with their `*Data` structs. The corresponding `HeapKind`
29//! ordinals are preserved (annotated "(removed)" in `heap_variants.rs`)
30//! and await monomorphized typed replacements per `docs/runtime-v2-spec.md`.
31
32use crate::aligned_vec::AlignedVec;
33use std::fmt;
34use std::sync::Arc;
35
36// ── Matrix storage (carries `HeapKind::Matrix` and `HeapKind::MatrixSlice`) ──
37//
38// ADR-006 §2.7.22 amendment (Round 18 S3 W12-matrix-floatslice-heapkind-exit,
39// 2026-05-13): Matrix is a single Matrix value (NOT a buffer-of-Matrix), and
40// exits the `TypedArrayData` carrier hierarchy. `HeapKind::Matrix = 34` +
41// `HeapValue::Matrix(Arc<MatrixData>)`; FloatSlice projection becomes
42// `HeapKind::MatrixSlice = 35` + `HeapValue::MatrixSlice(Arc<MatrixSliceData>)`.
43// The prior §2.7.22 Q23 ruling (Matrix lives under `HeapKind::TypedArray` via
44// `TypedArrayData::Matrix`) is superseded — see §2.7.22 amendment text.
45
46/// Flat, SIMD-aligned matrix storage (row-major order).
47#[derive(Debug, Clone)]
48pub struct MatrixData {
49    pub data: AlignedVec<f64>,
50    pub rows: u32,
51    pub cols: u32,
52}
53
54impl MatrixData {
55    /// Create a zero-initialized matrix.
56    pub fn new(rows: u32, cols: u32) -> Self {
57        let len = (rows as usize) * (cols as usize);
58        let mut data = AlignedVec::with_capacity(len);
59        for _ in 0..len {
60            data.push(0.0);
61        }
62        Self { data, rows, cols }
63    }
64
65    /// Create from a flat data buffer.
66    pub fn from_flat(data: AlignedVec<f64>, rows: u32, cols: u32) -> Self {
67        debug_assert_eq!(data.len(), (rows as usize) * (cols as usize));
68        Self { data, rows, cols }
69    }
70
71    /// Get element at (row, col).
72    #[inline]
73    pub fn get(&self, row: u32, col: u32) -> f64 {
74        self.data[(row as usize) * (self.cols as usize) + (col as usize)]
75    }
76
77    /// Set element at (row, col).
78    #[inline]
79    pub fn set(&mut self, row: u32, col: u32, val: f64) {
80        self.data[(row as usize) * (self.cols as usize) + (col as usize)] = val;
81    }
82
83    /// Get a row slice.
84    #[inline]
85    pub fn row_slice(&self, row: u32) -> &[f64] {
86        let start = (row as usize) * (self.cols as usize);
87        &self.data[start..start + self.cols as usize]
88    }
89
90    /// Get shape as (rows, cols).
91    #[inline]
92    pub fn shape(&self) -> (u32, u32) {
93        (self.rows, self.cols)
94    }
95
96    /// Get a row's data as a slice (alias for `row_slice`).
97    #[inline]
98    pub fn row_data(&self, row: u32) -> &[f64] {
99        self.row_slice(row)
100    }
101}
102
103/// Row/column projection into a parent `MatrixData` (`{ parent, offset, len }`).
104///
105/// ADR-006 §2.7.22 amendment (Round 18 S3 W12-matrix-floatslice-heapkind-exit,
106/// 2026-05-13): FloatSlice exits the `TypedArrayData` carrier hierarchy as a
107/// category-error. It is a projection-into-a-Matrix, not a buffer of floats.
108/// The carrier is `Arc<MatrixSliceData>` with kind `Ptr(HeapKind::MatrixSlice)`,
109/// constructed by `Matrix.row(i)` / `Matrix.col(i)` projection methods.
110///
111/// Aliasing semantics: the projection shares the parent Matrix's buffer
112/// (mutating through the projection writes through to the parent), preserved
113/// from the pre-amendment `TypedArrayData::FloatSlice` shape. The `parent`
114/// Arc retains one strong-count share for the projection's lifetime.
115#[derive(Debug, Clone)]
116pub struct MatrixSliceData {
117    pub parent: Arc<MatrixData>,
118    pub offset: u32,
119    pub len: u32,
120}
121
122impl MatrixSliceData {
123    /// Construct a projection into a parent matrix.
124    #[inline]
125    pub fn new(parent: Arc<MatrixData>, offset: u32, len: u32) -> Self {
126        Self { parent, offset, len }
127    }
128
129    /// Borrow the underlying slice into the parent's flat data buffer.
130    #[inline]
131    pub fn as_slice(&self) -> &[f64] {
132        let off = self.offset as usize;
133        let n = self.len as usize;
134        &self.parent.data.as_slice()[off..off + n]
135    }
136}
137
138// ── NativeScalar — width-preserving native ABI scalars ──────────────────────
139
140/// Native ABI-width scalars used by C ABI / `extern C fn` boundaries.
141///
142/// These values preserve their ABI width across VM boundaries so C wrappers
143/// can avoid lossy `i64` normalization.
144#[derive(Debug, Clone, Copy, PartialEq)]
145pub enum NativeScalar {
146    I8(i8),
147    U8(u8),
148    I16(i16),
149    U16(u16),
150    I32(i32),
151    I64(i64),
152    U32(u32),
153    U64(u64),
154    Isize(isize),
155    Usize(usize),
156    Ptr(usize),
157    F32(f32),
158}
159
160impl NativeScalar {
161    #[inline]
162    pub fn type_name(&self) -> &'static str {
163        match self {
164            NativeScalar::I8(_) => "i8",
165            NativeScalar::U8(_) => "u8",
166            NativeScalar::I16(_) => "i16",
167            NativeScalar::U16(_) => "u16",
168            NativeScalar::I32(_) => "i32",
169            NativeScalar::I64(_) => "i64",
170            NativeScalar::U32(_) => "u32",
171            NativeScalar::U64(_) => "u64",
172            NativeScalar::Isize(_) => "isize",
173            NativeScalar::Usize(_) => "usize",
174            NativeScalar::Ptr(_) => "ptr",
175            NativeScalar::F32(_) => "f32",
176        }
177    }
178
179    #[inline]
180    pub fn is_truthy(&self) -> bool {
181        match self {
182            NativeScalar::I8(v) => *v != 0,
183            NativeScalar::U8(v) => *v != 0,
184            NativeScalar::I16(v) => *v != 0,
185            NativeScalar::U16(v) => *v != 0,
186            NativeScalar::I32(v) => *v != 0,
187            NativeScalar::I64(v) => *v != 0,
188            NativeScalar::U32(v) => *v != 0,
189            NativeScalar::U64(v) => *v != 0,
190            NativeScalar::Isize(v) => *v != 0,
191            NativeScalar::Usize(v) => *v != 0,
192            NativeScalar::Ptr(v) => *v != 0,
193            NativeScalar::F32(v) => *v != 0.0 && !v.is_nan(),
194        }
195    }
196
197    #[inline]
198    pub fn as_i64(&self) -> Option<i64> {
199        match self {
200            NativeScalar::I8(v) => Some(*v as i64),
201            NativeScalar::U8(v) => Some(*v as i64),
202            NativeScalar::I16(v) => Some(*v as i64),
203            NativeScalar::U16(v) => Some(*v as i64),
204            NativeScalar::I32(v) => Some(*v as i64),
205            NativeScalar::I64(v) => Some(*v),
206            NativeScalar::U32(v) => Some(*v as i64),
207            NativeScalar::U64(v) => i64::try_from(*v).ok(),
208            NativeScalar::Isize(v) => i64::try_from(*v).ok(),
209            NativeScalar::Usize(v) => i64::try_from(*v).ok(),
210            NativeScalar::Ptr(v) => i64::try_from(*v).ok(),
211            NativeScalar::F32(_) => None,
212        }
213    }
214
215    #[inline]
216    pub fn as_u64(&self) -> Option<u64> {
217        match self {
218            NativeScalar::U8(v) => Some(*v as u64),
219            NativeScalar::U16(v) => Some(*v as u64),
220            NativeScalar::U32(v) => Some(*v as u64),
221            NativeScalar::U64(v) => Some(*v),
222            NativeScalar::Usize(v) => Some(*v as u64),
223            NativeScalar::Ptr(v) => Some(*v as u64),
224            NativeScalar::I8(v) if *v >= 0 => Some(*v as u64),
225            NativeScalar::I16(v) if *v >= 0 => Some(*v as u64),
226            NativeScalar::I32(v) if *v >= 0 => Some(*v as u64),
227            NativeScalar::I64(v) if *v >= 0 => Some(*v as u64),
228            NativeScalar::Isize(v) if *v >= 0 => Some(*v as u64),
229            _ => None,
230        }
231    }
232
233    #[inline]
234    pub fn as_i128(&self) -> Option<i128> {
235        match self {
236            NativeScalar::I8(v) => Some(*v as i128),
237            NativeScalar::U8(v) => Some(*v as i128),
238            NativeScalar::I16(v) => Some(*v as i128),
239            NativeScalar::U16(v) => Some(*v as i128),
240            NativeScalar::I32(v) => Some(*v as i128),
241            NativeScalar::U32(v) => Some(*v as i128),
242            NativeScalar::I64(v) => Some(*v as i128),
243            NativeScalar::U64(v) => Some(*v as i128),
244            NativeScalar::Isize(v) => Some(*v as i128),
245            NativeScalar::Usize(v) => Some(*v as i128),
246            NativeScalar::Ptr(v) => Some(*v as i128),
247            NativeScalar::F32(_) => None,
248        }
249    }
250
251    #[inline]
252    pub fn as_f64(&self) -> f64 {
253        match self {
254            NativeScalar::I8(v) => *v as f64,
255            NativeScalar::U8(v) => *v as f64,
256            NativeScalar::I16(v) => *v as f64,
257            NativeScalar::U16(v) => *v as f64,
258            NativeScalar::I32(v) => *v as f64,
259            NativeScalar::I64(v) => *v as f64,
260            NativeScalar::U32(v) => *v as f64,
261            NativeScalar::U64(v) => *v as f64,
262            NativeScalar::Isize(v) => *v as f64,
263            NativeScalar::Usize(v) => *v as f64,
264            NativeScalar::Ptr(v) => *v as f64,
265            NativeScalar::F32(v) => *v as f64,
266        }
267    }
268}
269
270impl std::fmt::Display for NativeScalar {
271    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272        match self {
273            NativeScalar::I8(v) => write!(f, "{v}"),
274            NativeScalar::U8(v) => write!(f, "{v}"),
275            NativeScalar::I16(v) => write!(f, "{v}"),
276            NativeScalar::U16(v) => write!(f, "{v}"),
277            NativeScalar::I32(v) => write!(f, "{v}"),
278            NativeScalar::I64(v) => write!(f, "{v}"),
279            NativeScalar::U32(v) => write!(f, "{v}"),
280            NativeScalar::U64(v) => write!(f, "{v}"),
281            NativeScalar::Isize(v) => write!(f, "{v}"),
282            NativeScalar::Usize(v) => write!(f, "{v}"),
283            NativeScalar::Ptr(v) => write!(f, "0x{v:x}"),
284            NativeScalar::F32(v) => write!(f, "{v}"),
285        }
286    }
287}
288
289// ── Native type layouts (used by C ABI native views) ─────────────────────────
290
291/// Field layout metadata for `type C` structs.
292#[derive(Debug, Clone)]
293pub struct NativeLayoutField {
294    pub name: String,
295    pub c_type: String,
296    pub offset: u32,
297    pub size: u32,
298    pub align: u32,
299}
300
301/// Runtime layout descriptor for one native type.
302#[derive(Debug, Clone)]
303pub struct NativeTypeLayout {
304    pub name: String,
305    pub abi: String,
306    pub size: u32,
307    pub align: u32,
308    pub fields: Vec<NativeLayoutField>,
309}
310
311impl NativeTypeLayout {
312    #[inline]
313    pub fn field(&self, name: &str) -> Option<&NativeLayoutField> {
314        self.fields.iter().find(|field| field.name == name)
315    }
316}
317
318/// Pointer-backed zero-copy view into native memory.
319#[derive(Debug, Clone)]
320pub struct NativeViewData {
321    pub ptr: usize,
322    pub layout: Arc<NativeTypeLayout>,
323    pub mutable: bool,
324}
325
326// ── I/O handles ──────────────────────────────────────────────────────────────
327
328/// I/O handle kind discriminant.
329#[derive(Debug, Clone, Copy, PartialEq, Eq)]
330#[repr(u8)]
331pub enum IoHandleKind {
332    File = 0,
333    TcpStream = 1,
334    TcpListener = 2,
335    UdpSocket = 3,
336    ChildProcess = 4,
337    PipeReader = 5,
338    PipeWriter = 6,
339    Custom = 7,
340}
341
342/// The underlying OS resource wrapped by an IoHandle.
343pub enum IoResource {
344    File(std::fs::File),
345    TcpStream(std::net::TcpStream),
346    TcpListener(std::net::TcpListener),
347    UdpSocket(std::net::UdpSocket),
348    ChildProcess(std::process::Child),
349    PipeReader(std::process::ChildStdout),
350    PipeWriter(std::process::ChildStdin),
351    PipeReaderErr(std::process::ChildStderr),
352    /// Type-erased resource for custom I/O handles (e.g. memoized transports).
353    Custom(Box<dyn std::any::Any + Send>),
354}
355
356impl std::fmt::Debug for IoResource {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358        match self {
359            IoResource::File(_) => write!(f, "File(...)"),
360            IoResource::TcpStream(_) => write!(f, "TcpStream(...)"),
361            IoResource::TcpListener(_) => write!(f, "TcpListener(...)"),
362            IoResource::UdpSocket(_) => write!(f, "UdpSocket(...)"),
363            IoResource::ChildProcess(_) => write!(f, "ChildProcess(...)"),
364            IoResource::PipeReader(_) => write!(f, "PipeReader(...)"),
365            IoResource::PipeWriter(_) => write!(f, "PipeWriter(...)"),
366            IoResource::PipeReaderErr(_) => write!(f, "PipeReaderErr(...)"),
367            IoResource::Custom(_) => write!(f, "Custom(...)"),
368        }
369    }
370}
371
372/// Data for IoHandle variant (Arc-wrapped at the HeapValue level to keep
373/// HeapValue small and to enable cluster #2 marshal `FromSlot for
374/// Arc<IoHandleData>`).
375///
376/// Wraps an OS resource (file, socket, process) in an Arc<Mutex<Option<IoResource>>>
377/// so it can be shared and closed. The `Option` is `None` after close().
378/// Rust's `Drop` closes the underlying resource if not already closed.
379///
380/// Storage: `HeapValue::IoHandle(Arc<IoHandleData>)`. The variant Arc is
381/// the marshal-layer's typed handle (per cluster #2 option γ in
382/// `docs/defections.md` 2026-05-06); the inner `Arc<Mutex<...>>` is the
383/// shared resource lock. Cloning the variant is one atomic op.
384#[derive(Clone)]
385pub struct IoHandleData {
386    pub kind: IoHandleKind,
387    pub resource: Arc<std::sync::Mutex<Option<IoResource>>>,
388    pub path: String,
389    pub mode: String,
390}
391
392impl std::fmt::Debug for IoHandleData {
393    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        f.debug_struct("IoHandleData")
395            .field("kind", &self.kind)
396            .field("path", &self.path)
397            .field("mode", &self.mode)
398            .field(
399                "open",
400                &self.resource.lock().map(|g| g.is_some()).unwrap_or(false),
401            )
402            .finish()
403    }
404}
405
406impl IoHandleData {
407    /// Create a new file handle.
408    pub fn new_file(file: std::fs::File, path: String, mode: String) -> Self {
409        Self {
410            kind: IoHandleKind::File,
411            resource: Arc::new(std::sync::Mutex::new(Some(IoResource::File(file)))),
412            path,
413            mode,
414        }
415    }
416
417    /// Create a new TCP stream handle.
418    pub fn new_tcp_stream(stream: std::net::TcpStream, addr: String) -> Self {
419        Self {
420            kind: IoHandleKind::TcpStream,
421            resource: Arc::new(std::sync::Mutex::new(Some(IoResource::TcpStream(stream)))),
422            path: addr,
423            mode: "rw".to_string(),
424        }
425    }
426
427    /// Create a new TCP listener handle.
428    pub fn new_tcp_listener(listener: std::net::TcpListener, addr: String) -> Self {
429        Self {
430            kind: IoHandleKind::TcpListener,
431            resource: Arc::new(std::sync::Mutex::new(Some(IoResource::TcpListener(
432                listener,
433            )))),
434            path: addr,
435            mode: "listen".to_string(),
436        }
437    }
438
439    /// Create a new UDP socket handle.
440    pub fn new_udp_socket(socket: std::net::UdpSocket, addr: String) -> Self {
441        Self {
442            kind: IoHandleKind::UdpSocket,
443            resource: Arc::new(std::sync::Mutex::new(Some(IoResource::UdpSocket(socket)))),
444            path: addr,
445            mode: "rw".to_string(),
446        }
447    }
448
449    /// Create a handle wrapping a spawned child process.
450    pub fn new_child_process(child: std::process::Child, cmd: String) -> Self {
451        Self {
452            kind: IoHandleKind::ChildProcess,
453            resource: Arc::new(std::sync::Mutex::new(Some(IoResource::ChildProcess(child)))),
454            path: cmd,
455            mode: "process".to_string(),
456        }
457    }
458
459    /// Create a handle wrapping a child stdout pipe.
460    pub fn new_pipe_reader(stdout: std::process::ChildStdout, label: String) -> Self {
461        Self {
462            kind: IoHandleKind::PipeReader,
463            resource: Arc::new(std::sync::Mutex::new(Some(IoResource::PipeReader(stdout)))),
464            path: label,
465            mode: "r".to_string(),
466        }
467    }
468
469    /// Create a handle wrapping a child stdin pipe.
470    pub fn new_pipe_writer(stdin: std::process::ChildStdin, label: String) -> Self {
471        Self {
472            kind: IoHandleKind::PipeWriter,
473            resource: Arc::new(std::sync::Mutex::new(Some(IoResource::PipeWriter(stdin)))),
474            path: label,
475            mode: "w".to_string(),
476        }
477    }
478
479    /// Create a handle wrapping a child stderr pipe.
480    pub fn new_pipe_reader_err(stderr: std::process::ChildStderr, label: String) -> Self {
481        Self {
482            kind: IoHandleKind::PipeReader,
483            resource: Arc::new(std::sync::Mutex::new(Some(IoResource::PipeReaderErr(
484                stderr,
485            )))),
486            path: label,
487            mode: "r".to_string(),
488        }
489    }
490
491    /// Create a handle wrapping a custom type-erased resource.
492    pub fn new_custom(resource: Box<dyn std::any::Any + Send>, label: String) -> Self {
493        Self {
494            kind: IoHandleKind::Custom,
495            resource: Arc::new(std::sync::Mutex::new(Some(IoResource::Custom(resource)))),
496            path: label,
497            mode: "custom".to_string(),
498        }
499    }
500
501    /// Check if the handle is still open.
502    pub fn is_open(&self) -> bool {
503        self.resource.lock().map(|g| g.is_some()).unwrap_or(false)
504    }
505
506    /// Close the handle, returning true if it was open.
507    pub fn close(&self) -> bool {
508        if let Ok(mut guard) = self.resource.lock() {
509            guard.take().is_some()
510        } else {
511            false
512        }
513    }
514}
515
516// ── TraitObjectStorage now lives at lines ~1790+ (W17-trait-object-storage merge) ──
517// The placeholder shape from W17-typed-carrier-bundle-A checkpoint 1 is
518// superseded by the real `TraitObjectStorage { value: Arc<TypedObjectStorage>,
519// vtable: Arc<VTable> }` from W17-trait-object-storage's close commit
520// (`e58218c`). 4-table lockstep landed there; HeapKind::TraitObject = 29
521// is the assigned ordinal.
522
523/// Owning newtype around `*const TypedObjectStorage` carrying one
524/// v2-raw refcount share on the pointed-to allocation's HeapHeader.
525///
526/// **Wave 2 Round 4 D4 ckpt-final (2026-05-14):** redesigned to own its
527/// share. Previously a trivially-Copy transparent newtype; that shape
528/// leaks element refcounts when the enclosing `Vec<TypedObjectPtr>`
529/// drops because trivial bit-copy Drop never calls `release_elem`. Now
530/// the wrapper:
531/// - Owns one v2-raw HeapHeader-at-offset-0 refcount share.
532/// - `Clone` bumps the refcount via `v2_retain`.
533/// - `Drop` retires the share via `TypedObjectStorage::release_elem`.
534/// - `Default` is the null pointer (no refcount share owed).
535///
536/// `#[repr(transparent)]` so the in-memory layout is identical to
537/// `*const TypedObjectStorage` — zero ABI cost vs the raw pointer; the
538/// wrapper exists only to localize the manual Send/Sync impl + the
539/// Drop/Clone refcount discipline (Rust disables auto-Send/Sync for ALL
540/// instantiations of a generic struct as soon as ANY manual impl exists,
541/// so per-T newtypes are the canonical workaround for raw-ptr inner
542/// elements in generic buffers).
543///
544/// Used as the inner element type of:
545/// - `HashMapValueBuf::TypedObject(Arc<TypedBuffer<TypedObjectPtr>>)`
546/// - `TypedArrayData::TypedObject(Arc<TypedBuffer<TypedObjectPtr>>)`
547///
548/// Construction-side contract: callers transfer one strong-count share
549/// on the v2-raw HeapHeader to the new `TypedObjectPtr`. Reads via
550/// `as_ptr()` return the underlying pointer without bumping refcount.
551#[repr(transparent)]
552#[derive(Debug, PartialEq, Eq, Hash)]
553pub struct TypedObjectPtr(pub *const TypedObjectStorage);
554
555// SAFETY: `*const TypedObjectStorage` is `!Send + !Sync` by default. The
556// wrapper is safe to share across threads because:
557// (1) `TypedObjectStorage` itself is `Send + Sync` (Box<[ValueSlot]> +
558//     `Arc<[NativeKind]>` + POD fields; ValueSlot wraps `u64`).
559// (2) The HeapHeader-based refcount uses atomic ops (`v2_retain` /
560//     `v2_release` in `v2/refcount.rs`).
561// (3) Aliasing safety is the same as `Arc<TypedObjectStorage>` — multiple
562//     threads can hold their own retain shares concurrently.
563unsafe impl Send for TypedObjectPtr {}
564unsafe impl Sync for TypedObjectPtr {}
565
566impl Default for TypedObjectPtr {
567    /// Null pointer default — used by `TypedBuffer::<TypedObjectPtr>::push_null`
568    /// and similar default-requiring construction sites. Callers must
569    /// not dereference a default-constructed `TypedObjectPtr`. No
570    /// refcount share is owed for a null wrapper; Drop on a null
571    /// pointer is a no-op.
572    #[inline]
573    fn default() -> Self {
574        Self(std::ptr::null())
575    }
576}
577
578impl Clone for TypedObjectPtr {
579    /// v2-raw refcount bump via `v2_retain` on the pointed-to
580    /// HeapHeader. The clone owns its own share, retired at its own
581    /// `Drop`.
582    #[inline]
583    fn clone(&self) -> Self {
584        if !self.0.is_null() {
585            // SAFETY: per the construction-side contract, `self.0` points
586            // to a live `TypedObjectStorage` allocated via `_new` (or a
587            // legacy Arc-allocated one whose embedded HeapHeader is
588            // unused but still bumpable safely — atomic increment is
589            // sound on any aligned u32 within the legitimate allocation).
590            unsafe { crate::v2::refcount::v2_retain(&(*self.0).header) };
591        }
592        Self(self.0)
593    }
594}
595
596impl Drop for TypedObjectPtr {
597    /// Retire the owned share via `TypedObjectStorage::release_elem`
598    /// (HeapElement trait — calls `v2_release` and, on refcount=0,
599    /// runs `_drop` to dealloc the allocation + retire heap-mask
600    /// shares). No-op on null wrappers.
601    #[inline]
602    fn drop(&mut self) {
603        if !self.0.is_null() {
604            use crate::v2::heap_element::HeapElement;
605            // SAFETY: per the construction-side contract this carrier owns
606            // one share on the HeapHeader-at-offset-0 refcount.
607            unsafe { TypedObjectStorage::release_elem(self.0) };
608        }
609    }
610}
611
612impl TypedObjectPtr {
613    /// Construct from a raw pointer obtained via `TypedObjectStorage::_new`.
614    /// The caller transfers one strong-count share to the wrapper.
615    #[inline]
616    pub fn new(ptr: *const TypedObjectStorage) -> Self {
617        Self(ptr)
618    }
619
620    /// Recover the underlying raw pointer. Does NOT bump refcount;
621    /// the returned pointer is borrowed for the wrapper's lifetime.
622    #[inline]
623    pub fn as_ptr(&self) -> *const TypedObjectStorage {
624        self.0
625    }
626
627    /// Whether the pointer is null. Construction-side contract permits
628    /// null only for default-initialized cells.
629    #[inline]
630    pub fn is_null(&self) -> bool {
631        self.0.is_null()
632    }
633
634    /// Consume the wrapper without running Drop, returning the raw
635    /// pointer. The caller takes over the one refcount share. Mirror
636    /// of `Arc::into_raw`.
637    #[inline]
638    pub fn into_raw(self) -> *const TypedObjectStorage {
639        let ptr = self.0;
640        std::mem::forget(self);
641        ptr
642    }
643}
644
645// Deref to `&TypedObjectStorage` so consumer sites can read fields
646// (`s.slots`, `s.schema_id`, etc.) without manual unsafe deref. The
647// wrapper owns one refcount share for its lifetime, so the pointed-to
648// storage is live while the wrapper is in scope.
649//
650// Wave 2 Round 4 D4 ckpt-final-prime² (2026-05-14): added to support the
651// HeapValue::TypedObject(TypedObjectPtr) variant signature flip with
652// minimal consumer-cascade churn.
653impl std::ops::Deref for TypedObjectPtr {
654    type Target = TypedObjectStorage;
655    #[inline]
656    fn deref(&self) -> &TypedObjectStorage {
657        // SAFETY: per the construction-side contract, `self.0` is non-null
658        // for any `TypedObjectPtr` constructed via `new(_new(...))` or
659        // cloned from such. The default-constructed null pointer must not
660        // be dereferenced — callers reading fields are expected to hold a
661        // wrapper that owns a real share. Debug builds catch the null case
662        // via the assert; release builds UB on deref of null (mirroring
663        // `Arc<T>::deref` semantics for a null Arc — not constructable
664        // without `unsafe`). Length-zero storage is fine.
665        debug_assert!(
666            !self.0.is_null(),
667            "TypedObjectPtr::deref on null pointer (default-constructed wrapper \
668             must not be dereferenced)"
669        );
670        unsafe { &*self.0 }
671    }
672}
673
674// ── TraitObjectPtr (Wave 2 Round 4 D4 ckpt-final-prime², 2026-05-14) ────────
675
676/// Owning newtype around `*const TraitObjectStorage` carrying one
677/// v2-raw refcount share on the pointed-to allocation's HeapHeader.
678///
679/// **Wave 2 Round 4 D4 ckpt-final-prime² (2026-05-14):** mirrors the
680/// `TypedObjectPtr` precedent (above) for `TraitObjectStorage`. Carrier
681/// shape used as both:
682/// - `HeapValue::TraitObject(TraitObjectPtr)` variant payload
683/// - Future `TypedArrayData::TraitObject` element type (if/when the
684///   §Q25.A monomorphic specialization for trait-object-element arrays
685///   lands; not under this ckpt's scope)
686///
687/// `#[repr(transparent)]` so the in-memory layout is identical to
688/// `*const TraitObjectStorage` — zero ABI cost vs the raw pointer; the
689/// wrapper exists only to localize the manual Send/Sync impl + the
690/// Drop/Clone refcount discipline. Same auto-trait suppression rule
691/// applies as for TypedObjectPtr — per-T newtype is the canonical
692/// workaround for raw-ptr inner elements in HeapValue variant payloads
693/// without disabling Rust's auto-derived Send/Sync/Clone/Drop on the
694/// enclosing `HeapValue` enum.
695///
696/// Construction-side contract: callers transfer one strong-count share
697/// on the v2-raw HeapHeader (initialized to 1 via `TraitObjectStorage::_new`)
698/// to the new `TraitObjectPtr`. Reads via `as_ptr()` return the
699/// underlying pointer without bumping refcount.
700#[repr(transparent)]
701#[derive(Debug, PartialEq, Eq, Hash)]
702pub struct TraitObjectPtr(pub *const TraitObjectStorage);
703
704// SAFETY: same argument as TypedObjectPtr's Send/Sync impls — the
705// underlying storage is Send + Sync (manually unsafe impl'd on
706// TraitObjectStorage), the HeapHeader-based refcount uses atomic ops
707// (`v2_retain` / `v2_release` in `v2/refcount.rs`), and aliasing safety
708// matches `Arc<TraitObjectStorage>` — multiple threads can hold their
709// own retain shares concurrently.
710unsafe impl Send for TraitObjectPtr {}
711unsafe impl Sync for TraitObjectPtr {}
712
713impl Default for TraitObjectPtr {
714    /// Null pointer default — used by container types that need a
715    /// `Default` impl. Callers must not dereference a default-constructed
716    /// `TraitObjectPtr`. No refcount share is owed; Drop on a null
717    /// pointer is a no-op.
718    #[inline]
719    fn default() -> Self {
720        Self(std::ptr::null())
721    }
722}
723
724impl Clone for TraitObjectPtr {
725    /// v2-raw refcount bump via `v2_retain` on the pointed-to
726    /// HeapHeader. The clone owns its own share, retired at its own
727    /// `Drop`.
728    #[inline]
729    fn clone(&self) -> Self {
730        if !self.0.is_null() {
731            // SAFETY: per the construction-side contract, `self.0` points
732            // to a live `TraitObjectStorage` allocated via `_new` (or a
733            // legacy Arc-allocated one whose embedded HeapHeader is
734            // unused but still bumpable safely — atomic increment is
735            // sound on any aligned u32 within the legitimate allocation).
736            unsafe { crate::v2::refcount::v2_retain(&(*self.0).header) };
737        }
738        Self(self.0)
739    }
740}
741
742impl Drop for TraitObjectPtr {
743    /// Retire the owned share via `TraitObjectStorage::release_elem`
744    /// (HeapElement trait — calls `v2_release` and, on refcount=0,
745    /// runs `_drop` to dealloc the allocation + retire inner shares).
746    /// No-op on null wrappers.
747    #[inline]
748    fn drop(&mut self) {
749        if !self.0.is_null() {
750            use crate::v2::heap_element::HeapElement;
751            // SAFETY: per the construction-side contract this carrier owns
752            // one share on the HeapHeader-at-offset-0 refcount.
753            unsafe { TraitObjectStorage::release_elem(self.0) };
754        }
755    }
756}
757
758impl TraitObjectPtr {
759    /// Construct from a raw pointer obtained via `TraitObjectStorage::_new`.
760    /// The caller transfers one strong-count share to the wrapper.
761    #[inline]
762    pub fn new(ptr: *const TraitObjectStorage) -> Self {
763        Self(ptr)
764    }
765
766    /// Recover the underlying raw pointer. Does NOT bump refcount;
767    /// the returned pointer is borrowed for the wrapper's lifetime.
768    #[inline]
769    pub fn as_ptr(&self) -> *const TraitObjectStorage {
770        self.0
771    }
772
773    /// Whether the pointer is null.
774    #[inline]
775    pub fn is_null(&self) -> bool {
776        self.0.is_null()
777    }
778
779    /// Consume the wrapper without running Drop, returning the raw
780    /// pointer. The caller takes over the one refcount share. Mirror
781    /// of `Arc::into_raw`.
782    #[inline]
783    pub fn into_raw(self) -> *const TraitObjectStorage {
784        let ptr = self.0;
785        std::mem::forget(self);
786        ptr
787    }
788}
789
790// Same Deref-as-shortcut rationale as TypedObjectPtr — the wrapper owns
791// the share so the pointed-to storage is live for the wrapper's lifetime.
792impl std::ops::Deref for TraitObjectPtr {
793    type Target = TraitObjectStorage;
794    #[inline]
795    fn deref(&self) -> &TraitObjectStorage {
796        debug_assert!(
797            !self.0.is_null(),
798            "TraitObjectPtr::deref on null pointer (default-constructed wrapper \
799             must not be dereferenced)"
800        );
801        unsafe { &*self.0 }
802    }
803}
804
805// ── HashMap storage (Stage C P1(b), 2026-05-07) ─────────────────────────────
806//
807// Wave 2 Round 3b C2-joint ckpt-1 (2026-05-14): `HashMapValueBuf` deleted;
808// `HashMapData` replaced with `HashMapData<V>` generic per audit §C.4
809// option (a.2) — values buffer is `*mut TypedArray<V>` (per-V monomorphized
810// at compile time via the `HashMapValueElem` trait). `HashMapKindedRef`
811// enum bundles per-V `Arc<HashMapData<V>>` variants as the HeapValue-arm
812// carrier (ckpt-2 flips `HeapValue::HashMap(Arc<HashMapData>)` to
813// `HeapValue::HashMap(HashMapKindedRef)`). See ADR-006 §2.7.24 Q25.B
814// SUPERSEDED + `docs/cluster-audits/bulldozer-wave-1-inventory.md` §C.
815
816/// Per-V dispatcher trait for `HashMapData<V>::Drop` — releases the value
817/// buffer (`*mut TypedArray<V>`) at refcount-0 of the enclosing
818/// `Arc<HashMapData<V>>`.
819///
820/// Authority: ADR-006 §2.7.24 Q25.B SUPERSEDED + `bulldozer-wave-1-inventory.md`
821/// §C.4 option (a.2). The trait dispatches per-V release at compile time via
822/// the Rust type system — no runtime `NativeKind` probe, no `is_heap()` probe,
823/// no Bool-default fallback. Mirror of `v2::heap_element::HeapElement` shape,
824/// but operates on the OUTER `TypedArray<V>` allocation rather than on
825/// individual heap-element pointers.
826///
827/// Impls partition V into:
828///
829/// - POD scalar Vs (`i64`, `f64`, `u8` for Bool): `TypedArray::<V>::drop_array`
830///   frees the data buffer + the struct; no per-element work.
831/// - HeapHeader-equipped raw pointers (`*const StringObj`, `*const DecimalObj`):
832///   `TypedArray::<*const T>::drop_array_heap` walks the data buffer and
833///   calls `T::release_elem` per element, then frees the struct. Requires
834///   `T: v2::heap_element::HeapElement`.
835/// - `#[repr(transparent)]` newtype-as-element shapes (`TypedObjectPtr`,
836///   `TraitObjectPtr`): manual walk that `ptr::read`s each element to invoke
837///   its `Drop` (which retires the v2-raw refcount share via `release_elem`
838///   on the inner `*const TypedObjectStorage` / `*const TraitObjectStorage`).
839///
840/// Char (`char` codepoint) is reachable per §C.5 dead-but-derived disposition
841/// — included as a POD-scalar V.
842///
843/// # Safety
844///
845/// Implementors must guarantee:
846/// 1. `release_typed_array(ptr)` is sound when `ptr` points to a live
847///    `TypedArray<Self>` allocation produced by `TypedArray::<Self>::new` /
848///    `with_capacity` / `from_slice`.
849/// 2. After this call, `ptr` is invalid; the data buffer + struct are freed.
850/// 3. Per-element ownership semantics match the storage contract — POD
851///    elements need no per-element release; HeapHeader-equipped elements
852///    have their shares retired before the data buffer is freed.
853pub unsafe trait HashMapValueElem {
854    /// Release a `*mut TypedArray<Self>` allocation: retire per-element
855    /// shares (where applicable) + free the data buffer + free the struct.
856    ///
857    /// # Safety
858    /// `ptr` must point to a valid, live `TypedArray<Self>` allocated by
859    /// the v2-raw `TypedArray::<Self>` allocator (`new`, `with_capacity`,
860    /// `from_slice`). After this call returns, `ptr` is invalid.
861    unsafe fn release_typed_array(ptr: *mut crate::v2::typed_array::TypedArray<Self>)
862    where
863        Self: Sized;
864
865    /// Clone a single element with proper refcount-share semantics.
866    ///
867    /// - POD scalar Vs (`i64`/`f64`/`u8`/`char`): byte copy — no refcount work.
868    /// - HeapHeader-equipped raw pointers (`*const StringObj`/`*const DecimalObj`):
869    ///   pointer copy + `v2_retain` on the pointed-to HeapHeader.
870    /// - `#[repr(transparent)]` ptr-newtypes (`TypedObjectPtr`/`TraitObjectPtr`):
871    ///   delegate to the wrapper's `Clone` impl (which does v2_retain).
872    ///
873    /// Wave 2 Round 3b C2-joint ckpt-3 (2026-05-14): added to support the
874    /// per-V mutation API (insert / merge / get_share) on `HashMapData<V>`.
875    /// Per ADR-006 §2.7.24 Q25.B SUPERSEDED + audit §C.4.
876    ///
877    /// # Safety
878    /// `elem` must reference a live element of a `TypedArray<Self>` (or a
879    /// freshly-allocated element owned by the caller). The implementor
880    /// must produce a new owned share — for HeapElement / ptr-newtype V
881    /// this bumps the refcount on the pointed-to allocation; for POD V
882    /// it is a trivial copy.
883    unsafe fn share_clone(elem: &Self) -> Self
884    where
885        Self: Sized;
886
887    /// Release a single owned value (one refcount share). For POD V it is
888    /// a no-op (byte copy falls out of scope). For HeapElement V the
889    /// share is retired via `release_elem` on the pointer. For Ptr-newtype
890    /// V the wrapper's Drop runs automatically when the value drops.
891    ///
892    /// Wave 2 Round 3b C2-joint ckpt-3 (2026-05-14): added for the per-V
893    /// mutation API's overwrite path (insert when key already present —
894    /// the old value's share must be retired before the slot is overwritten).
895    ///
896    /// # Safety
897    /// `value` must be a valid owned V — for HeapElement / ptr-newtype V
898    /// types the caller transfers one refcount share to this method, which
899    /// retires it.
900    unsafe fn release_owned(value: Self)
901    where
902        Self: Sized;
903}
904
905// ── POD scalar V impls (i64 / f64 / u8 / char) ─────────────────────────────
906
907unsafe impl HashMapValueElem for i64 {
908    #[inline]
909    unsafe fn release_typed_array(ptr: *mut crate::v2::typed_array::TypedArray<Self>) {
910        // SAFETY: caller-bound contract; `i64` is Copy/POD — no per-element
911        // shares to retire.
912        unsafe { crate::v2::typed_array::TypedArray::<i64>::drop_array(ptr) }
913    }
914    #[inline]
915    unsafe fn share_clone(elem: &Self) -> Self {
916        *elem
917    }
918    #[inline]
919    unsafe fn release_owned(_value: Self) {
920        // POD: byte copy falls out of scope; no-op.
921    }
922}
923
924unsafe impl HashMapValueElem for f64 {
925    #[inline]
926    unsafe fn release_typed_array(ptr: *mut crate::v2::typed_array::TypedArray<Self>) {
927        unsafe { crate::v2::typed_array::TypedArray::<f64>::drop_array(ptr) }
928    }
929    #[inline]
930    unsafe fn share_clone(elem: &Self) -> Self {
931        *elem
932    }
933    #[inline]
934    unsafe fn release_owned(_value: Self) {}
935}
936
937unsafe impl HashMapValueElem for u8 {
938    /// Used as the `Bool` V (one byte per element).
939    #[inline]
940    unsafe fn release_typed_array(ptr: *mut crate::v2::typed_array::TypedArray<Self>) {
941        unsafe { crate::v2::typed_array::TypedArray::<u8>::drop_array(ptr) }
942    }
943    #[inline]
944    unsafe fn share_clone(elem: &Self) -> Self {
945        *elem
946    }
947    #[inline]
948    unsafe fn release_owned(_value: Self) {}
949}
950
951unsafe impl HashMapValueElem for char {
952    /// Char codepoint (4 bytes / element). Dead-but-derived per §C.5;
953    /// included for forward-cleanliness with the `HeapValue::Char`
954    /// xml/json marshal path.
955    #[inline]
956    unsafe fn release_typed_array(ptr: *mut crate::v2::typed_array::TypedArray<Self>) {
957        unsafe { crate::v2::typed_array::TypedArray::<char>::drop_array(ptr) }
958    }
959    #[inline]
960    unsafe fn share_clone(elem: &Self) -> Self {
961        *elem
962    }
963    #[inline]
964    unsafe fn release_owned(_value: Self) {}
965}
966
967// ── HeapHeader-equipped raw-pointer V impls (*const StringObj / *const DecimalObj) ──
968
969unsafe impl HashMapValueElem for *const crate::v2::string_obj::StringObj {
970    #[inline]
971    unsafe fn release_typed_array(ptr: *mut crate::v2::typed_array::TypedArray<Self>) {
972        // SAFETY: `StringObj: HeapElement`; `drop_array_heap` walks elements,
973        // calls `StringObj::release_elem` per `*const StringObj`, then frees
974        // the data buffer + struct.
975        unsafe {
976            crate::v2::typed_array::TypedArray::<*const crate::v2::string_obj::StringObj>::drop_array_heap(ptr)
977        }
978    }
979    #[inline]
980    unsafe fn share_clone(elem: &Self) -> Self {
981        // SAFETY: per the construction-side contract on
982        // `HashMapData<*const StringObj>` element buffer, *elem points at a
983        // live StringObj with HeapHeader at offset 0; v2_retain bumps the
984        // refcount via atomic increment.
985        if !elem.is_null() {
986            unsafe { crate::v2::refcount::v2_retain(&(**elem).header) };
987        }
988        *elem
989    }
990    #[inline]
991    unsafe fn release_owned(value: Self) {
992        // SAFETY: caller transfers one share on a live StringObj; route
993        // through HeapElement::release_elem to atomic-decrement + dealloc
994        // on refcount=0.
995        if !value.is_null() {
996            unsafe {
997                use crate::v2::heap_element::HeapElement;
998                crate::v2::string_obj::StringObj::release_elem(value);
999            }
1000        }
1001    }
1002}
1003
1004unsafe impl HashMapValueElem for *const crate::v2::decimal_obj::DecimalObj {
1005    #[inline]
1006    unsafe fn release_typed_array(ptr: *mut crate::v2::typed_array::TypedArray<Self>) {
1007        unsafe {
1008            crate::v2::typed_array::TypedArray::<*const crate::v2::decimal_obj::DecimalObj>::drop_array_heap(ptr)
1009        }
1010    }
1011    #[inline]
1012    unsafe fn share_clone(elem: &Self) -> Self {
1013        // SAFETY: see *const StringObj impl above. DecimalObj has
1014        // HeapHeader at offset 0 (HeapElement contract).
1015        if !elem.is_null() {
1016            unsafe { crate::v2::refcount::v2_retain(&(**elem).header) };
1017        }
1018        *elem
1019    }
1020    #[inline]
1021    unsafe fn release_owned(value: Self) {
1022        if !value.is_null() {
1023            unsafe {
1024                use crate::v2::heap_element::HeapElement;
1025                crate::v2::decimal_obj::DecimalObj::release_elem(value);
1026            }
1027        }
1028    }
1029}
1030
1031// ── Ptr-newtype V impls (TypedObjectPtr / TraitObjectPtr) ───────────────────
1032
1033unsafe impl HashMapValueElem for TypedObjectPtr {
1034    /// `TypedObjectPtr` is `#[repr(transparent)]` over `*const TypedObjectStorage`
1035    /// but has a manual `Drop` impl (calls `release_elem`). Walk the buffer
1036    /// via `ptr::read` to invoke each element's Drop (which retires the v2-raw
1037    /// HeapHeader share), then free the data allocation + struct.
1038    #[inline]
1039    unsafe fn release_typed_array(ptr: *mut crate::v2::typed_array::TypedArray<Self>) {
1040        unsafe {
1041            let arr = &*ptr;
1042            if arr.cap > 0 && !arr.data.is_null() {
1043                // Walk: read each element; the read transfers ownership to a
1044                // local `TypedObjectPtr`, which drops at scope-end via its
1045                // manual `Drop` impl (calls `release_elem` on the inner
1046                // `*const TypedObjectStorage`).
1047                for i in 0..arr.len {
1048                    let _elem: TypedObjectPtr = std::ptr::read(arr.data.add(i as usize));
1049                }
1050                let data_layout =
1051                    std::alloc::Layout::array::<TypedObjectPtr>(arr.cap as usize)
1052                        .expect("invalid array layout");
1053                std::alloc::dealloc(arr.data as *mut u8, data_layout);
1054            }
1055            let layout = std::alloc::Layout::new::<crate::v2::typed_array::TypedArray<Self>>();
1056            std::alloc::dealloc(ptr as *mut u8, layout);
1057        }
1058    }
1059    #[inline]
1060    unsafe fn share_clone(elem: &Self) -> Self {
1061        // Delegate to the wrapper's Clone impl (which bumps the v2_retain
1062        // refcount on the inner *const TypedObjectStorage's HeapHeader).
1063        elem.clone()
1064    }
1065    #[inline]
1066    unsafe fn release_owned(_value: Self) {
1067        // TypedObjectPtr has a manual Drop impl that calls release_elem;
1068        // letting `_value` go out of scope runs Drop. No explicit work.
1069    }
1070}
1071
1072unsafe impl HashMapValueElem for TraitObjectPtr {
1073    /// Mirror of the `TypedObjectPtr` impl above; per-element Drop runs
1074    /// `release_elem` on `*const TraitObjectStorage`.
1075    #[inline]
1076    unsafe fn release_typed_array(ptr: *mut crate::v2::typed_array::TypedArray<Self>) {
1077        unsafe {
1078            let arr = &*ptr;
1079            if arr.cap > 0 && !arr.data.is_null() {
1080                for i in 0..arr.len {
1081                    let _elem: TraitObjectPtr = std::ptr::read(arr.data.add(i as usize));
1082                }
1083                let data_layout =
1084                    std::alloc::Layout::array::<TraitObjectPtr>(arr.cap as usize)
1085                        .expect("invalid array layout");
1086                std::alloc::dealloc(arr.data as *mut u8, data_layout);
1087            }
1088            let layout = std::alloc::Layout::new::<crate::v2::typed_array::TypedArray<Self>>();
1089            std::alloc::dealloc(ptr as *mut u8, layout);
1090        }
1091    }
1092    #[inline]
1093    unsafe fn share_clone(elem: &Self) -> Self {
1094        // Delegate to TraitObjectPtr's Clone impl (v2_retain on inner
1095        // *const TraitObjectStorage's HeapHeader).
1096        elem.clone()
1097    }
1098    #[inline]
1099    unsafe fn release_owned(_value: Self) {
1100        // TraitObjectPtr's Drop impl runs at scope-end.
1101    }
1102}
1103
1104// ── Recursive HashMap-value V impl (HashMapKindedRef) ───────────────────────
1105//
1106// Wave N hashmap-value-v-arm follow-up (cluster-2 closure-wave-C,
1107// 2026-05-16). Per ADR-006 §2.7.24 Q25.B SUPERSEDED canonical pattern
1108// (HashMapKindedRef carrier + per-V monomorphization at the method tier)
1109// extended to a recursive HashMap-value V arm. The values buffer is a
1110// `*mut TypedArray<HashMapKindedRef>` — each element is the per-V
1111// kinded-ref payload (auto-derived Drop chains through the inner Arc).
1112//
1113// HashMapKindedRef is non-Copy and carries a Drop (auto-derived: each
1114// variant holds Arc<HashMapData<V>> whose Drop retires one strong-count
1115// share). The shape matches TypedObjectPtr / TraitObjectPtr (manual Drop
1116// + manual Clone): walk-with-ptr::read on release; delegate to the
1117// wrapper's Clone on share; let scope-end run Drop on release_owned.
1118
1119unsafe impl HashMapValueElem for HashMapKindedRef {
1120    /// `HashMapKindedRef` is non-Copy with an auto-derived Drop (each
1121    /// variant holds `Arc<HashMapData<V>>` whose Drop retires one
1122    /// strong-count share). Walk the buffer via `ptr::read` to invoke
1123    /// each element's Drop, then free the data allocation + struct.
1124    /// Mirror of the `TypedObjectPtr` / `TraitObjectPtr` impl shape.
1125    #[inline]
1126    unsafe fn release_typed_array(ptr: *mut crate::v2::typed_array::TypedArray<Self>) {
1127        unsafe {
1128            let arr = &*ptr;
1129            if arr.cap > 0 && !arr.data.is_null() {
1130                // Walk: read each element; the read transfers ownership
1131                // to a local `HashMapKindedRef`, which drops at scope-end
1132                // via its auto-derived Drop (chains through Arc::drop on
1133                // the inner `Arc<HashMapData<V_inner>>`).
1134                for i in 0..arr.len {
1135                    let _elem: HashMapKindedRef =
1136                        std::ptr::read(arr.data.add(i as usize));
1137                }
1138                let data_layout =
1139                    std::alloc::Layout::array::<HashMapKindedRef>(arr.cap as usize)
1140                        .expect("invalid array layout");
1141                std::alloc::dealloc(arr.data as *mut u8, data_layout);
1142            }
1143            let layout = std::alloc::Layout::new::<crate::v2::typed_array::TypedArray<Self>>();
1144            std::alloc::dealloc(ptr as *mut u8, layout);
1145        }
1146    }
1147    #[inline]
1148    unsafe fn share_clone(elem: &Self) -> Self {
1149        // Delegate to HashMapKindedRef's manual Clone impl (per-variant
1150        // Arc::clone on the inner `Arc<HashMapData<V_inner>>` — single
1151        // refcount bump per the §C.3 audit ground-truth).
1152        elem.clone()
1153    }
1154    #[inline]
1155    unsafe fn release_owned(_value: Self) {
1156        // HashMapKindedRef's auto-derived Drop runs at scope-end (per
1157        // variant: Arc::drop on the inner `Arc<HashMapData<V_inner>>`).
1158    }
1159}
1160
1161/// HashMap storage — keys buffer (string-typed v2-raw `*mut TypedArray<*const StringObj>`)
1162/// + per-V monomorphized values buffer (`*mut TypedArray<V>`) + eager
1163/// bucket-index for O(1) lookup.
1164///
1165/// **Wave 2 Round 3b C2-joint ckpt-1 (2026-05-14):** the parametric
1166/// `HashMapValueBuf` enum has been REPLACED with a generic type parameter `V`
1167/// constrained by `HashMapValueElem`. Per audit §C.4 option (a.2):
1168/// per-V monomorphization at compile time via the `HashMapKindedRef` carrier
1169/// (defined below). No runtime kind discriminator field on this struct (the
1170/// variant tag lives on `HashMapKindedRef` at the carrier layer).
1171///
1172/// The values buffer is a raw `*mut TypedArray<V>` — the v2-raw heap shape
1173/// (HeapHeader-at-offset-0 produced via `TypedArray::<V>::new`). Drop runs
1174/// `V::release_typed_array(self.values)` via the `HashMapValueElem` trait —
1175/// per-V monomorphized at compile time. The keys buffer is a `*mut
1176/// TypedArray<*const StringObj>` — v2-raw shape using the `HeapElement`
1177/// dispatch on `StringObj`.
1178///
1179/// Per-V monomorphizations supported at landing (mirror of §A migration):
1180/// `i64`, `f64`, `u8` (Bool), `*const StringObj`, `*const DecimalObj`,
1181/// `TypedObjectPtr`, `TraitObjectPtr`. DateTime / Timespan / Duration /
1182/// Instant / Char are dead per §C.5 (Char retained as POD-scalar arm only
1183/// for the dead-but-derived defensive path; no live root producer).
1184///
1185/// **Eager bucket-only at first landing** (preserved from Stage C): `index`
1186/// is built at construction and maintained incrementally on insert / remove.
1187/// The `shape_id` hidden-class fast-path that the pre-bulldozer architecture
1188/// used for ≤64-string-keyed-maps remains deferred to a separate
1189/// optimization workstream.
1190///
1191/// **Forbidden under Q25.B SUPERSEDED:**
1192/// - `Arc<TypedBuffer<V>>` field shape (the value-buffer carrier is
1193///   `*mut TypedArray<V>` per audit §C.4).
1194/// - HashMap-wide runtime kind discriminator on this struct (per-V
1195///   monomorphization at compile time via the carrier; no inline tag
1196///   byte on `HashMapData<V>` itself).
1197/// - Re-introducing `HashMapValueBuf` arms under any rename.
1198#[derive(Debug)]
1199pub struct HashMapData<V: HashMapValueElem> {
1200    /// Insertion-ordered keys — v2-raw `*mut TypedArray<*const StringObj>`.
1201    /// Owned by this struct (one strong-count share on the keys array's
1202    /// HeapHeader at offset 0). Drop calls `<*const StringObj as
1203    /// HashMapValueElem>::release_typed_array` to retire the share.
1204    pub keys: *mut crate::v2::typed_array::TypedArray<*const crate::v2::string_obj::StringObj>,
1205    /// Insertion-ordered values — v2-raw `*mut TypedArray<V>`. Owned by
1206    /// this struct. Drop calls `V::release_typed_array(self.values)`.
1207    pub values: *mut crate::v2::typed_array::TypedArray<V>,
1208    /// Eager bucket-index: hash → list of indices into `keys` / `values`
1209    /// arrays. Enables O(1) lookup at the user-facing `map.get(key)` path.
1210    /// Hash is computed via FNV-1a over the key string bytes.
1211    pub index: std::collections::HashMap<u64, Vec<u32>>,
1212}
1213
1214// SAFETY: `*mut TypedArray<T>` is `!Send + !Sync` by default. `HashMapData<V>`
1215// is safe to share across threads because:
1216// (1) `TypedArray<T>` is HeapHeader-equipped with atomic refcount ops
1217//     (`v2_retain` / `v2_release` in `v2/refcount.rs`).
1218// (2) Element-level Send/Sync is preserved by the `HashMapValueElem` impls
1219//     (StringObj / DecimalObj / TypedObjectPtr / TraitObjectPtr all carry
1220//     manual `unsafe impl Send + Sync`; scalar V types are auto-Send/Sync).
1221// (3) `HashMapData<V>` is treated as immutable at the marshal boundary;
1222//     mutation goes through `Arc::make_mut` on the consumer side (ckpt-3
1223//     territory).
1224unsafe impl<V: HashMapValueElem> Send for HashMapData<V> {}
1225unsafe impl<V: HashMapValueElem> Sync for HashMapData<V> {}
1226
1227impl<V: HashMapValueElem> HashMapData<V> {
1228    /// Build an empty HashMapData with no entries.
1229    ///
1230    /// Allocates two v2-raw `TypedArray` storages (keys + values) at
1231    /// capacity 0. The struct owns one strong-count share on each.
1232    pub fn new() -> Self {
1233        Self {
1234            // `*const StringObj` is `Copy` (raw pointer), so the Copy-bounded
1235            // `TypedArray::<*const StringObj>::new` works here.
1236            keys: crate::v2::typed_array::TypedArray::<
1237                *const crate::v2::string_obj::StringObj,
1238            >::new(),
1239            // V may be non-Copy (e.g. `TypedObjectPtr` has manual `Drop`),
1240            // so use the non-Copy `TypedArray::<V>::new_generic` path
1241            // (allocation only; no element-level reads/writes).
1242            values: crate::v2::typed_array::TypedArray::<V>::new_generic(),
1243            index: std::collections::HashMap::new(),
1244        }
1245    }
1246
1247    /// Build from parallel buffers — caller transfers one strong-count share
1248    /// on each `TypedArray` to this struct. Computes the bucket index eagerly
1249    /// from the keys buffer.
1250    ///
1251    /// # Safety
1252    /// `keys` must point to a live `TypedArray<*const StringObj>` and
1253    /// `values` to a live `TypedArray<V>`, both with at least one
1254    /// strong-count share owned by the caller (transferred to this struct).
1255    /// `keys.len` must equal `values.len`.
1256    pub unsafe fn from_pairs(
1257        keys: *mut crate::v2::typed_array::TypedArray<*const crate::v2::string_obj::StringObj>,
1258        values: *mut crate::v2::typed_array::TypedArray<V>,
1259    ) -> Self {
1260        // SAFETY: caller-bound contract per docstring.
1261        let (n_keys, n_values) = unsafe {
1262            (
1263                // keys is `TypedArray<*const StringObj>` — Copy-bounded `len`.
1264                crate::v2::typed_array::TypedArray::len(keys),
1265                // values is `TypedArray<V>` where V may be non-Copy — use the
1266                // non-Copy `len_generic`.
1267                crate::v2::typed_array::TypedArray::len_generic(values),
1268            )
1269        };
1270        assert_eq!(
1271            n_keys, n_values,
1272            "HashMapData::from_pairs: keys/values length mismatch \
1273             (keys.len={}, values.len={})",
1274            n_keys, n_values,
1275        );
1276
1277        // Build the bucket index from the keys buffer. Walks `*const StringObj`
1278        // pointers without taking ownership; each `StringObj::as_str` is a
1279        // borrow that's valid while the keys buffer is alive.
1280        let mut index: std::collections::HashMap<u64, Vec<u32>> =
1281            std::collections::HashMap::new();
1282        // SAFETY: keys is live + len is the element count.
1283        let keys_slice: &[*const crate::v2::string_obj::StringObj] = unsafe {
1284            crate::v2::typed_array::TypedArray::as_slice(keys)
1285        };
1286        for (i, &key_ptr) in keys_slice.iter().enumerate() {
1287            // SAFETY: keys-buffer elements are live `*const StringObj` per the
1288            // construction-side contract (caller owned one share each; that
1289            // share moved into the buffer at `TypedArray::push`).
1290            let key_bytes = unsafe {
1291                let len = (*key_ptr).len as usize;
1292                if len == 0 {
1293                    &[][..]
1294                } else {
1295                    std::slice::from_raw_parts((*key_ptr).data, len)
1296                }
1297            };
1298            index
1299                .entry(fnv1a_hash(key_bytes))
1300                .or_default()
1301                .push(i as u32);
1302        }
1303
1304        Self {
1305            keys,
1306            values,
1307            index,
1308        }
1309    }
1310
1311    /// Number of entries.
1312    #[inline]
1313    pub fn len(&self) -> usize {
1314        // SAFETY: `self.keys` is live for the lifetime of `&self`.
1315        unsafe { crate::v2::typed_array::TypedArray::len(self.keys) as usize }
1316    }
1317
1318    /// Whether the map is empty.
1319    #[inline]
1320    pub fn is_empty(&self) -> bool {
1321        self.len() == 0
1322    }
1323
1324    /// Read the value at index `i`. Returns a copy of the `V` element
1325    /// (POD scalars copy bytes; `*const StringObj` / `*const DecimalObj` /
1326    /// `TypedObjectPtr` / `TraitObjectPtr` copy the pointer bits — caller
1327    /// must NOT treat the returned value as carrying refcount ownership
1328    /// unless the caller explicitly bumps the v2-raw refcount).
1329    ///
1330    /// For owned-share semantics use `value_at_owned` (ckpt-2 / ckpt-3
1331    /// territory; not in scope for ckpt-1 foundation).
1332    ///
1333    /// # Safety
1334    /// `i` must be less than `self.len()`.
1335    #[inline]
1336    pub unsafe fn value_at_raw(&self, i: usize) -> V
1337    where
1338        V: Copy,
1339    {
1340        // SAFETY: caller-bound bounds contract + values is live.
1341        unsafe {
1342            crate::v2::typed_array::TypedArray::get_unchecked(self.values, i as u32)
1343        }
1344    }
1345
1346    /// Look up a value by string key. Returns `Some(i)` (the index into the
1347    /// values buffer) if the key is present, else `None`. The returned
1348    /// index can be used with `value_at_raw` / `Arc::make_mut`-based
1349    /// mutation paths (ckpt-3 territory).
1350    ///
1351    /// O(1) via the bucket index plus a short bucket scan for collision
1352    /// disambiguation.
1353    pub fn get_index(&self, key: &str) -> Option<usize> {
1354        let hash = fnv1a_hash(key.as_bytes());
1355        let bucket = self.index.get(&hash)?;
1356        // SAFETY: keys is live; len is the bucket-recorded element count.
1357        let keys_slice = unsafe {
1358            crate::v2::typed_array::TypedArray::as_slice(self.keys)
1359        };
1360        for &idx in bucket {
1361            let i = idx as usize;
1362            // SAFETY: key-buffer elements are live `*const StringObj`.
1363            let stored = unsafe { keys_slice[i] };
1364            let stored_str = unsafe { crate::v2::string_obj::StringObj::as_str(stored) };
1365            if stored_str == key {
1366                return Some(i);
1367            }
1368        }
1369        None
1370    }
1371
1372    /// Whether the map contains the given key.
1373    #[inline]
1374    pub fn contains_key(&self, key: &str) -> bool {
1375        self.get_index(key).is_some()
1376    }
1377
1378    // ── Mutation API (Wave 2 Round 3b C2-joint ckpt-3, 2026-05-14) ────────
1379    //
1380    // Per-V mutation surface mirroring `HashSetData::insert/remove` shape
1381    // (line 1514+ above) but with parallel values-buffer maintenance.
1382    // ADR-006 §2.7.24 Q25.B SUPERSEDED + audit §C.4 option (a.2).
1383    //
1384    // Uses raw `ptr::write` / `ptr::read` against the `*mut TypedArray<V>`
1385    // values buffer (bypassing TypedArray::<T:Copy>::push/pop since
1386    // `TypedObjectPtr` / `TraitObjectPtr` are non-Copy). `HashMapValueElem
1387    // ::share_clone` handles per-V refcount-aware copying when callers need
1388    // to clone elements (e.g. `merge`).
1389    //
1390    // Caller-managed ownership: `insert`/`insert_share` take a `V` by value,
1391    // transferring one share. `remove` returns the `V` by value, transferring
1392    // the share to the caller. `merge` uses `share_clone` to bump shares on
1393    // the source's elements before inserting them locally.
1394
1395    /// Insert a key/value pair, transferring one share on `value` to the
1396    /// map. If the key was already present, the old value's share is
1397    /// retired (via `V::release_typed_array`-style single-element drop)
1398    /// and the slot is overwritten with `value`. Returns `true` on
1399    /// new-key insert, `false` on overwrite.
1400    ///
1401    /// The `key` is allocated as a new `StringObj` (one fresh
1402    /// `v2_retain`=1 share owned by the map).
1403    ///
1404    /// # Safety
1405    /// `value` must be a valid owned V — for HeapElement / ptr-newtype V
1406    /// types, the caller must transfer ownership of one refcount share
1407    /// to this method. POD V types (`i64`/`f64`/`u8`/`char`) trivially
1408    /// own themselves.
1409    pub unsafe fn insert(&mut self, key: &str, value: V) -> bool {
1410        let hash = fnv1a_hash(key.as_bytes());
1411        // Check for existing key — overwrite path.
1412        if let Some(bucket) = self.index.get(&hash) {
1413            for &idx in bucket {
1414                let i = idx as usize;
1415                // SAFETY: keys live + index points into keys range.
1416                let stored_ptr = unsafe {
1417                    crate::v2::typed_array::TypedArray::get_unchecked(self.keys, idx)
1418                };
1419                let stored_str = unsafe { crate::v2::string_obj::StringObj::as_str(stored_ptr) };
1420                if stored_str == key {
1421                    // Overwrite: read+drop the old value, write the new.
1422                    unsafe {
1423                        let data_ptr = (*self.values).data.add(i);
1424                        let old_value: V = std::ptr::read(data_ptr);
1425                        // Drop the old value's share by walking through a
1426                        // single-element TypedArray. Simpler: rely on V's
1427                        // Drop impl (or for HeapElement V types, manual
1428                        // release_elem). For uniformity, route through the
1429                        // share_clone-aware path: since old_value is owned,
1430                        // letting it go out of scope at end of this block
1431                        // invokes its Drop impl (TypedObjectPtr/TraitObjectPtr
1432                        // Drop calls release_elem; *const StringObj has no
1433                        // Drop — we must manually release).
1434                        Self::drop_owned_value(old_value);
1435                        std::ptr::write(data_ptr, value);
1436                    }
1437                    return false;
1438                }
1439            }
1440        }
1441        // New-key insert path. Allocate new StringObj for the key.
1442        let key_obj: *const crate::v2::string_obj::StringObj =
1443            crate::v2::string_obj::StringObj::new(key);
1444        let new_idx_u32 = unsafe { crate::v2::typed_array::TypedArray::len(self.keys) };
1445        // Push key into the StringObj keys buffer (Copy-bounded *const T).
1446        unsafe { crate::v2::typed_array::TypedArray::push(self.keys, key_obj) };
1447        // Push value into the values buffer via raw write (V may be non-Copy).
1448        unsafe { Self::values_push(self.values, value) };
1449        // Update bucket index.
1450        self.index.entry(hash).or_default().push(new_idx_u32);
1451        true
1452    }
1453
1454    /// Remove the entry under `key`. Returns the removed value (transferring
1455    /// one share to the caller) if present, else `None`.
1456    ///
1457    /// The bucket index is updated to reflect the buffer's post-removal
1458    /// indices: every entry after the removed slot shifts down by one
1459    /// position (mirror of `HashSetData::remove`).
1460    pub unsafe fn remove(&mut self, key: &str) -> Option<V> {
1461        let hash = fnv1a_hash(key.as_bytes());
1462        let removed_idx: usize = {
1463            let bucket = self.index.get(&hash)?;
1464            let mut found: Option<usize> = None;
1465            for (bucket_pos, &idx) in bucket.iter().enumerate() {
1466                // SAFETY: keys live.
1467                let stored_ptr = unsafe {
1468                    crate::v2::typed_array::TypedArray::get_unchecked(self.keys, idx)
1469                };
1470                let stored_str = unsafe { crate::v2::string_obj::StringObj::as_str(stored_ptr) };
1471                if stored_str == key {
1472                    found = Some(bucket_pos);
1473                    break;
1474                }
1475            }
1476            let bucket_pos = found?;
1477            let bucket = self.index.get_mut(&hash).expect("bucket present");
1478            let removed_idx = bucket.swap_remove(bucket_pos) as usize;
1479            if bucket.is_empty() {
1480                self.index.remove(&hash);
1481            }
1482            removed_idx
1483        };
1484        // Read the value out (transferring share to caller).
1485        let removed_value: V = unsafe {
1486            let data_ptr = (*self.values).data.add(removed_idx);
1487            std::ptr::read(data_ptr)
1488        };
1489        // Read the key pointer out + release its share.
1490        let removed_key: *const crate::v2::string_obj::StringObj = unsafe {
1491            crate::v2::typed_array::TypedArray::get_unchecked(self.keys, removed_idx as u32)
1492        };
1493        unsafe {
1494            use crate::v2::heap_element::HeapElement;
1495            crate::v2::string_obj::StringObj::release_elem(removed_key);
1496        }
1497        // Shift remaining elements down by one (compact the buffers).
1498        let n_keys = unsafe { crate::v2::typed_array::TypedArray::len(self.keys) } as usize;
1499        unsafe {
1500            let keys_data = (*self.keys).data;
1501            let values_data = (*self.values).data;
1502            for j in removed_idx..n_keys - 1 {
1503                std::ptr::write(keys_data.add(j), std::ptr::read(keys_data.add(j + 1)));
1504                std::ptr::write(values_data.add(j), std::ptr::read(values_data.add(j + 1)));
1505            }
1506            (*self.keys).len -= 1;
1507            (*self.values).len -= 1;
1508        }
1509        // Renumber the bucket index entries pointing past the removed slot.
1510        for bucket in self.index.values_mut() {
1511            for slot in bucket.iter_mut() {
1512                if (*slot as usize) > removed_idx {
1513                    *slot -= 1;
1514                }
1515            }
1516        }
1517        Some(removed_value)
1518    }
1519
1520    /// Look up a value by key. Returns a *share-cloned* copy of the stored
1521    /// value (the caller takes one fresh share — for POD V trivial copy;
1522    /// for HeapElement / ptr-newtype V the v2_retain happens via
1523    /// `HashMapValueElem::share_clone`).
1524    ///
1525    /// Returns `None` if the key is absent.
1526    pub fn get_share(&self, key: &str) -> Option<V> {
1527        let i = self.get_index(key)?;
1528        // SAFETY: i < len(values).
1529        let elem_ref: &V = unsafe { &*(*self.values).data.add(i) };
1530        Some(unsafe { V::share_clone(elem_ref) })
1531    }
1532
1533    /// Merge `other`'s entries into `self`, last-write-wins on key
1534    /// collision. Each value from `other` is share-cloned before being
1535    /// inserted (so `other`'s shares are preserved).
1536    pub unsafe fn merge(&mut self, other: &Self) {
1537        let n = other.len();
1538        for i in 0..n {
1539            // SAFETY: i < other.len().
1540            let key_ptr = unsafe {
1541                crate::v2::typed_array::TypedArray::get_unchecked(other.keys, i as u32)
1542            };
1543            let key_str = unsafe { crate::v2::string_obj::StringObj::as_str(key_ptr) };
1544            let value_ref: &V = unsafe { &*(*other.values).data.add(i) };
1545            let cloned_value = unsafe { V::share_clone(value_ref) };
1546            unsafe { self.insert(key_str, cloned_value) };
1547        }
1548    }
1549
1550    /// Push a single value onto the values buffer, growing the data
1551    /// allocation if needed. Bypasses `TypedArray::<T: Copy>::push` so
1552    /// non-Copy V types (TypedObjectPtr/TraitObjectPtr) work too.
1553    ///
1554    /// # Safety
1555    /// `values` must point to a live `TypedArray<V>`; `value` must be a
1556    /// valid owned V (caller transfers one share).
1557    unsafe fn values_push(values: *mut crate::v2::typed_array::TypedArray<V>, value: V) {
1558        use std::alloc::{alloc, realloc, Layout};
1559        unsafe {
1560            let arr = &mut *values;
1561            if arr.len == arr.cap {
1562                // Grow (doubling, min 4).
1563                let new_cap = if arr.cap == 0 { 4u32 } else { arr.cap.checked_mul(2).expect("capacity overflow") };
1564                let new_layout = Layout::array::<V>(new_cap as usize).expect("invalid array layout");
1565                let new_data = if arr.cap == 0 || arr.data.is_null() {
1566                    alloc(new_layout) as *mut V
1567                } else {
1568                    let old_layout = Layout::array::<V>(arr.cap as usize).expect("invalid array layout");
1569                    realloc(arr.data as *mut u8, old_layout, new_layout.size()) as *mut V
1570                };
1571                assert!(!new_data.is_null(), "reallocation failed for HashMapData<V> values");
1572                arr.data = new_data;
1573                arr.cap = new_cap;
1574            }
1575            std::ptr::write(arr.data.add(arr.len as usize), value);
1576            arr.len += 1;
1577        }
1578    }
1579
1580    /// Drop an owned value, retiring its refcount share if it owns one.
1581    /// Per-V dispatch via `HashMapValueElem::release_owned`. For POD V
1582    /// (i64/f64/u8/char) this is a no-op; for HeapElement V the share is
1583    /// retired via `release_elem`; for Ptr-newtype V the wrapper's Drop
1584    /// impl runs at scope-end.
1585    ///
1586    /// # Safety
1587    /// `value` must be a valid owned V — callers transfer one share to
1588    /// this method; the method retires it.
1589    unsafe fn drop_owned_value(value: V) {
1590        unsafe { V::release_owned(value) }
1591    }
1592}
1593
1594impl<V: HashMapValueElem> Default for HashMapData<V> {
1595    fn default() -> Self {
1596        Self::new()
1597    }
1598}
1599
1600impl<V: HashMapValueElem> Drop for HashMapData<V> {
1601    /// Retire the per-buffer strong-count shares: keys (via
1602    /// `<*const StringObj as HashMapValueElem>::release_typed_array`) +
1603    /// values (via `V::release_typed_array`). Per-V monomorphized at compile
1604    /// time — no runtime kind probe.
1605    fn drop(&mut self) {
1606        if !self.keys.is_null() {
1607            // SAFETY: `self.keys` was allocated via the v2-raw
1608            // `TypedArray::<*const StringObj>` allocator and owns one
1609            // strong-count share. After this call `self.keys` is invalid.
1610            unsafe {
1611                <*const crate::v2::string_obj::StringObj as HashMapValueElem>::release_typed_array(
1612                    self.keys,
1613                )
1614            }
1615        }
1616        if !self.values.is_null() {
1617            // SAFETY: `self.values` was allocated via the v2-raw
1618            // `TypedArray::<V>` allocator and owns one strong-count share.
1619            // Per-V dispatcher via `HashMapValueElem`.
1620            unsafe { V::release_typed_array(self.values) }
1621        }
1622    }
1623}
1624
1625/// Clone-on-write impl for `HashMapData<V>` (Wave 2 Round 3b C2-joint
1626/// ckpt-3, 2026-05-14). Allocates fresh keys + values buffers and
1627/// share-clones each element per the per-V `HashMapValueElem::share_clone`
1628/// dispatcher (and `v2_retain` on each key via the *const StringObj impl).
1629/// The fresh `HashMapData<V>` owns one refcount share on each per-element
1630/// allocation; the source's shares are untouched.
1631///
1632/// This impl is required for `Arc::make_mut(&mut Arc<HashMapData<V>>)` to
1633/// work at the consumer side (clone-on-write at the dispatch shell). Per
1634/// ADR-006 §2.7.24 Q25.B SUPERSEDED + audit §C.4 option (a.2).
1635impl<V: HashMapValueElem> Clone for HashMapData<V> {
1636    fn clone(&self) -> Self {
1637        let n = self.len();
1638        // Allocate fresh keys buffer with capacity n (Copy-bounded
1639        // `with_capacity` since *const StringObj is Copy).
1640        let new_keys = crate::v2::typed_array::TypedArray::<
1641            *const crate::v2::string_obj::StringObj,
1642        >::with_capacity(n as u32);
1643        // Allocate fresh values buffer with capacity n. Use the non-Copy
1644        // generic variant so V can be either Copy (POD/raw pointers) or
1645        // non-Copy (TypedObjectPtr / TraitObjectPtr).
1646        let new_values = crate::v2::typed_array::TypedArray::<V>::with_capacity_generic(n as u32);
1647        // Walk source elements; share_clone keys + values into the new buffers.
1648        unsafe {
1649            for i in 0..n {
1650                let key_ptr = crate::v2::typed_array::TypedArray::get_unchecked(
1651                    self.keys, i as u32,
1652                );
1653                // Share-clone the key (v2_retain on the *const StringObj).
1654                let cloned_key = <*const crate::v2::string_obj::StringObj
1655                    as HashMapValueElem>::share_clone(&key_ptr);
1656                std::ptr::write((*new_keys).data.add(i), cloned_key);
1657                let value_ref: &V = &*(*self.values).data.add(i);
1658                let cloned_value = V::share_clone(value_ref);
1659                std::ptr::write((*new_values).data.add(i), cloned_value);
1660            }
1661            (*new_keys).len = n as u32;
1662            (*new_values).len = n as u32;
1663        }
1664        Self {
1665            keys: new_keys,
1666            values: new_values,
1667            index: self.index.clone(),
1668        }
1669    }
1670}
1671
1672/// HashMapKindedRef — kinded carrier for `Arc<HashMapData<V>>` per audit
1673/// §C.4 option (a.2). Bundles per-V monomorphized payload types as enum
1674/// variants; the variant tag IS the `NativeKind` discriminator at the
1675/// carrier layer.
1676///
1677/// Used as the `HeapValue::HashMap` arm payload (ckpt-2 flips the variant
1678/// signature). Stays within shape-value / shape-runtime / shape-vm internal
1679/// Rust boundaries per ADR-006 §2.7.5 Cross-crate ABI policy — does NOT
1680/// leak into the extension contract raw-bits ABI at `module_exports.rs:21`.
1681///
1682/// **Manual Drop + Clone discipline** mirroring `TypedObjectPtr`: the
1683/// auto-derived `Drop` / `Clone` on the enclosing `HeapValue` enum chains
1684/// through `HashMapKindedRef`'s manual impls, which dispatch to per-variant
1685/// `Arc::drop` / `Arc::clone` on the typed inner `Arc<HashMapData<V>>`.
1686///
1687/// Per-V variants supported at landing (mirror of §C.4 audit shape;
1688/// post-D4 TypedObjectPtr canonical pattern):
1689///
1690/// - `I64` — `Arc<HashMapData<i64>>`
1691/// - `F64` — `Arc<HashMapData<f64>>`
1692/// - `Bool` — `Arc<HashMapData<u8>>`
1693/// - `Char` — `Arc<HashMapData<char>>` (dead-but-derived per §C.5)
1694/// - `String` — `Arc<HashMapData<*const StringObj>>`
1695/// - `Decimal` — `Arc<HashMapData<*const DecimalObj>>`
1696/// - `TypedObject` — `Arc<HashMapData<TypedObjectPtr>>`
1697/// - `TraitObject` — `Arc<HashMapData<TraitObjectPtr>>`
1698///
1699/// **Forbidden** (per CLAUDE.md broader-family regex + Q25.B SUPERSEDED
1700/// post-supersession #1):
1701///
1702/// - "HashMapKindedRef shim" / "HashMapKindedRef bridge" / "kinded-ref helper"
1703///   framing — refused on sight; the Ref-suffix is canonical per ADR-006
1704///   §2.7.6 / Q8 carrier-API-bound naming. Mirror of `KindedSlot::from_X`
1705///   constructor-shape; not a shim.
1706/// - Re-introducing `HashMapValueBuf` arms inside or alongside this enum
1707///   ("Q25.B-inside-enum carriers retained" / "documented intentional
1708///   duality"). The Wave 2 cadence shift authorization stands — per-V
1709///   monomorphization at the method tier with HashMapKindedRef carrier is
1710///   the deletion target, NOT a preserved-alongside alternative.
1711/// - HashMap-wide runtime kind discriminator on `HashMapData<V>` itself
1712///   (per audit §C.4 rationale: per-V monomorphization at compile time via
1713///   this carrier API; NO inline tag byte on `HashMapData<V>`).
1714#[derive(Debug)]
1715pub enum HashMapKindedRef {
1716    /// `Arc<HashMapData<i64>>` — V = i64 (POD scalar).
1717    I64(Arc<HashMapData<i64>>),
1718    /// `Arc<HashMapData<f64>>` — V = f64 (POD scalar).
1719    F64(Arc<HashMapData<f64>>),
1720    /// `Arc<HashMapData<u8>>` — V = u8 (Bool; one byte per element).
1721    Bool(Arc<HashMapData<u8>>),
1722    /// `Arc<HashMapData<char>>` — V = char (codepoint; dead-but-derived per §C.5).
1723    Char(Arc<HashMapData<char>>),
1724    /// `Arc<HashMapData<*const StringObj>>` — V = `*const StringObj`
1725    /// (HeapElement-equipped raw pointer).
1726    String(Arc<HashMapData<*const crate::v2::string_obj::StringObj>>),
1727    /// `Arc<HashMapData<*const DecimalObj>>` — V = `*const DecimalObj`
1728    /// (HeapElement-equipped raw pointer).
1729    Decimal(Arc<HashMapData<*const crate::v2::decimal_obj::DecimalObj>>),
1730    /// `Arc<HashMapData<TypedObjectPtr>>` — V = `TypedObjectPtr`
1731    /// (#[repr(transparent)] newtype over `*const TypedObjectStorage`,
1732    /// per ADR-006 §2.3 amendment D4 ckpt-final-prime² canonical pattern).
1733    TypedObject(Arc<HashMapData<TypedObjectPtr>>),
1734    /// `Arc<HashMapData<TraitObjectPtr>>` — V = `TraitObjectPtr`
1735    /// (#[repr(transparent)] newtype over `*const TraitObjectStorage`).
1736    TraitObject(Arc<HashMapData<TraitObjectPtr>>),
1737    /// `Arc<HashMapData<HashMapKindedRef>>` — V = `HashMapKindedRef` itself
1738    /// (recursive carrier). The inner HashMaps' values buffer is a flat
1739    /// array of `HashMapKindedRef` payloads (per-V kinded refs, each
1740    /// holding its own `Arc<HashMapData<V_inner>>`). Used by
1741    /// `HashMap.groupBy` to produce `HashMap<string, HashMap>` outputs.
1742    ///
1743    /// Wave N hashmap-value-v-arm follow-up (cluster-2 closure-wave-C,
1744    /// 2026-05-16). Per ADR-006 §2.7.24 Q25.B SUPERSEDED canonical
1745    /// pattern (HashMapKindedRef carrier + per-V monomorphization at
1746    /// the method tier) extended naturally to a recursive HashMap-value
1747    /// V arm via the existing `HashMapValueElem` trait dispatch shape.
1748    HashMap(Arc<HashMapData<HashMapKindedRef>>),
1749}
1750
1751impl Clone for HashMapKindedRef {
1752    /// Per-variant `Arc::clone` — single refcount bump on the inner
1753    /// `Arc<HashMapData<V>>`. No structural copy.
1754    fn clone(&self) -> Self {
1755        match self {
1756            HashMapKindedRef::I64(arc) => HashMapKindedRef::I64(Arc::clone(arc)),
1757            HashMapKindedRef::F64(arc) => HashMapKindedRef::F64(Arc::clone(arc)),
1758            HashMapKindedRef::Bool(arc) => HashMapKindedRef::Bool(Arc::clone(arc)),
1759            HashMapKindedRef::Char(arc) => HashMapKindedRef::Char(Arc::clone(arc)),
1760            HashMapKindedRef::String(arc) => HashMapKindedRef::String(Arc::clone(arc)),
1761            HashMapKindedRef::Decimal(arc) => HashMapKindedRef::Decimal(Arc::clone(arc)),
1762            HashMapKindedRef::TypedObject(arc) => HashMapKindedRef::TypedObject(Arc::clone(arc)),
1763            HashMapKindedRef::TraitObject(arc) => HashMapKindedRef::TraitObject(Arc::clone(arc)),
1764            HashMapKindedRef::HashMap(arc) => HashMapKindedRef::HashMap(Arc::clone(arc)),
1765        }
1766    }
1767}
1768
1769// Drop is auto-derived: each variant holds `Arc<HashMapData<V>>` whose Drop
1770// retires one strong-count share; on refcount-0 the inner `HashMapData<V>::Drop`
1771// runs and retires keys + values buffer shares via the `HashMapValueElem`
1772// dispatch. No manual `impl Drop` needed.
1773
1774impl HashMapKindedRef {
1775    /// The per-V `NativeKind` discriminator for the values buffer of this
1776    /// HashMap. Used at carrier boundaries (e.g. `HashMap.values()`
1777    /// projection to `TypedArrayData::<V>` arm + the parallel-kind stack
1778    /// track at §2.7.7 / Q9 stack reads of HashMap-iter yields) to feed
1779    /// the per-V Arc into the matching `KindedSlot::from_*` constructor.
1780    ///
1781    /// Per ADR-006 §2.7.6 / Q8 carrier-API-bound rule: one accessor per
1782    /// `NativeKind` heap variant — no per-V escape-hatch accessor (e.g.
1783    /// `as_string_arc()` returning `Arc<HashMapData<*const StringObj>>`)
1784    /// at this layer; consumers destructure the enum to recover the
1785    /// typed inner Arc.
1786    ///
1787    /// **Per-V NativeKind mapping** (Wave 2 Round 3b C2-joint ckpt-2
1788    /// 2026-05-14):
1789    ///
1790    /// - `I64` → `NativeKind::Int64`
1791    /// - `F64` → `NativeKind::Float64`
1792    /// - `Bool` → `NativeKind::Bool`
1793    /// - `Char` → `NativeKind::Char` (dead-but-derived per §C.5)
1794    /// - `String` → `NativeKind::Ptr(HeapKind::String)`
1795    /// - `Decimal` → `NativeKind::Ptr(HeapKind::Decimal)`
1796    /// - `TypedObject` → `NativeKind::Ptr(HeapKind::TypedObject)`
1797    /// - `TraitObject` → `NativeKind::Ptr(HeapKind::TraitObject)`
1798    /// - `HashMap` → `NativeKind::Ptr(HeapKind::HashMap)` (recursive carrier;
1799    ///   Wave N hashmap-value-v-arm follow-up 2026-05-16)
1800    ///
1801    /// **StringV2 / DecimalV2 gate-flip dependency note:** at ckpt-2
1802    /// landing time (2026-05-14), the v2-raw `StringV2` / `DecimalV2`
1803    /// `NativeKind` variants were proposed in Round 3a' but the
1804    /// gate-flip from `NativeKind::Ptr(HeapKind::String)` →
1805    /// `NativeKind::StringV2` (et al.) had not propagated across all
1806    /// carrier APIs. This accessor maps `String` and `Decimal` arms to
1807    /// the heap-pointer variant per the post-3a-flip baseline; if a
1808    /// future gate-flip moves the canonical surface to StringV2/DecimalV2,
1809    /// this mapping is updated lockstep at the same wave (ckpt-3 or
1810    /// follow-up).
1811    #[inline]
1812    pub fn values_kind(&self) -> crate::NativeKind {
1813        use crate::NativeKind;
1814        match self {
1815            HashMapKindedRef::I64(_) => NativeKind::Int64,
1816            HashMapKindedRef::F64(_) => NativeKind::Float64,
1817            HashMapKindedRef::Bool(_) => NativeKind::Bool,
1818            HashMapKindedRef::Char(_) => NativeKind::Char,
1819            HashMapKindedRef::String(_) => NativeKind::Ptr(HeapKind::String),
1820            HashMapKindedRef::Decimal(_) => NativeKind::Ptr(HeapKind::Decimal),
1821            HashMapKindedRef::TypedObject(_) => NativeKind::Ptr(HeapKind::TypedObject),
1822            HashMapKindedRef::TraitObject(_) => NativeKind::Ptr(HeapKind::TraitObject),
1823            HashMapKindedRef::HashMap(_) => NativeKind::Ptr(HeapKind::HashMap),
1824        }
1825    }
1826
1827    /// Number of entries in the HashMap. Dispatches per-V to the inner
1828    /// `HashMapData<V>::len()` (same impl for every V — the keys buffer
1829    /// length, which equals the values buffer length per the from_pairs
1830    /// invariant).
1831    #[inline]
1832    pub fn len(&self) -> usize {
1833        match self {
1834            HashMapKindedRef::I64(arc) => arc.len(),
1835            HashMapKindedRef::F64(arc) => arc.len(),
1836            HashMapKindedRef::Bool(arc) => arc.len(),
1837            HashMapKindedRef::Char(arc) => arc.len(),
1838            HashMapKindedRef::String(arc) => arc.len(),
1839            HashMapKindedRef::Decimal(arc) => arc.len(),
1840            HashMapKindedRef::TypedObject(arc) => arc.len(),
1841            HashMapKindedRef::TraitObject(arc) => arc.len(),
1842            HashMapKindedRef::HashMap(arc) => arc.len(),
1843        }
1844    }
1845
1846    /// Whether the map is empty (zero entries). Dispatches per-V via `len()`.
1847    #[inline]
1848    pub fn is_empty(&self) -> bool {
1849        self.len() == 0
1850    }
1851
1852    /// Whether the map contains the given key. Dispatches per-V via the
1853    /// inner `HashMapData<V>::contains_key` (same impl for every V — keys
1854    /// are stringly-typed, so the lookup is V-agnostic).
1855    #[inline]
1856    pub fn contains_key(&self, key: &str) -> bool {
1857        match self {
1858            HashMapKindedRef::I64(arc) => arc.contains_key(key),
1859            HashMapKindedRef::F64(arc) => arc.contains_key(key),
1860            HashMapKindedRef::Bool(arc) => arc.contains_key(key),
1861            HashMapKindedRef::Char(arc) => arc.contains_key(key),
1862            HashMapKindedRef::String(arc) => arc.contains_key(key),
1863            HashMapKindedRef::Decimal(arc) => arc.contains_key(key),
1864            HashMapKindedRef::TypedObject(arc) => arc.contains_key(key),
1865            HashMapKindedRef::TraitObject(arc) => arc.contains_key(key),
1866            HashMapKindedRef::HashMap(arc) => arc.contains_key(key),
1867        }
1868    }
1869
1870    /// The `HeapKind` discriminator for `KindedSlot::from_hashmap` slot
1871    /// stamping (§2.7.6 / Q8 / Q9 parallel-kind track). Always
1872    /// `HeapKind::HashMap` regardless of the inner V — the V-discriminator
1873    /// is encoded in the `HashMapKindedRef` variant tag, not in the
1874    /// `HeapKind` ordinal (HashMap stays at ordinal 17).
1875    #[inline]
1876    pub const fn heap_kind(&self) -> HeapKind {
1877        HeapKind::HashMap
1878    }
1879}
1880
1881/// Per-V `{key: value, …}` formatter for `HashMapKindedRef`. Walks the
1882/// keys buffer + per-V values buffer; renders keys as quoted strings
1883/// and each value via the matching primitive `Display` (i64/f64/u8 as
1884/// "true"/"false"/char). For HeapElement / Ptr-newtype V we route
1885/// through the inner pointer's `Display` shape.
1886///
1887/// Wave 2 Round 3b C2-joint ckpt-3 (2026-05-14). ADR-006 §2.7.24 Q25.B
1888/// SUPERSEDED + audit §C.4.
1889fn hashmap_kref_display(
1890    kref: &HashMapKindedRef,
1891    f: &mut std::fmt::Formatter<'_>,
1892) -> std::fmt::Result {
1893    use std::fmt::Write as _;
1894    write!(f, "{{")?;
1895
1896    /// Read all keys as `&str` from the v2-raw `*mut TypedArray<*const StringObj>` buffer.
1897    ///
1898    /// # Safety
1899    /// `keys` must point to a live `TypedArray<*const StringObj>` whose
1900    /// elements are live StringObjs (the HashMapData<V> contract).
1901    unsafe fn read_keys<'a>(
1902        keys: *const crate::v2::typed_array::TypedArray<*const crate::v2::string_obj::StringObj>,
1903    ) -> Vec<&'a str> {
1904        unsafe {
1905            let n = crate::v2::typed_array::TypedArray::len(keys) as usize;
1906            let mut out = Vec::with_capacity(n);
1907            for i in 0..n {
1908                let ptr = crate::v2::typed_array::TypedArray::get_unchecked(keys, i as u32);
1909                out.push(crate::v2::string_obj::StringObj::as_str(ptr));
1910            }
1911            out
1912        }
1913    }
1914
1915    fn emit_key(f: &mut std::fmt::Formatter<'_>, i: usize, key: &str) -> std::fmt::Result {
1916        if i > 0 {
1917            f.write_str(", ")?;
1918        }
1919        write!(f, "\"{}\": ", key)
1920    }
1921
1922    match kref {
1923        HashMapKindedRef::I64(arc) => {
1924            let keys = unsafe { read_keys(arc.keys) };
1925            for (i, k) in keys.iter().enumerate() {
1926                emit_key(f, i, k)?;
1927                let v = unsafe { *(*arc.values).data.add(i) };
1928                write!(f, "{}", v)?;
1929            }
1930        }
1931        HashMapKindedRef::F64(arc) => {
1932            let keys = unsafe { read_keys(arc.keys) };
1933            for (i, k) in keys.iter().enumerate() {
1934                emit_key(f, i, k)?;
1935                let v = unsafe { *(*arc.values).data.add(i) };
1936                write!(f, "{}", v)?;
1937            }
1938        }
1939        HashMapKindedRef::Bool(arc) => {
1940            let keys = unsafe { read_keys(arc.keys) };
1941            for (i, k) in keys.iter().enumerate() {
1942                emit_key(f, i, k)?;
1943                let v: u8 = unsafe { *(*arc.values).data.add(i) };
1944                write!(f, "{}", v != 0)?;
1945            }
1946        }
1947        HashMapKindedRef::Char(arc) => {
1948            let keys = unsafe { read_keys(arc.keys) };
1949            for (i, k) in keys.iter().enumerate() {
1950                emit_key(f, i, k)?;
1951                let v: char = unsafe { *(*arc.values).data.add(i) };
1952                write!(f, "'{}'", v)?;
1953            }
1954        }
1955        HashMapKindedRef::String(arc) => {
1956            let keys = unsafe { read_keys(arc.keys) };
1957            for (i, k) in keys.iter().enumerate() {
1958                emit_key(f, i, k)?;
1959                let v_ptr: *const crate::v2::string_obj::StringObj =
1960                    unsafe { *(*arc.values).data.add(i) };
1961                let s = unsafe { crate::v2::string_obj::StringObj::as_str(v_ptr) };
1962                write!(f, "\"{}\"", s)?;
1963            }
1964        }
1965        HashMapKindedRef::Decimal(arc) => {
1966            let keys = unsafe { read_keys(arc.keys) };
1967            for (i, k) in keys.iter().enumerate() {
1968                emit_key(f, i, k)?;
1969                let v_ptr: *const crate::v2::decimal_obj::DecimalObj =
1970                    unsafe { *(*arc.values).data.add(i) };
1971                // DecimalObj::as_decimal returns the Decimal value via the
1972                // v2-raw payload (mirrors StringObj::as_str shape).
1973                let d = unsafe { (*v_ptr).value };
1974                let mut tmp = String::new();
1975                let _ = write!(tmp, "{}", d);
1976                f.write_str(&tmp)?;
1977            }
1978        }
1979        HashMapKindedRef::TypedObject(arc) => {
1980            let keys = unsafe { read_keys(arc.keys) };
1981            for (i, k) in keys.iter().enumerate() {
1982                emit_key(f, i, k)?;
1983                // Render as opaque tag — full recursive rendering lives at
1984                // printing.rs::format_typed_object (depth-budgeted).
1985                let v_ref: &TypedObjectPtr = unsafe { &*(*arc.values).data.add(i) };
1986                write!(f, "<typed_object:{:p}>", v_ref.as_ptr())?;
1987            }
1988        }
1989        HashMapKindedRef::TraitObject(arc) => {
1990            let keys = unsafe { read_keys(arc.keys) };
1991            for (i, k) in keys.iter().enumerate() {
1992                emit_key(f, i, k)?;
1993                let v_ref: &TraitObjectPtr = unsafe { &*(*arc.values).data.add(i) };
1994                write!(f, "<trait_object:{:p}>", v_ref.as_ptr())?;
1995            }
1996        }
1997        HashMapKindedRef::HashMap(arc) => {
1998            // Recursive carrier: each value is itself a HashMapKindedRef.
1999            // Recurse via this same display formatter (Wave N
2000            // hashmap-value-v-arm follow-up, cluster-2 closure-wave-C,
2001            // 2026-05-16).
2002            let keys = unsafe { read_keys(arc.keys) };
2003            for (i, k) in keys.iter().enumerate() {
2004                emit_key(f, i, k)?;
2005                let inner_ref: &HashMapKindedRef = unsafe { &*(*arc.values).data.add(i) };
2006                hashmap_kref_display(inner_ref, f)?;
2007            }
2008        }
2009    }
2010    write!(f, "}}")
2011}
2012
2013// ── Legacy HashMapValueBuf + non-generic HashMapData REMOVED (Wave 2 Round 3b
2014//    C2-joint ckpt-1, 2026-05-14) ──────────────────────────────────────────
2015//
2016// The pre-Q25.B-SUPERSEDED `HashMapValueBuf` enum + non-generic `HashMapData`
2017// struct/impl have been removed. The replacement is `HashMapData<V>` +
2018// `HashMapKindedRef` + `HashMapValueElem` trait (above). Consumer sites at
2019// `HeapValue::HashMap` variant payload + 51 `Arc<HashMapData>` usages cascade
2020// in ckpt-2 (variant signature) + ckpt-3 (hashmap_methods.rs / printing.rs /
2021// xml.rs / json.rs / array_transform.rs / vm_impl/builtins.rs /
2022// trait_object_ops.rs) + ckpt-final (JIT FFI).
2023
2024// ── HashSet storage (Wave 13 W13-hashset-rebuild, 2026-05-10) ───────────────
2025
2026/// HashSet storage — one keyspace, no values. Mirror of `HashMapData`
2027/// with the values buffer dropped.
2028///
2029/// ADR-006 §2.7.15 / Q16 amendment (mirror of §2.7.9 FilterExpr / §2.7.13
2030/// Reference precedent for the cardinality-amendment shape, but Set is
2031/// a HashMap *sibling* — full `HeapValue::HashSet` arm rather than
2032/// pure-discriminator). Reuses the Stage C P1(b) Phase 2d Array shape
2033/// (`TypedBuffer<Arc<String>>`) for the keys buffer verbatim. Insertion
2034/// order is the canonical storage; the `index` is a sidecar acceleration
2035/// structure for O(1) `set.has(key)`.
2036///
2037/// **String-only keyspace at landing** (per the W9-set-methods owner
2038/// audit's Path A scope and the §2.7.15 Q16 ruling). Heterogeneous-
2039/// element keysets (int-keyed, TypedObject-keyed) are explicitly
2040/// out-of-scope; the Path B (`TypedSet<T>` per element kind) rebuild
2041/// is a future Phase-2c amendment with measurement.
2042#[derive(Debug)]
2043pub struct HashSetData {
2044    /// Insertion-ordered keys (string-typed buffer).
2045    ///
2046    /// Storage shape: `Arc<Vec<Arc<String>>>` post-V3-S5 ckpt-5-prime²a
2047    /// (Migration shape (a) per supervisor 2026-05-15 ratification —
2048    /// `TypedBuffer<T>` wrapper layer retired wholesale at ckpt-4;
2049    /// `Arc<Vec<T>>` is the smallest delta preserving `Arc::make_mut`
2050    /// clone-on-write semantics).
2051    pub keys: Arc<Vec<Arc<String>>>,
2052    /// Eager bucket-index: hash → list of indices into `keys` array.
2053    /// Enables O(1) lookup at `set.has(key)`. Hash is FNV-1a over the
2054    /// key string bytes — same as `HashMapData::index`.
2055    pub index: std::collections::HashMap<u64, Vec<u32>>,
2056}
2057
2058impl HashSetData {
2059    /// Build an empty HashSetData with no entries.
2060    pub fn new() -> Self {
2061        Self {
2062            keys: Arc::new(Vec::new()),
2063            index: std::collections::HashMap::new(),
2064        }
2065    }
2066
2067    /// Build from a `Vec<Arc<String>>` of keys, computing the bucket
2068    /// index eagerly. Duplicate keys in the input are collapsed
2069    /// (insertion-order preserved, first occurrence wins).
2070    pub fn from_keys(keys: Vec<Arc<String>>) -> Self {
2071        let mut out = Self::new();
2072        for k in keys {
2073            out.insert(k);
2074        }
2075        out
2076    }
2077
2078    /// Number of entries.
2079    #[inline]
2080    pub fn len(&self) -> usize {
2081        self.keys.len()
2082    }
2083
2084    /// Whether the set is empty.
2085    #[inline]
2086    pub fn is_empty(&self) -> bool {
2087        self.keys.is_empty()
2088    }
2089
2090    /// Whether the set contains the given key. O(1) via the bucket
2091    /// index plus a short bucket scan for collision disambiguation.
2092    pub fn contains(&self, key: &str) -> bool {
2093        let hash = fnv1a_hash(key.as_bytes());
2094        let Some(bucket) = self.index.get(&hash) else {
2095            return false;
2096        };
2097        for &idx in bucket {
2098            let i = idx as usize;
2099            if self.keys[i].as_str() == key {
2100                return true;
2101            }
2102        }
2103        false
2104    }
2105
2106    // ── Mutation API (Wave 13 W13-hashset-rebuild, 2026-05-10) ──────────────
2107    //
2108    // Mirror of HashMapData's W13-hashmap-mutation API with the values
2109    // buffer dropped. `Arc::make_mut` clone-on-write over the inner
2110    // `Arc<TypedBuffer<Arc<String>>>` keys plus parallel bucket-index
2111    // maintenance — same shape, one less buffer to mutate.
2112
2113    /// Insert a key. Returns `true` if the key was newly added,
2114    /// `false` if it was already present (no-op in the latter case).
2115    pub fn insert(&mut self, key: Arc<String>) -> bool {
2116        let hash = fnv1a_hash(key.as_bytes());
2117        if let Some(bucket) = self.index.get(&hash) {
2118            for &idx in bucket {
2119                let i = idx as usize;
2120                if self.keys[i].as_str() == key.as_str() {
2121                    return false;
2122                }
2123            }
2124        }
2125        let new_idx = self.keys.len();
2126        Arc::make_mut(&mut self.keys).push(key);
2127        self.index.entry(hash).or_default().push(new_idx as u32);
2128        true
2129    }
2130
2131    /// Remove the entry under `key`. Returns `true` if the key was
2132    /// present (and removed), `false` if no entry existed. The bucket
2133    /// index is updated to reflect the buffer's post-removal indices:
2134    /// every entry after the removed slot shifts down by one position
2135    /// (mirror of `HashMapData::remove`).
2136    pub fn remove(&mut self, key: &str) -> bool {
2137        let hash = fnv1a_hash(key.as_bytes());
2138        let removed_idx: usize = {
2139            let Some(bucket) = self.index.get(&hash) else {
2140                return false;
2141            };
2142            let mut found: Option<usize> = None;
2143            for (bucket_pos, &idx) in bucket.iter().enumerate() {
2144                if self.keys[idx as usize].as_str() == key {
2145                    found = Some(bucket_pos);
2146                    break;
2147                }
2148            }
2149            let bucket_pos = match found {
2150                Some(p) => p,
2151                None => return false,
2152            };
2153            let bucket = self.index.get_mut(&hash).expect("bucket present");
2154            let removed_idx = bucket.swap_remove(bucket_pos) as usize;
2155            if bucket.is_empty() {
2156                self.index.remove(&hash);
2157            }
2158            removed_idx
2159        };
2160        Arc::make_mut(&mut self.keys).remove(removed_idx);
2161        for bucket in self.index.values_mut() {
2162            for slot in bucket.iter_mut() {
2163                if (*slot as usize) > removed_idx {
2164                    *slot -= 1;
2165                }
2166            }
2167        }
2168        true
2169    }
2170}
2171
2172impl Default for HashSetData {
2173    fn default() -> Self {
2174        Self::new()
2175    }
2176}
2177
2178impl Clone for HashSetData {
2179    fn clone(&self) -> Self {
2180        Self {
2181            keys: Arc::clone(&self.keys),
2182            index: self.index.clone(),
2183        }
2184    }
2185}
2186
2187// ── Result / Option storage (ADR-006 §2.7.17 / Q18, W14-variant-codegen) ────
2188//
2189// Wave 14 W14-variant-codegen amendment: Result<T,E> and Option<T> are
2190// represented as kinded carriers `Arc<ResultData>` / `Arc<OptionData>`
2191// holding (a) a `is_ok` / `is_some` discriminator boolean and (b) a single
2192// payload `KindedSlot` carrying one strong-count share for the inner
2193// value. Mirrors the §2.7.16 IteratorState typed-Arc shape and the §2.5
2194// AnyError schema-keyed kind discipline (per-slot kind threaded
2195// alongside slot bits, drop dispatched on the kind label). Slot bits at
2196// the §2.7.7 stack tier are `Arc::into_raw(Arc<ResultData>)` /
2197// `Arc::into_raw(Arc<OptionData>)` directly with kind labels
2198// `NativeKind::Ptr(HeapKind::Result)` / `NativeKind::Ptr(HeapKind::Option)`.
2199//
2200// The payload `KindedSlot` lives inside the typed-Arc so the value's
2201// strong-count share is owned by the wrapper for the wrapper's lifetime;
2202// `KindedSlot::Drop` retires the inner share when the wrapper Drop runs
2203// (Arc refcount reaches zero). On clone, `KindedSlot::Clone` bumps the
2204// inner share. Same recursion-through-Arc discipline as
2205// `IteratorTransform::Map(Arc<HeapValue>)` per §2.7.16.
2206
2207/// Result<T, E> carrier. `is_ok` discriminates Ok vs Err; `payload` carries
2208/// the inner value (`T` for Ok, `E` for Err). Both arms share the same
2209/// payload slot — the variant tag is the discriminator, not the slot's
2210/// physical layout.
2211#[derive(Debug)]
2212pub struct ResultData {
2213    pub is_ok: bool,
2214    pub payload: crate::kinded_slot::KindedSlot,
2215}
2216
2217impl ResultData {
2218    /// Construct an Ok-tagged result.
2219    #[inline]
2220    pub fn ok(payload: crate::kinded_slot::KindedSlot) -> Self {
2221        Self { is_ok: true, payload }
2222    }
2223
2224    /// Construct an Err-tagged result.
2225    #[inline]
2226    pub fn err(payload: crate::kinded_slot::KindedSlot) -> Self {
2227        Self { is_ok: false, payload }
2228    }
2229}
2230
2231impl Clone for ResultData {
2232    /// Per-field clone — `KindedSlot::Clone` bumps the payload's
2233    /// strong-count share.
2234    fn clone(&self) -> Self {
2235        Self {
2236            is_ok: self.is_ok,
2237            payload: self.payload.clone(),
2238        }
2239    }
2240}
2241
2242/// Option<T> carrier. `is_some` discriminates Some vs None; `payload`
2243/// carries the inner value for Some. For None the payload is a
2244/// `KindedSlot::none()` placeholder (Bool-kind, zero bits) so
2245/// `KindedSlot::Drop` is a no-op.
2246#[derive(Debug)]
2247pub struct OptionData {
2248    pub is_some: bool,
2249    pub payload: crate::kinded_slot::KindedSlot,
2250}
2251
2252impl OptionData {
2253    /// Construct a Some-tagged option.
2254    #[inline]
2255    pub fn some(payload: crate::kinded_slot::KindedSlot) -> Self {
2256        Self { is_some: true, payload }
2257    }
2258
2259    /// Construct a None-tagged option (payload is a no-op KindedSlot).
2260    #[inline]
2261    pub fn none() -> Self {
2262        Self {
2263            is_some: false,
2264            payload: crate::kinded_slot::KindedSlot::none(),
2265        }
2266    }
2267}
2268
2269impl Clone for OptionData {
2270    /// Per-field clone — `KindedSlot::Clone` bumps the payload's
2271    /// strong-count share. For None the payload is a zero-bits Bool
2272    /// slot; clone is a no-op refcount-wise.
2273    fn clone(&self) -> Self {
2274        Self {
2275            is_some: self.is_some,
2276            payload: self.payload.clone(),
2277        }
2278    }
2279}
2280
2281/// FNV-1a hash for byte slices. Matches the `v2/typed_map.rs` hash
2282/// function so that key-hash semantics are consistent across the
2283/// HashMap-marshal layer and any future cross-cluster perf path.
2284#[inline]
2285fn fnv1a_hash(bytes: &[u8]) -> u64 {
2286    let mut h: u64 = 0xcbf29ce484222325;
2287    for &b in bytes {
2288        h ^= b as u64;
2289        h = h.wrapping_mul(0x100000001b3);
2290    }
2291    h
2292}
2293
2294// ── Deque storage (W15-deque, ADR-006 §2.7.19 / Q20, 2026-05-10) ───────────
2295
2296/// Double-ended queue storage. Heterogeneous element kinds are stored as
2297/// `Arc<HeapValue>` payloads (mirror of `HashMapData::values` per ADR-005
2298/// §1 single-discriminator) — the deque is element-kind-agnostic at
2299/// landing, in line with the W13-hashmap precedent.
2300///
2301/// ADR-006 §2.7.19 / Q20 amendment (Wave 15 W15-deque, 2026-05-10).
2302/// Mirror of the §2.7.15 HashSet shape (full `HeapValue::Deque` arm,
2303/// NOT pure-discriminator like FilterExpr / SharedCell): receivers
2304/// flow through `slot.as_heap_value()` for receiver classification at
2305/// method dispatch (`d.pushBack(...)` / `d.popFront()` / `d.size()`).
2306///
2307/// **Heterogeneous-element keyspace at landing.** Element kinds that
2308/// can be heap-wrapped (string / int via `BigInt(Arc<i64>)` / typed
2309/// arrays / typed objects / hashmaps / etc.) are accepted by the
2310/// mutation API; bare `Float64` / `Bool` results are rejected (no
2311/// matching `HeapValue::*` arm exists post-§2.3). Same coverage shape
2312/// as `HashMapData::values` storage (`hashmap_methods.rs::
2313/// result_slot_to_heap_value_arc`).
2314///
2315/// Per the W15-deque audit: `VecDeque<Arc<HeapValue>>` chosen over the
2316/// alternative `Vec<u64>` + parallel `Vec<NativeKind>` (per §2.7.7
2317/// stack ABI) — Deque is heterogeneous-element, not scalar-only, so
2318/// the parallel-kind track shape would force every push site to
2319/// carry both bits and kind through the deque API. The
2320/// `Arc<HeapValue>` shape collapses both into a single payload at the
2321/// element tier and matches the Stage C P1(b) HashMap precedent.
2322#[derive(Debug)]
2323pub struct DequeData {
2324    /// Insertion-ordered double-ended queue of heap-allocated element
2325    /// payloads. Element kinds are recovered via the canonical ADR-005
2326    /// §1 single-discriminator `HeapValue` match at the read site.
2327    pub items: std::collections::VecDeque<Arc<HeapValue>>,
2328}
2329
2330impl DequeData {
2331    /// Build an empty DequeData with no elements.
2332    pub fn new() -> Self {
2333        Self {
2334            items: std::collections::VecDeque::new(),
2335        }
2336    }
2337
2338    /// Build from a `Vec<Arc<HeapValue>>`. Insertion order is the
2339    /// front-to-back walk order.
2340    pub fn from_items(items: Vec<Arc<HeapValue>>) -> Self {
2341        Self {
2342            items: std::collections::VecDeque::from(items),
2343        }
2344    }
2345
2346    /// Number of elements.
2347    #[inline]
2348    pub fn len(&self) -> usize {
2349        self.items.len()
2350    }
2351
2352    /// Whether the deque is empty.
2353    #[inline]
2354    pub fn is_empty(&self) -> bool {
2355        self.items.is_empty()
2356    }
2357
2358    /// Borrow the front element without removing it. `None` when empty.
2359    pub fn peek_front(&self) -> Option<&Arc<HeapValue>> {
2360        self.items.front()
2361    }
2362
2363    /// Borrow the back element without removing it. `None` when empty.
2364    pub fn peek_back(&self) -> Option<&Arc<HeapValue>> {
2365        self.items.back()
2366    }
2367
2368    /// Borrow the element at `index` (front-counted). `None` when out
2369    /// of bounds.
2370    pub fn get(&self, index: usize) -> Option<&Arc<HeapValue>> {
2371        self.items.get(index)
2372    }
2373
2374    // ── Mutation API (W15-deque, 2026-05-10) ────────────────────────────────
2375    //
2376    // Mirror of HashMapData / HashSetData clone-on-write shape — callers
2377    // wrap mutation in `Arc::make_mut(&mut arc).push_back(...)` so the
2378    // shared-receiver semantics are preserved per ADR-006 §2.7.4.
2379
2380    /// Push an element onto the back of the deque.
2381    pub fn push_back(&mut self, value: Arc<HeapValue>) {
2382        self.items.push_back(value);
2383    }
2384
2385    /// Push an element onto the front of the deque.
2386    pub fn push_front(&mut self, value: Arc<HeapValue>) {
2387        self.items.push_front(value);
2388    }
2389
2390    /// Remove and return the back element. `None` when empty.
2391    pub fn pop_back(&mut self) -> Option<Arc<HeapValue>> {
2392        self.items.pop_back()
2393    }
2394
2395    /// Remove and return the front element. `None` when empty.
2396    pub fn pop_front(&mut self) -> Option<Arc<HeapValue>> {
2397        self.items.pop_front()
2398    }
2399}
2400
2401impl Default for DequeData {
2402    fn default() -> Self {
2403        Self::new()
2404    }
2405}
2406
2407// ── Channel storage (Wave 15 W15-channel, ADR-006 §2.7.20 / Q21,
2408// 2026-05-10) ──────────────────────────────────────────────────────────────
2409
2410/// MPSC-style synchronous channel storage.
2411///
2412/// ADR-006 §2.7.20 / Q21 amendment (Wave 15 W15-channel-rebuild,
2413/// 2026-05-10). Channel is a concurrency primitive; unlike the
2414/// HashMap/HashSet siblings (insertion-ordered immutable-on-clone
2415/// keys-buffer with `Arc::make_mut` clone-on-write), Channel needs
2416/// **interior mutability** so that two `Arc<ChannelData>` shares of
2417/// the same channel observe each other's `send` / `recv` mutations
2418/// (the producer and consumer endpoints share the same buffer). The
2419/// inner state therefore lives behind a `Mutex<ChannelInner>`; the
2420/// outer `Arc` is purely a refcount carrier.
2421///
2422/// **Sync same-thread path only at landing.** Cross-task / cross-
2423/// thread blocking `recv()` (the canonical async-channel use case)
2424/// requires integration with the §2.7.4 task-scheduler boundary
2425/// (`shape-vm/src/executor/task_scheduler.rs`), which is itself a
2426/// phase-2c surface; per the W15 playbook the async paths SURFACE
2427/// cleanly. The sync path (same-thread `send` then `recv`) lands
2428/// here end-to-end.
2429///
2430/// **Element typing.** The buffer stores `KindedSlot` payloads
2431/// directly so heterogeneous-element queues are first-class (a
2432/// channel can carry ints, strings, or typed objects without a
2433/// per-element-kind specialisation). Each slot owns one strong-
2434/// count share for heap-bearing kinds; the `KindedSlot::Drop`
2435/// dispatch retires shares cleanly when the channel itself drops.
2436/// This is the same shape `concurrency_methods.rs` (Mutex/Atomic/
2437/// Lazy) will use when those primitives rebuild — Channel is the
2438/// first concurrency primitive to land kinded.
2439///
2440/// **Closed flag.** `closed: bool` records whether the producer
2441/// side has signalled end-of-stream. After `close()` further
2442/// `send()` calls return a closed-channel error; `recv()` continues
2443/// to drain queued elements and only errors once the queue is
2444/// empty (canonical drain-on-close semantics).
2445#[derive(Debug)]
2446pub struct ChannelData {
2447    inner: std::sync::Mutex<ChannelInner>,
2448}
2449
2450/// Inner mutable state of a `ChannelData`. Held under `Mutex` so
2451/// concurrent `Arc<ChannelData>` shares observe each other's
2452/// mutations.
2453#[derive(Debug)]
2454struct ChannelInner {
2455    /// FIFO queue of pending kinded elements.
2456    queue: std::collections::VecDeque<crate::kinded_slot::KindedSlot>,
2457    /// Producer-side end-of-stream signal. Once set, further
2458    /// `send()` calls return a closed-channel error and `recv()`
2459    /// drains remaining elements before erroring.
2460    closed: bool,
2461}
2462
2463impl ChannelData {
2464    /// Build an empty open channel.
2465    pub fn new() -> Self {
2466        Self {
2467            inner: std::sync::Mutex::new(ChannelInner {
2468                queue: std::collections::VecDeque::new(),
2469                closed: false,
2470            }),
2471        }
2472    }
2473
2474    /// Number of pending elements. Useful for diagnostics; not part
2475    /// of the user-facing method surface.
2476    pub fn len(&self) -> usize {
2477        self.inner.lock().expect("channel mutex poisoned").queue.len()
2478    }
2479
2480    /// Whether the queue currently holds zero pending elements.
2481    pub fn is_empty(&self) -> bool {
2482        self.inner
2483            .lock()
2484            .expect("channel mutex poisoned")
2485            .queue
2486            .is_empty()
2487    }
2488
2489    /// Whether `close()` has been called.
2490    pub fn is_closed(&self) -> bool {
2491        self.inner.lock().expect("channel mutex poisoned").closed
2492    }
2493
2494    /// Append `slot` to the queue.
2495    ///
2496    /// Returns `Ok(())` on success, `Err(())` if the channel is
2497    /// already closed (callers surface this as a runtime error
2498    /// from the `send` method body).
2499    pub fn send(&self, slot: crate::kinded_slot::KindedSlot) -> Result<(), ()> {
2500        let mut inner = self.inner.lock().expect("channel mutex poisoned");
2501        if inner.closed {
2502            // Drop the slot — its share retires through KindedSlot::Drop.
2503            drop(slot);
2504            return Err(());
2505        }
2506        inner.queue.push_back(slot);
2507        Ok(())
2508    }
2509
2510    /// Pop the front element non-blocking.
2511    ///
2512    /// Returns `Some(slot)` if an element was available, `None`
2513    /// otherwise. Per ADR §2.7.20 the same-thread sync path is the
2514    /// supported surface; blocking `recv()` (await-style) requires
2515    /// the §2.7.4 task-scheduler boundary and is SURFACE'd at the
2516    /// method body.
2517    pub fn try_recv(&self) -> Option<crate::kinded_slot::KindedSlot> {
2518        self.inner
2519            .lock()
2520            .expect("channel mutex poisoned")
2521            .queue
2522            .pop_front()
2523    }
2524
2525    /// Mark the channel closed. Idempotent — calling close on an
2526    /// already-closed channel is a no-op.
2527    pub fn close(&self) {
2528        self.inner.lock().expect("channel mutex poisoned").closed = true;
2529    }
2530}
2531
2532impl Default for ChannelData {
2533    fn default() -> Self {
2534        Self::new()
2535    }
2536}
2537
2538// ── Mutex / Atomic / Lazy storage (Wave 17 W17-concurrency,
2539//    ADR-006 §2.7.25, 2026-05-11) ──────────────────────────────────────────
2540//
2541// W17-concurrency rebuild: Mutex, Atomic, and Lazy are the three
2542// concurrency primitives left SURFACE'd by the strict-typing Phase-2
2543// bulldozer (the `HeapValue::Concurrency(ConcurrencyData::*)` enum form
2544// was deleted alongside `ValueWord`; see the deletion-fate comment in
2545// `executor/objects/concurrency_methods.rs:1-22`). Each lands as its own
2546// typed-Arc HeapValue arm per ADR-006 §2.3 / §2.7.25, mirror of the
2547// §2.7.20 Channel rebuild structure:
2548//
2549// - `Mutex<T>` carries a single `KindedSlot` payload protected by a
2550//   `Mutex<MutexInner>`. Like `ChannelData`, two `Arc<MutexData>` shares
2551//   observe each other's mutations — the canonical "shared cell with
2552//   exclusion" shape. `lock()` is a no-op marker at landing (single-
2553//   threaded VM); the contract is that the inner value is mutated under
2554//   exclusion. `try_lock()` mirrors `lock()`. `set(value)` swaps the
2555//   inner payload (KindedSlot Drop retires the prior share).
2556// - `Atomic<i64>` carries a `std::sync::atomic::AtomicI64` for the
2557//   atomic operations (`load`, `store`, `fetch_add`, `fetch_sub`,
2558//   `compare_exchange`). i64-only at landing per the playbook's
2559//   "i64-priority" / "string-only" precedents (W15-priority-queue,
2560//   W13-hashset). A typed-payload `Atomic<T>` is a future amendment
2561//   with measurement.
2562// - `Lazy<T>` carries a `Mutex<LazyInner>` wrapping `(initializer:
2563//   Option<KindedSlot>, value: Option<KindedSlot>)`. `get()` returns
2564//   the cached value or runs the initializer closure (closure-call
2565//   path unlocked by W17-make-closure, merged at `aa47364`).
2566//   `is_initialized()` returns whether the value has been computed.
2567//
2568// Forbidden shapes refused (per CLAUDE.md "Renames to refuse on sight"
2569// + playbook §3 W17-concurrency forbidden list):
2570//
2571// - Generic "concurrency primitive" wrapper (`ConcurrencyData` enum
2572//   shape from the deleted form). Each primitive is its own typed-Arc
2573//   HeapValue arm.
2574// - Inline-scalar Mutex/Atomic carriers (these are always heap — the
2575//   semantic identity is "this is a shared cell with mutation", which
2576//   has no inline-scalar reduction).
2577// - Re-using `HeapKind::SharedCell` for Mutex (different semantics —
2578//   `SharedCell` is binding-storage interior-mutability for `var`
2579//   binding-form values, while `MutexData` is a runtime synchronization
2580//   primitive user code asks for explicitly).
2581
2582/// `Mutex<T>` storage — a single typed payload protected by a Rust
2583/// `Mutex` so concurrent `Arc<MutexData>` shares observe each other's
2584/// mutations (the canonical "shared cell with exclusion" shape, mirror
2585/// of `ChannelData`'s `Mutex<ChannelInner>` interior-mutability shape).
2586///
2587/// At landing the VM is single-threaded so `lock()` / `try_lock()` are
2588/// no-op markers — the contract they preserve is "the inner value is
2589/// mutated under exclusion" (the same contract user code reasons
2590/// about). When the VM grows real concurrency, the same `Mutex` here
2591/// will serialize concurrent `lock()` calls without API churn.
2592///
2593/// The inner `Option<KindedSlot>` carries one strong-count share for
2594/// the wrapped value when present; `take()` / `replace()` discipline
2595/// preserves the share-discipline across `set(...)` (the old slot
2596/// drops, the new slot is owned by the cell).
2597#[derive(Debug)]
2598pub struct MutexData {
2599    inner: std::sync::Mutex<MutexInner>,
2600}
2601
2602#[derive(Debug)]
2603struct MutexInner {
2604    /// Wrapped value. `None` only transiently between `take` and replace
2605    /// during `set(...)` — never observable externally.
2606    value: Option<crate::kinded_slot::KindedSlot>,
2607}
2608
2609impl MutexData {
2610    /// Build a `MutexData` wrapping `value`.
2611    pub fn new(value: crate::kinded_slot::KindedSlot) -> Self {
2612        Self {
2613            inner: std::sync::Mutex::new(MutexInner { value: Some(value) }),
2614        }
2615    }
2616
2617    /// `lock()` — at landing a no-op marker (single-threaded VM). When
2618    /// the runtime grows real concurrency, this is the acquire point
2619    /// for the inner `std::sync::Mutex`.
2620    pub fn lock(&self) {
2621        let _g = self.inner.lock().expect("mutex poisoned");
2622    }
2623
2624    /// `try_lock()` — at landing always returns true (single-threaded
2625    /// VM; there's no contention to fail). Mirror of `lock()`.
2626    pub fn try_lock(&self) -> bool {
2627        self.inner.try_lock().is_ok()
2628    }
2629
2630    /// Read the current value (clone of the inner `KindedSlot`).
2631    /// `KindedSlot::Clone` bumps the inner share so the returned slot
2632    /// is independently owned.
2633    pub fn get(&self) -> crate::kinded_slot::KindedSlot {
2634        let inner = self.inner.lock().expect("mutex poisoned");
2635        inner
2636            .value
2637            .as_ref()
2638            .expect("mutex value present")
2639            .clone()
2640    }
2641
2642    /// Replace the wrapped value. The prior slot drops here
2643    /// (`KindedSlot::Drop` retires its inner share); the new slot is
2644    /// owned by the cell.
2645    pub fn set(&self, new_value: crate::kinded_slot::KindedSlot) {
2646        let mut inner = self.inner.lock().expect("mutex poisoned");
2647        inner.value = Some(new_value);
2648    }
2649}
2650
2651/// `Atomic<i64>` storage — wraps a `std::sync::atomic::AtomicI64` for
2652/// the atomic operations exposed by the `Atomic.load` / `store` /
2653/// `fetch_add` / `fetch_sub` / `compare_exchange` method surface.
2654///
2655/// **i64-only at landing** per the playbook's typed-payload deferral
2656/// precedent (W15-priority-queue i64-priority-only). A typed-payload
2657/// `Atomic<T>` is a future Phase-2c amendment with measurement.
2658///
2659/// Memory ordering is `SeqCst` (sequential consistency) throughout —
2660/// the simplest semantically-correct ordering. Relaxed-ordering
2661/// optimizations are a measured follow-up.
2662#[derive(Debug)]
2663pub struct AtomicData {
2664    value: std::sync::atomic::AtomicI64,
2665}
2666
2667impl AtomicData {
2668    /// Build an `AtomicData` with initial value `init`.
2669    pub fn new(init: i64) -> Self {
2670        Self {
2671            value: std::sync::atomic::AtomicI64::new(init),
2672        }
2673    }
2674
2675    /// Atomic load (SeqCst).
2676    pub fn load(&self) -> i64 {
2677        self.value.load(std::sync::atomic::Ordering::SeqCst)
2678    }
2679
2680    /// Atomic store (SeqCst).
2681    pub fn store(&self, v: i64) {
2682        self.value.store(v, std::sync::atomic::Ordering::SeqCst)
2683    }
2684
2685    /// Atomic fetch-add (SeqCst). Returns the prior value.
2686    pub fn fetch_add(&self, delta: i64) -> i64 {
2687        self.value
2688            .fetch_add(delta, std::sync::atomic::Ordering::SeqCst)
2689    }
2690
2691    /// Atomic fetch-sub (SeqCst). Returns the prior value.
2692    pub fn fetch_sub(&self, delta: i64) -> i64 {
2693        self.value
2694            .fetch_sub(delta, std::sync::atomic::Ordering::SeqCst)
2695    }
2696
2697    /// Atomic compare-exchange (SeqCst). Returns the prior value
2698    /// regardless of success — callers infer success by comparing to
2699    /// `expected`.
2700    pub fn compare_exchange(&self, expected: i64, new_v: i64) -> i64 {
2701        match self.value.compare_exchange(
2702            expected,
2703            new_v,
2704            std::sync::atomic::Ordering::SeqCst,
2705            std::sync::atomic::Ordering::SeqCst,
2706        ) {
2707            Ok(prev) => prev,
2708            Err(prev) => prev,
2709        }
2710    }
2711}
2712
2713/// `Lazy<T>` storage — wraps an initializer closure (`KindedSlot` of
2714/// kind `Ptr(HeapKind::Closure)`) and a cached value slot. `get()`
2715/// runs the initializer the first time and caches the result;
2716/// subsequent calls return the cached value.
2717///
2718/// Closure-call dispatch (`vm.call_value_immediate_nb`) is unlocked by
2719/// the W17-make-closure partial-gate (merged at `aa47364`); see
2720/// `executor/objects/concurrency_methods.rs::v2_lazy_get` for the
2721/// closure-call body.
2722///
2723/// **`Mutex<LazyInner>` for interior mutability**: like `ChannelData`,
2724/// two `Arc<LazyData>` shares observe each other's initialization
2725/// state. The `OnceCell`-style "init only happens once" guarantee is
2726/// preserved by the inner Mutex serializing concurrent `get()` calls
2727/// when the runtime grows real concurrency. At landing (single-
2728/// threaded VM) the mutex is uncontended.
2729#[derive(Debug)]
2730pub struct LazyData {
2731    inner: std::sync::Mutex<LazyInner>,
2732}
2733
2734#[derive(Debug)]
2735struct LazyInner {
2736    /// Initializer closure (`KindedSlot` of kind `Ptr(HeapKind::Closure)`).
2737    /// `None` after first successful `get()` — the closure is dropped
2738    /// once its result is cached.
2739    initializer: Option<crate::kinded_slot::KindedSlot>,
2740    /// Cached value. `None` before first `get()`, `Some` after.
2741    value: Option<crate::kinded_slot::KindedSlot>,
2742}
2743
2744impl LazyData {
2745    /// Build a `LazyData` wrapping `initializer` (expected to be a
2746    /// closure `KindedSlot`).
2747    pub fn new(initializer: crate::kinded_slot::KindedSlot) -> Self {
2748        Self {
2749            inner: std::sync::Mutex::new(LazyInner {
2750                initializer: Some(initializer),
2751                value: None,
2752            }),
2753        }
2754    }
2755
2756    /// Whether `get()` has been called and the value cached.
2757    pub fn is_initialized(&self) -> bool {
2758        self.inner
2759            .lock()
2760            .expect("lazy mutex poisoned")
2761            .value
2762            .is_some()
2763    }
2764
2765    /// Read the cached value if present, else `None`. The closure-call
2766    /// path (running the initializer) lives in the handler tier — this
2767    /// method is the storage-tier cache lookup. Returns a clone of the
2768    /// cached slot (one strong-count share bumped).
2769    pub fn cached(&self) -> Option<crate::kinded_slot::KindedSlot> {
2770        self.inner
2771            .lock()
2772            .expect("lazy mutex poisoned")
2773            .value
2774            .as_ref()
2775            .cloned()
2776    }
2777
2778    /// Take the initializer closure (for the handler tier to invoke
2779    /// via `vm.call_value_immediate_nb`). Returns `None` if the value
2780    /// is already cached (caller should use `cached()` instead).
2781    pub fn take_initializer(&self) -> Option<crate::kinded_slot::KindedSlot> {
2782        let mut inner = self.inner.lock().expect("lazy mutex poisoned");
2783        if inner.value.is_some() {
2784            return None;
2785        }
2786        inner.initializer.take()
2787    }
2788
2789    /// Cache the result of running the initializer. The initializer
2790    /// slot has already been dropped (via `take_initializer`); this
2791    /// installs the result. If a value was concurrently cached
2792    /// (impossible at single-threaded landing, but defensive for
2793    /// future concurrency), the new value drops cleanly via
2794    /// `KindedSlot::Drop`.
2795    pub fn store_result(&self, value: crate::kinded_slot::KindedSlot) {
2796        let mut inner = self.inner.lock().expect("lazy mutex poisoned");
2797        // The take_initializer caller is the only path that should
2798        // reach store_result, so value is None here at the
2799        // single-threaded landing.
2800        inner.value = Some(value);
2801    }
2802}
2803
2804// ── TraitObject storage (W17-trait-object-storage, ADR-006 §2.7.24 Q25.C,
2805//    2026-05-11) ──────────────────────────────────────────────────────────────
2806
2807/// `dyn Trait` storage — the typed-Arc replacement for the
2808/// bulldozer-deleted `HeapValue::TraitObject { value: Box<u64>,
2809/// vtable: Arc<VTable> }`. Pairs the boxed data half (always a
2810/// `TypedObject` per §Q25.C.4 universal-dyn ruling — scalars/strings
2811/// that implement traits are boxed into `TypedObject` first; the
2812/// auto-boxing rule lifts Rust's object-safety restrictions at the
2813/// cost of one heap indirection per `dyn` coerce) with the vtable
2814/// half (shared `Arc<VTable>` so per-impl vtables are constructed
2815/// once and IC-cached per §Q25.C.6).
2816///
2817/// **Forbidden alternative.** `Box<u64>` data half is explicitly
2818/// refused (ADR-006 §Q25.E #3 — kind-blind raw-bits storage, same
2819/// defection-attractor as the deleted ValueWord). The data half is
2820/// kinded by being a typed object with a schema — `Arc<TypedObjectStorage>`
2821/// recovers the per-field kind table via the schema_id.
2822///
2823/// **Identity contract.** `Arc::ptr_eq` on the vtable Arc is the
2824/// canonical equality for the §Q25.C.2 `Self`-arg runtime check;
2825/// `vtable.concrete_type_id` is the IC-stabilization key per §Q25.C.6.
2826///
2827/// Mirror of the §2.7.20 / §2.7.25 typed-Arc shape — refcount
2828/// discipline goes through the kind label (`HeapKind::TraitObject = 29`)
2829/// in `clone_with_kind` / `drop_with_kind`, NOT through `HeapValue`.
2830/// Method-receiver classification flows through `slot.as_heap_value()`
2831/// → `HeapValue::TraitObject(arc)` per ADR-005 §1 single-discriminator;
2832/// the `op_dyn_method_call` opcode handler (compiler-emission tier)
2833/// uses the recovered `Arc<TraitObjectStorage>` to look up the method
2834/// in `vtable.methods` and dispatch the appropriate `VTableEntry`.
2835///
2836/// **Wave 2 Agent E (2026-05-14): HeapHeader-equipped shape change.**
2837/// Per audit §4.3 Obstacle O-3.a resolution + ADR-006 §Q25.C.5 amendment,
2838/// the struct now carries a `HeapHeader` at offset 0 (`#[repr(C)]`) so
2839/// v2-raw raw-pointer allocations (`_new` / `_drop` + `impl HeapElement`)
2840/// can dispatch refcount on the header via `v2_retain` / `v2_release`.
2841/// Existing `Arc<TraitObjectStorage>` construction sites continue to work
2842/// unchanged — `Arc::new(TraitObjectStorage::new(...))` produces a Rust
2843/// `Arc`-wrapped instance whose embedded header sits at refcount=1 unused;
2844/// the dispatch arms continue to use `Arc::increment_strong_count` /
2845/// `Arc::decrement_strong_count` on those bits. The new `_new`-allocated
2846/// raw-pointer bits use the header's refcount via the `HeapElement` trait.
2847///
2848/// The inner `value: Arc<TypedObjectStorage>` field remains Arc-typed in
2849/// E's scope per Wave 1 §E.6 dispatch contract (the audit's E-a path
2850/// recommends both inner pointers become raw, but D2 owns the inner
2851/// `*mut TypedObjectStorage` flip in lockstep with TypedObjectStorage's
2852/// own Arc-path retirement; E's struct shape change exposes the
2853/// HeapHeader at offset 0 + manual lifecycle so subsequent rounds can
2854/// flip the inner field without re-shaping the outer carrier). The
2855/// `vtable: Arc<VTable>` field stays Arc-typed indefinitely under E's
2856/// scope — VTable lifecycle is decoupled from this migration (audit
2857/// §E.3 recommended a separate VTable HeapHeader migration if/when
2858/// IC devirtualization measurement justifies it).
2859#[repr(C)]
2860#[derive(Debug)]
2861pub struct TraitObjectStorage {
2862    /// v2-raw HeapHeader at offset 0 (8 bytes). Refcount/kind/flags.
2863    /// Initialized to `HeapHeader::new(HEAP_KIND_V2_TRAIT_OBJECT)` by
2864    /// `_new`; for `Arc`-wrapped instances allocated via
2865    /// `TraitObjectStorage::new` the header sits at refcount=1 unused
2866    /// (the enclosing `Arc` owns the lifecycle). See struct docstring.
2867    pub header: crate::v2::heap_header::HeapHeader,
2868
2869    /// The data half of the fat pointer — owned, heap-allocated as a
2870    /// `TypedObject`. Always present (never null); universal-dyn
2871    /// per-method auto-boxing makes the boxed value a real TypedObject
2872    /// even for scalar concrete types (per §Q25.C.1).
2873    ///
2874    /// **Wave 2 Round 4 D4 ckpt-3 (2026-05-14): inner-field shift from
2875    /// `Arc<TypedObjectStorage>` to `*const TypedObjectStorage`** per E
2876    /// (Round 2) close note + D3 R3a finding D3-1 — the 5th production-
2877    /// site class (audit-side parallel to D2's HashMapValueBuf cascade).
2878    /// The raw pointer was produced by `TypedObjectStorage::_new` (refcount
2879    /// initialized to 1 on the HeapHeader at offset 0). The carrier owns
2880    /// one strong-count share, retired at `_drop` / auto-derived `Drop`
2881    /// via `TypedObjectStorage::release_elem(ptr)` (NOT Rust `Arc::drop`).
2882    pub value: *const TypedObjectStorage,
2883
2884    /// The vtable half of the fat pointer. Shared via `Arc` across
2885    /// all `TraitObjectStorage` instances built from the same
2886    /// `(impl Trait for Type)` pair — vtable construction happens
2887    /// once per impl, the resulting `Arc<VTable>` is cached and
2888    /// cloned into each boxing site. IC stabilizes on
2889    /// `Arc::as_ptr(&vtable)` per §Q25.C.6.
2890    pub vtable: Arc<crate::value::VTable>,
2891}
2892
2893impl TraitObjectStorage {
2894    /// Build a `TraitObjectStorage` from its two halves. The caller
2895    /// owns one strong-count share on the v2-raw value pointer's
2896    /// HeapHeader-at-offset-0 refcount AND one strong-count share on
2897    /// the vtable Arc; the resulting struct owns both shares.
2898    ///
2899    /// **Wave 2 Round 4 D4 ckpt-3 (2026-05-14): `value` param signature
2900    /// shifted from `Arc<TypedObjectStorage>` to `*const TypedObjectStorage`**
2901    /// per E (Round 2) close note + D3 R3a finding D3-1 — caller produces
2902    /// the raw ptr via `TypedObjectStorage::_new` (refcount=1) or by
2903    /// `v2_retain`-bumping an existing live ptr. The carrier retires
2904    /// that share at `_drop` / auto-derived `Drop` via
2905    /// `TypedObjectStorage::release_elem(value)`.
2906    ///
2907    /// The embedded HeapHeader is initialized to refcount=1 with kind
2908    /// `HEAP_KIND_V2_TRAIT_OBJECT`. For `Arc<TraitObjectStorage>`
2909    /// instances the header sits unused (the enclosing `Arc` owns the
2910    /// lifecycle); the v2-raw `_new` path is the production carrier
2911    /// for the on-header refcount lifecycle.
2912    #[inline]
2913    pub fn new(value: *const TypedObjectStorage, vtable: Arc<crate::value::VTable>) -> Self {
2914        Self {
2915            header: crate::v2::heap_header::HeapHeader::new(
2916                crate::v2::heap_header::HEAP_KIND_V2_TRAIT_OBJECT,
2917            ),
2918            value,
2919            vtable,
2920        }
2921    }
2922
2923    /// Wave 2 Agent E (2026-05-14): v2-raw raw-pointer allocator.
2924    ///
2925    /// Allocates a new `TraitObjectStorage` on the heap and returns a raw
2926    /// pointer with refcount initialized to 1. Mirrors the `TypedObjectStorage::_new`
2927    /// precedent at `heap_value.rs` (D1, 2026-05-14) — `#[repr(C)]` struct
2928    /// with `HeapHeader` at offset 0; refcount discipline goes through
2929    /// `v2_retain` / `v2_release` via the `HeapElement` trait.
2930    ///
2931    /// Construction-side contract: the caller transfers ownership of one
2932    /// strong-count share on `value: Arc<TypedObjectStorage>` and on
2933    /// `vtable: Arc<VTable>` to the storage; the storage retires those
2934    /// shares at `_drop` (via the in-place `drop_in_place` on the field
2935    /// payloads). The inner Arcs follow normal Rust `Arc` discipline —
2936    /// only the outer struct's lifecycle is HeapHeader-managed.
2937    ///
2938    /// Callers (Wave 2 Round 2): replace the legacy pattern
2939    /// ```ignore
2940    /// let arc = Arc::new(TraitObjectStorage::new(value, vtable));
2941    /// let slot = ValueSlot::from_trait_object(arc);
2942    /// ```
2943    /// with the v2-raw pattern
2944    /// ```ignore
2945    /// let ptr = TraitObjectStorage::_new(value, vtable);
2946    /// let slot = ValueSlot::from_trait_object_raw(ptr);
2947    /// ```
2948    pub fn _new(
2949        value: *const TypedObjectStorage,
2950        vtable: Arc<crate::value::VTable>,
2951    ) -> *mut Self {
2952        let layout = std::alloc::Layout::new::<Self>();
2953        let ptr = unsafe { std::alloc::alloc(layout) as *mut Self };
2954        assert!(!ptr.is_null(), "allocation failed for TraitObjectStorage");
2955        unsafe {
2956            // SAFETY: `ptr` points to fresh, uninitialized memory of size
2957            // `Layout::new::<Self>()`. We write every field via `ptr::write`
2958            // to avoid running drop on uninitialized bytes (the existing
2959            // memory contains garbage, never a valid prior `Self`).
2960            std::ptr::write(
2961                &mut (*ptr).header,
2962                crate::v2::heap_header::HeapHeader::new(
2963                    crate::v2::heap_header::HEAP_KIND_V2_TRAIT_OBJECT,
2964                ),
2965            );
2966            std::ptr::write(&mut (*ptr).value, value);
2967            std::ptr::write(&mut (*ptr).vtable, vtable);
2968        }
2969        ptr
2970    }
2971
2972    /// Wave 2 Agent E (2026-05-14): v2-raw raw-pointer deallocator.
2973    ///
2974    /// Runs `drop_in_place` on the inner `Arc<TypedObjectStorage>` value
2975    /// field and `Arc<VTable>` vtable field (retires one strong-count
2976    /// share on each via standard Rust `Arc::drop`), then deallocates
2977    /// the struct's heap memory via `Layout::new::<Self>()`.
2978    ///
2979    /// Mirrors the `TypedObjectStorage::_drop` precedent.
2980    ///
2981    /// # Safety
2982    /// `ptr` must point to a live `TraitObjectStorage` allocated via
2983    /// `Self::_new` with no remaining references. Must not be called
2984    /// more than once on the same pointer; must not be called on
2985    /// `Arc<TraitObjectStorage>`-allocated instances (those run
2986    /// through Rust's `Arc` drop machinery + the auto-derived shape).
2987    pub unsafe fn _drop(ptr: *mut Self) {
2988        unsafe {
2989            // Wave 2 Round 4 D4 ckpt-3 (2026-05-14): `value: *const
2990            // TypedObjectStorage` (v2-raw shape) is released via
2991            // `TypedObjectStorage::release_elem` (HeapElement trait —
2992            // calls `v2_release` on the inner HeapHeader; on refcount=0
2993            // the inner `TypedObjectStorage::_drop` runs the per-field
2994            // heap-mask walk + deallocates). The `vtable: Arc<VTable>`
2995            // field is retired via `drop_in_place` (standard Arc::drop).
2996            // The `header` field is POD — no Drop work owed.
2997            use crate::v2::heap_element::HeapElement;
2998            let inner_ptr = (*ptr).value;
2999            if !inner_ptr.is_null() {
3000                TypedObjectStorage::release_elem(inner_ptr);
3001            }
3002            std::ptr::drop_in_place(&mut (*ptr).vtable);
3003            // Deallocate the struct's heap memory.
3004            let layout = std::alloc::Layout::new::<Self>();
3005            std::alloc::dealloc(ptr as *mut u8, layout);
3006        }
3007    }
3008
3009    /// Convenience: look up a method by name in the vtable. Returns
3010    /// `None` for an unknown method (the dispatch tier surfaces this
3011    /// as a runtime error — under universal-dyn there is no compile-
3012    /// time `ETO-002` for "method not in trait" since the trait's
3013    /// declared method set is the surface checked at compile time;
3014    /// runtime lookup failures indicate a vtable-construction bug).
3015    #[inline]
3016    pub fn method(&self, name: &str) -> Option<&crate::value::VTableEntry> {
3017        self.vtable.methods.get(name)
3018    }
3019
3020    /// Identity check for the §Q25.C.2 `Self`-arg runtime contract.
3021    /// `Arc::ptr_eq` on vtable Arcs is the tightest comparison; both
3022    /// `TraitObjectStorage` instances must share the same vtable
3023    /// allocation (which happens when both came from the same
3024    /// `(impl Trait for Type)` pair).
3025    #[inline]
3026    pub fn vtable_eq(&self, other: &Self) -> bool {
3027        Arc::ptr_eq(&self.vtable, &other.vtable)
3028    }
3029}
3030
3031// Wave 2 Round 4 D4 ckpt-3 (2026-05-14): manual Send + Sync impls. The raw
3032// pointer `value: *const TypedObjectStorage` makes the struct !Send/!Sync
3033// by default; the safety argument mirrors `Arc<T>`'s — the pointer
3034// targets a heap allocation whose lifecycle is managed by the HeapHeader
3035// refcount (v2_retain/v2_release atomics in `v2/refcount.rs`), and
3036// TypedObjectStorage itself is Send + Sync (its fields are Box<[ValueSlot]>
3037// + Arc<[NativeKind]>, all of which are Send + Sync). Multi-thread
3038// observers share the inner via the same refcount-bumped raw pointer that
3039// the v2-raw carrier ABI uses across thread boundaries (KindedSlot Send +
3040// Sync; `Arc<TraitObjectStorage>` Send + Sync requires this).
3041unsafe impl Send for TraitObjectStorage {}
3042unsafe impl Sync for TraitObjectStorage {}
3043
3044impl Clone for TraitObjectStorage {
3045    /// Per-field clone — the inner value ptr's HeapHeader-at-offset-0
3046    /// refcount is bumped via `v2_retain`; the vtable Arc bumps its
3047    /// strong count by one. Cloning a `TraitObjectStorage` produces a
3048    /// fat-pointer carrier that observes the same underlying TypedObject
3049    /// and dispatches against the same VTable. The cloned struct's
3050    /// `header` is a fresh HeapHeader at refcount=1 (matches `Self::new`'s
3051    /// contract — the embedded header is unused on `Arc<TraitObjectStorage>`
3052    /// instances; it carries lifecycle only for `_new`-allocated raw-
3053    /// pointer instances).
3054    ///
3055    /// **Wave 2 Round 4 D4 ckpt-3 (2026-05-14): inner-value retain shifted
3056    /// from `Arc::clone(&self.value)` to `v2_retain(&(*self.value).header)`**
3057    /// per the `value: *const TypedObjectStorage` inner-field shift.
3058    fn clone(&self) -> Self {
3059        // SAFETY: `self.value` is a `*const TypedObjectStorage` allocated
3060        // via `TypedObjectStorage::_new` (refcount initialized to 1 on
3061        // the HeapHeader at offset 0). The `Clone` impl bumps that
3062        // refcount via `v2_retain` so the cloned struct owns its own
3063        // share, retired at its `_drop` / auto-derived `Drop` via
3064        // `TypedObjectStorage::release_elem(value)`.
3065        if !self.value.is_null() {
3066            unsafe { crate::v2::refcount::v2_retain(&(*self.value).header); }
3067        }
3068        Self {
3069            header: crate::v2::heap_header::HeapHeader::new(
3070                crate::v2::heap_header::HEAP_KIND_V2_TRAIT_OBJECT,
3071            ),
3072            value: self.value,
3073            vtable: Arc::clone(&self.vtable),
3074        }
3075    }
3076}
3077
3078// Wave 2 Agent E (2026-05-14): v2-raw HeapElement impl per ADR-006
3079// §Q25.C.5 amendment + audit §4.3 Obstacle O-3.a resolution. Constrains
3080// `TraitObjectStorage` to the HeapHeader-at-offset-0 v2-raw element-carrier
3081// contract so future call sites can store raw `*const TraitObjectStorage`
3082// bits and dispatch retain/release via the trait.
3083//
3084// The trait dispatches refcount through the on-header refcount via
3085// `v2_release` — distinct from the legacy `Arc<TraitObjectStorage>` path
3086// which dispatches via Rust `Arc::decrement_strong_count`. Per the struct
3087// docstring, both carrier shapes coexist at the struct level during the
3088// Wave 2 dispatch transition; the slot ABI discriminates them by
3089// allocation provenance (call sites that use `_new` and
3090// `from_trait_object_raw` follow the raw-pointer lifecycle; existing
3091// `Arc::new` + `from_trait_object` callers retain Arc-style lifecycle).
3092unsafe impl crate::v2::heap_element::HeapElement for TraitObjectStorage {
3093    unsafe fn release_elem(ptr: *const Self) {
3094        if unsafe { crate::v2::refcount::v2_release(&(*ptr).header) } {
3095            unsafe { Self::_drop(ptr as *mut Self) };
3096        }
3097    }
3098}
3099
3100// ── PriorityQueue storage (Wave 15 W15-priority-queue, 2026-05-10) ──────────
3101
3102/// PriorityQueue storage — i64-priority min-heap.
3103///
3104/// ADR-006 §2.7.18 / Q19 amendment (mirror of §2.7.15 HashSet precedent
3105/// for the cardinality-amendment shape). Storage is a binary min-heap
3106/// laid out in a single `Vec<i64>` over an `Arc<TypedBuffer<i64>>`
3107/// so the buffer-Arc pattern matches the rest of the typed-Arc heap
3108/// family (clone-on-write via `Arc::make_mut`, single atomic refcount
3109/// at slot drop).
3110///
3111/// **i64-priority-only at landing** (per the Wave 15 audit and the
3112/// §2.7.18 Q19 ruling). Heterogeneous-payload priority queues
3113/// (TypedObject-payload, payload-with-comparator-closure) are
3114/// explicitly out-of-scope; the playbook called out the i64-priority-
3115/// only design as the simpler valid path, and the smoke target
3116/// (`pq.push(3); pq.push(1); pq.push(2); pq.pop() == 1`) is exercised
3117/// end-to-end on this shape. A typed-payload rebuild (`PriorityQueue
3118/// <T, K>` with key-extractor and arbitrary `T` payloads) is a future
3119/// Phase-2c amendment with measurement.
3120///
3121/// The heap invariant is "min-heap": the minimum priority sits at
3122/// index 0 (`peek()` / `pop()` return it). Standard
3123/// sift-up-on-push / sift-down-on-pop maintenance, `O(log n)` per
3124/// push/pop.
3125#[derive(Debug)]
3126pub struct PriorityQueueData {
3127    /// Heap-ordered i64 priorities. Index 0 is the current min.
3128    /// Backed by an `Arc<Vec<i64>>` so a HeapValue clone is a single
3129    /// atomic refcount bump and `Arc::make_mut` is the canonical
3130    /// clone-on-write entry per the W13-hashmap-mutation precedent.
3131    ///
3132    /// Storage shape: `Arc<Vec<i64>>` post-V3-S5 ckpt-5-prime²a
3133    /// (Migration shape (a) per supervisor 2026-05-15 ratification —
3134    /// `TypedBuffer<T>` wrapper layer retired wholesale at ckpt-4;
3135    /// `Arc<Vec<T>>` is the smallest delta preserving `Arc::make_mut`
3136    /// clone-on-write semantics).
3137    pub heap: Arc<Vec<i64>>,
3138}
3139
3140impl PriorityQueueData {
3141    /// Build an empty PriorityQueueData with no entries.
3142    pub fn new() -> Self {
3143        Self {
3144            heap: Arc::new(Vec::new()),
3145        }
3146    }
3147
3148    /// Number of entries.
3149    #[inline]
3150    pub fn len(&self) -> usize {
3151        self.heap.len()
3152    }
3153
3154    /// Whether the queue is empty.
3155    #[inline]
3156    pub fn is_empty(&self) -> bool {
3157        self.heap.is_empty()
3158    }
3159
3160    /// Peek at the minimum (root) without removing it. Returns `None`
3161    /// for an empty queue.
3162    pub fn peek(&self) -> Option<i64> {
3163        self.heap.first().copied()
3164    }
3165
3166    /// Push a value, restoring the min-heap invariant via sift-up.
3167    /// Mirror of W13-hashmap-mutation `insert`: `Arc::make_mut`
3168    /// clone-on-write over the inner `Arc<Vec<i64>>`.
3169    pub fn push(&mut self, value: i64) {
3170        let buf = Arc::make_mut(&mut self.heap);
3171        buf.push(value);
3172        let last = buf.len() - 1;
3173        sift_up(buf, last);
3174    }
3175
3176    /// Pop the minimum value, restoring the min-heap invariant via
3177    /// sift-down. Returns `None` for an empty queue. Mirror of
3178    /// W13-hashmap-mutation `remove`: `Arc::make_mut` clone-on-write.
3179    pub fn pop(&mut self) -> Option<i64> {
3180        let buf = Arc::make_mut(&mut self.heap);
3181        if buf.is_empty() {
3182            return None;
3183        }
3184        let last = buf.len() - 1;
3185        buf.swap(0, last);
3186        let min = buf.pop();
3187        if !buf.is_empty() {
3188            sift_down(buf, 0);
3189        }
3190        min
3191    }
3192
3193    /// Return the heap contents as a flat `Vec<i64>` in heap-array
3194    /// order (NOT sorted). Used for the `toArray` method's `Vec<int>`
3195    /// projection; for the sorted form see `to_sorted_vec`.
3196    pub fn to_vec(&self) -> Vec<i64> {
3197        (*self.heap).clone()
3198    }
3199
3200    /// Return the heap contents as a sorted `Vec<i64>` (ascending —
3201    /// pop-order). Used for the `toSortedArray` method.
3202    pub fn to_sorted_vec(&self) -> Vec<i64> {
3203        let mut v: Vec<i64> = (*self.heap).clone();
3204        v.sort_unstable();
3205        v
3206    }
3207}
3208
3209impl Default for PriorityQueueData {
3210    fn default() -> Self {
3211        Self::new()
3212    }
3213}
3214
3215impl Clone for DequeData {
3216    fn clone(&self) -> Self {
3217        // Per-element `Arc<HeapValue>` clone bumps each element's strong-
3218        // count share; the resulting `VecDeque` is structurally
3219        // independent of the source. Mirror of `HashSetData::clone`'s
3220        // `Arc::clone(&keys)` shape but without the buffer-Arc indirection
3221        // (the per-element `Arc<HeapValue>` already provides the share).
3222        Self {
3223            items: self.items.iter().map(Arc::clone).collect(),
3224        }
3225    }
3226}
3227
3228impl Clone for PriorityQueueData {
3229    fn clone(&self) -> Self {
3230        Self {
3231            heap: Arc::clone(&self.heap),
3232        }
3233    }
3234}
3235
3236/// Sift up: restore the min-heap invariant after a push at index `i`
3237/// by walking parent links upward, swapping while the child is less
3238/// than its parent.
3239#[inline]
3240fn sift_up(data: &mut [i64], mut i: usize) {
3241    while i > 0 {
3242        let parent = (i - 1) / 2;
3243        if data[i] < data[parent] {
3244            data.swap(i, parent);
3245            i = parent;
3246        } else {
3247            break;
3248        }
3249    }
3250}
3251
3252/// Sift down: restore the min-heap invariant after a pop-replacement
3253/// at index `i` by walking down the smaller child link, swapping
3254/// while a child is less than the current node.
3255#[inline]
3256fn sift_down(data: &mut [i64], mut i: usize) {
3257    let n = data.len();
3258    loop {
3259        let left = 2 * i + 1;
3260        let right = 2 * i + 2;
3261        let mut smallest = i;
3262        if left < n && data[left] < data[smallest] {
3263            smallest = left;
3264        }
3265        if right < n && data[right] < data[smallest] {
3266            smallest = right;
3267        }
3268        if smallest == i {
3269            break;
3270        }
3271        data.swap(i, smallest);
3272        i = smallest;
3273    }
3274}
3275
3276// ── Range storage (W15-range, ADR-006 §2.7.23 / Q24, 2026-05-10) ────────────
3277
3278/// Range value carrier — an inclusive-or-exclusive integer interval with
3279/// step. Built by `MakeRange` from the surface syntax `start..end` (exclusive)
3280/// and `start..=end` (inclusive); produced as a typed `Arc<RangeData>` slot
3281/// labeled `NativeKind::Ptr(HeapKind::Range)`.
3282///
3283/// **Distinct from `IteratorState`.** Range is a value with identity
3284/// (`r.start`, `r.end`, `r.contains(x)`, `print(r)` -> `0..10`) — an
3285/// `IteratorState` is a stateful pipeline with a cursor. The `.iter()`
3286/// receiver method on Range converts a `RangeData` into a fresh
3287/// `IteratorState` with `IteratorSource::Range { start, end_exclusive,
3288/// step }`, where `end_exclusive` is `end + step` for inclusive ranges
3289/// (so `0..=10` step 1 produces values 0..11). `IteratorSource::Range`
3290/// already models the post-conversion shape (W13-iterator-state, ADR-006
3291/// §2.7.16); `RangeData` is the pre-iter receiver value.
3292///
3293/// **Bounds storage.** Today only `i64` integer ranges are representable.
3294/// The `Option<i64>` shape used by the deleted pre-bulldozer Range payload
3295/// (open ranges `..end`, `start..`, `..`) is deliberately NOT modeled here:
3296/// the surface syntax for open ranges still compiles via `op_make_range`
3297/// pushing a `PushNull` for the missing side, but the SURFACE handler
3298/// rejects them per the playbook's surface-and-stop discipline (open
3299/// ranges need an iterator-tier semantic for `for i in 0..` infinite
3300/// loops which is its own ADR follow-up). `step` is always positive —
3301/// matching the pre-strict-typing `0..n` Rust-shape semantics.
3302#[derive(Debug, Clone)]
3303pub struct RangeData {
3304    /// Inclusive lower bound.
3305    pub start: i64,
3306    /// Upper bound. When `inclusive == true`, the value `end` itself is
3307    /// reachable; when `inclusive == false`, `end` is exclusive (the
3308    /// surface-syntax `start..end` shape).
3309    pub end: i64,
3310    /// Per-iteration increment. Always positive; defaults to 1 from the
3311    /// `MakeRange` opcode (the surface syntax has no step suffix today).
3312    pub step: i64,
3313    /// Whether the upper bound is reachable (`start..=end` shape).
3314    pub inclusive: bool,
3315}
3316
3317impl RangeData {
3318    /// Construct a fresh range with the given bounds and step.
3319    #[inline]
3320    pub fn new(start: i64, end: i64, step: i64, inclusive: bool) -> Self {
3321        Self {
3322            start,
3323            end,
3324            step,
3325            inclusive,
3326        }
3327    }
3328
3329    /// Construct an exclusive range `start..end` with step 1 (matching
3330    /// the surface-syntax `0..n` shape).
3331    #[inline]
3332    pub fn exclusive(start: i64, end: i64) -> Self {
3333        Self::new(start, end, 1, false)
3334    }
3335
3336    /// Construct an inclusive range `start..=end` with step 1.
3337    #[inline]
3338    pub fn inclusive(start: i64, end: i64) -> Self {
3339        Self::new(start, end, 1, true)
3340    }
3341
3342    /// Effective exclusive end — `end + step` for inclusive ranges,
3343    /// `end` for exclusive ranges. Matches the upper bound used by
3344    /// `IteratorSource::Range`'s `end` field (which is exclusive by
3345    /// W13-iterator-state's contract).
3346    #[inline]
3347    pub fn end_exclusive(&self) -> i64 {
3348        if self.inclusive {
3349            self.end.saturating_add(self.step)
3350        } else {
3351            self.end
3352        }
3353    }
3354
3355    /// Element count. Mirrors `IteratorSource::Range::len` so a range
3356    /// and its post-`.iter()` IteratorState report the same count. For
3357    /// non-positive step or an empty interval, returns 0.
3358    #[inline]
3359    pub fn len(&self) -> usize {
3360        let end = self.end_exclusive();
3361        if self.step <= 0 || end <= self.start {
3362            return 0;
3363        }
3364        let span = (end - self.start) as u64;
3365        let step = self.step as u64;
3366        ((span + step - 1) / step) as usize
3367    }
3368
3369    /// Whether the range yields zero elements.
3370    #[inline]
3371    pub fn is_empty(&self) -> bool {
3372        self.len() == 0
3373    }
3374
3375    /// Whether `value` falls within the range. The check is bound-aware
3376    /// (inclusive vs exclusive end) but does NOT enforce step alignment
3377    /// — `(0..10).contains(5)` is true regardless of step. This matches
3378    /// the pre-bulldozer surface-syntax shape: `range.contains` is a
3379    /// bound test, not a "would .iter() yield this exact value" probe.
3380    #[inline]
3381    pub fn contains(&self, value: i64) -> bool {
3382        if value < self.start {
3383            return false;
3384        }
3385        if self.inclusive {
3386            value <= self.end
3387        } else {
3388            value < self.end
3389        }
3390    }
3391
3392    /// Materialize the range into a `Vec<i64>` of every yielded value
3393    /// (mirror of `.iter().collect()` for the pre-bulldozer
3394    /// `range.toArray()` method shape). Empty range -> empty vec.
3395    pub fn to_vec_i64(&self) -> Vec<i64> {
3396        let n = self.len();
3397        let mut out = Vec::with_capacity(n);
3398        let mut v = self.start;
3399        for _ in 0..n {
3400            out.push(v);
3401            v += self.step;
3402        }
3403        out
3404    }
3405}
3406
3407// ── TaskGroup storage (ADR-006 §2.3) ────────────────────────────────────────
3408
3409/// Task-group payload. Extracted from the inline
3410/// `HeapValue::TaskGroup { kind, task_ids }` struct variant per ADR-006 §2.3
3411/// so `HeapValue::TaskGroup` becomes a single-tuple `Arc<T>` payload like
3412/// every other ADR-006 §2.3 heap arm.
3413///
3414/// The struct preserves the `kind` discriminant and `task_ids` list verbatim
3415/// — clone semantics live on the enclosing `Arc<TaskGroupData>` (one atomic
3416/// refcount bump). Phase 1.B migrates the cascade pattern-match sites
3417/// (`shape-vm::executor::async_ops`, `shape-jit::ffi::async_ops`,
3418/// `shape-runtime::wire_conversion`, ...) from struct-variant destructuring
3419/// to `task_group.kind` / `task_group.task_ids` field reads.
3420#[derive(Debug, Clone)]
3421pub struct TaskGroupData {
3422    pub kind: u8,
3423    pub task_ids: Vec<u64>,
3424}
3425
3426// ── TypedObject storage (ADR-006 §2.3 / §2.5) ───────────────────────────────
3427
3428/// Schema-keyed object storage. Extracted from the inline
3429/// `HeapValue::TypedObject { schema_id, slots, heap_mask }` struct
3430/// variant per ADR-006 §2.3, so that:
3431///
3432/// 1. `HeapValue::TypedObject` becomes `HeapValue::TypedObject(Arc<TypedObjectStorage>)`
3433///    — a typed `Arc<T>` payload like every other ADR-006 §2.3 heap arm.
3434/// 2. The `Drop` impl (Step 5) lives on `TypedObjectStorage` and dispatches
3435///    per-field on `NativeKind` from the embedded `field_kinds: Arc<[NativeKind]>`
3436///    table — no schema-registry probe and no cross-crate function-pointer
3437///    hook at drop time.
3438///
3439/// Field invariants (ADR-006 §2.3):
3440///
3441/// - `schema_id` is the registry key for the TypeSchema. Kept for wire /
3442///   snapshot round-trip and downstream schema-aware code (printing,
3443///   marshal); not consulted at drop time.
3444/// - `slots` is a per-field 8-byte storage array. Field at index `i`
3445///   stores its bits per the schema's `FieldType` for that field.
3446/// - `heap_mask` has bit `i` set iff slot `i` holds a heap pointer
3447///   (`Arc<T>` raw pointer per ADR-006 §2.4). Bits beyond `slots.len()`
3448///   must be zero.
3449/// - `field_kinds` is an `Arc<[NativeKind]>` of length `slots.len()`,
3450///   one entry per field, carrying the proven `NativeKind` for that
3451///   field's slot bits. The Arc payload is **shared per-schema**: the
3452///   construction path (in `shape-runtime`) maps `schema_id ⇒ Arc<[NativeKind]>`
3453///   once per schema (one HashMap probe at the first construction; cached
3454///   for subsequent constructions) and clones the cached Arc into each
3455///   instance — so 1M Customer-objects of the same shape share one
3456///   `[NativeKind]` allocation. Drop is then constant-time per slot
3457///   without any cross-crate registry call.
3458///
3459/// Why the `Arc<[NativeKind]>` (Option B' per supervisor ruling, ADR-006 §17):
3460///
3461/// - **Option B** (per-instance `Box<[NativeKind]>`) was rejected: it
3462///   duplicates the same NativeKind sequence across every instance of a
3463///   schema (1M × 8 fields = 16MB cumulative duplication).
3464/// - **Option C** (function-pointer hook in shape-value installed by
3465///   shape-runtime) was rejected: it adds a cross-crate runtime hook for
3466///   metadata that's already known at construction time.
3467/// - **Option B'** (this one) does the lookup once at construction (where
3468///   the schema is in scope) and shares the result via Arc — 8-byte
3469///   pointer per instance, single payload allocation per schema, no
3470///   probe at drop. Per Q8 spirit: the schema lookup happens, but it's
3471///   profile-driven *preempted* to construction time and cached.
3472///
3473/// `TypedObjectStorage` is `pub` with `pub` fields so the existing
3474/// destructuring call sites can migrate by reading
3475/// `storage.schema_id` / `storage.slots` / `storage.heap_mask`. The
3476/// struct is intentionally not `Clone` — clone semantics belong to the
3477/// enclosing `Arc<TypedObjectStorage>` (one atomic refcount bump).
3478///
3479/// **Wave 2 Agent D1 (2026-05-14): HeapHeader-equipped shape change.**
3480/// Per audit §4.3 Obstacle O-3.a resolution + ADR-006 §2.3 amendment, the
3481/// struct now carries a `HeapHeader` at offset 0 (`#[repr(C)]`) so v2-raw
3482/// raw-pointer allocations (`_new` / `_drop` + `impl HeapElement`) can
3483/// dispatch refcount on the header via `v2_retain` / `v2_release`. Existing
3484/// `Arc<TypedObjectStorage>` construction sites continue to work
3485/// unchanged — `Arc::new(TypedObjectStorage::new(...))` produces a Rust
3486/// `Arc`-wrapped instance whose embedded header sits at refcount=1 unused;
3487/// the dispatch arms continue to use `Arc::increment_strong_count` /
3488/// `Arc::decrement_strong_count` on those bits. The new `_new`-allocated
3489/// raw-pointer bits use the header's refcount via the `HeapElement` trait.
3490/// Agent D2 (Wave 2 Round 2) migrates the 18 production construction sites
3491/// to the raw-pointer carrier; Agent E (Wave 2 Round 2) consumes the same
3492/// shape change for `TraitObjectStorage`. Until that migration completes,
3493/// both carriers coexist at the struct level; the slot-ABI discriminator
3494/// (`NativeKind::Ptr(HeapKind::TypedObject)`) is unchanged.
3495#[repr(C)]
3496#[derive(Debug)]
3497pub struct TypedObjectStorage {
3498    /// v2-raw HeapHeader at offset 0 (8 bytes). Refcount/kind/flags.
3499    /// Initialized to `HeapHeader::new(HEAP_KIND_V2_TYPED_OBJECT)` by
3500    /// `_new`; for `Arc`-wrapped instances allocated via
3501    /// `TypedObjectStorage::new` the header sits at refcount=1 unused
3502    /// (the enclosing `Arc` owns the lifecycle). See struct docstring.
3503    pub header: crate::v2::heap_header::HeapHeader,
3504    /// Registry key for the TypeSchema describing each slot's `FieldType`.
3505    pub schema_id: u64,
3506    /// Per-field 8-byte storage. Length matches the schema's field count.
3507    pub slots: Box<[crate::slot::ValueSlot]>,
3508    /// Bit `i` set ⇔ slot `i` holds a heap pointer that participates in
3509    /// Arc refcount discipline. Bits beyond `slots.len()` must be zero.
3510    pub heap_mask: u64,
3511    /// Per-field `NativeKind` table — same length as `slots`. **Shared
3512    /// per-schema** via `Arc`: every instance of the same schema clones
3513    /// the same payload (one atomic refcount bump per construction).
3514    /// Consulted by `Drop` to dispatch per-slot `Arc::decrement_strong_count`
3515    /// without any schema-registry probe.
3516    pub field_kinds: std::sync::Arc<[crate::native_kind::NativeKind]>,
3517}
3518
3519impl TypedObjectStorage {
3520    /// Construct a new `TypedObjectStorage`.
3521    ///
3522    /// Construction-side contract (callers in `shape-runtime`):
3523    ///
3524    /// 1. `slots.len() == field_kinds.len()` — one kind per slot.
3525    /// 2. For each bit `i` set in `heap_mask`, `field_kinds[i]` must be
3526    ///    a heap-pointer kind (`NativeKind::String` or
3527    ///    `NativeKind::Ptr(_)`) and the slot's `u64` must be the raw
3528    ///    pointer of an `Arc::into_raw::<T>` for the matching `T`. Drop
3529    ///    relies on this for soundness.
3530    /// 3. `field_kinds` should be the per-schema cached `Arc<[NativeKind]>`
3531    ///    (callers maintain a `schema_id ⇒ Arc<[NativeKind]>` cache to
3532    ///    avoid per-instance allocation).
3533    ///
3534    /// Returns the storage by value; the canonical wrap is
3535    /// `Arc::new(TypedObjectStorage::new(...))` immediately followed by
3536    /// `HeapValue::TypedObject(arc)` or `ValueSlot::from_typed_object(arc)`.
3537    #[inline]
3538    pub fn new(
3539        schema_id: u64,
3540        slots: Box<[crate::slot::ValueSlot]>,
3541        heap_mask: u64,
3542        field_kinds: std::sync::Arc<[crate::native_kind::NativeKind]>,
3543    ) -> Self {
3544        debug_assert_eq!(
3545            slots.len(),
3546            field_kinds.len(),
3547            "TypedObjectStorage::new: slots/field_kinds length mismatch \
3548             (slots={}, field_kinds={}) — every slot must have a proven NativeKind",
3549            slots.len(),
3550            field_kinds.len(),
3551        );
3552        Self {
3553            header: crate::v2::heap_header::HeapHeader::new(
3554                crate::v2::heap_header::HEAP_KIND_V2_TYPED_OBJECT,
3555            ),
3556            schema_id,
3557            slots,
3558            heap_mask,
3559            field_kinds,
3560        }
3561    }
3562
3563    /// Wave 2 Agent D1 (2026-05-14): v2-raw raw-pointer allocator.
3564    ///
3565    /// Allocates a new `TypedObjectStorage` on the heap and returns a raw
3566    /// pointer with refcount initialized to 1. Mirrors the `DecimalObj::new`
3567    /// / `StringObj::new` precedents at `crates/shape-value/src/v2/` —
3568    /// `#[repr(C)]` struct with `HeapHeader` at offset 0; refcount discipline
3569    /// goes through `v2_retain` / `v2_release` via the `HeapElement` trait.
3570    ///
3571    /// Construction-side contract: same as `new()` — `slots.len() ==
3572    /// field_kinds.len()`; heap-mask bits correspond to heap-kinded slots
3573    /// whose bits are `Arc::into_raw::<T>` for the matching `T`. The raw-
3574    /// pointer carrier owns one strong-count share for every heap-kinded
3575    /// slot it carries, retired by `_drop` at refcount=0.
3576    ///
3577    /// Callers (Wave 2 Agent D2, Round 2 cascade): construct via
3578    /// `TypedObjectStorage::_new(...)` and store the pointer in
3579    /// `ValueSlot::from_typed_object_raw(ptr)`. Drop runs at refcount=0
3580    /// via the `HeapElement::release_elem` trait method, NOT via Rust
3581    /// `Arc::drop` (the `Arc<TypedObjectStorage>` path is the legacy
3582    /// transitional carrier; both coexist at the struct level per the
3583    /// struct docstring).
3584    pub fn _new(
3585        schema_id: u64,
3586        slots: Box<[crate::slot::ValueSlot]>,
3587        heap_mask: u64,
3588        field_kinds: std::sync::Arc<[crate::native_kind::NativeKind]>,
3589    ) -> *mut Self {
3590        debug_assert_eq!(
3591            slots.len(),
3592            field_kinds.len(),
3593            "TypedObjectStorage::_new: slots/field_kinds length mismatch \
3594             (slots={}, field_kinds={}) — every slot must have a proven NativeKind",
3595            slots.len(),
3596            field_kinds.len(),
3597        );
3598        let layout = std::alloc::Layout::new::<Self>();
3599        let ptr = unsafe { std::alloc::alloc(layout) as *mut Self };
3600        assert!(!ptr.is_null(), "allocation failed for TypedObjectStorage");
3601        unsafe {
3602            // SAFETY: `ptr` points to fresh, uninitialized memory of size
3603            // `Layout::new::<Self>()`. We write every field via `ptr::write`
3604            // to avoid running drop on uninitialized bytes (the existing
3605            // memory contains garbage, never a valid prior `Self`).
3606            std::ptr::write(
3607                &mut (*ptr).header,
3608                crate::v2::heap_header::HeapHeader::new(
3609                    crate::v2::heap_header::HEAP_KIND_V2_TYPED_OBJECT,
3610                ),
3611            );
3612            std::ptr::write(&mut (*ptr).schema_id, schema_id);
3613            std::ptr::write(&mut (*ptr).slots, slots);
3614            std::ptr::write(&mut (*ptr).heap_mask, heap_mask);
3615            std::ptr::write(&mut (*ptr).field_kinds, field_kinds);
3616        }
3617        ptr
3618    }
3619
3620    /// Wave 2 Agent D1 (2026-05-14): v2-raw raw-pointer deallocator.
3621    ///
3622    /// Runs the per-field heap-mask walk (releasing one strong-count share
3623    /// per heap-kinded slot via `Arc::decrement_strong_count`) and then
3624    /// deallocates the struct's heap memory via `Layout::new::<Self>()`.
3625    /// The field walk delegates to `drop_fields` so the same logic powers
3626    /// both the legacy `impl Drop for TypedObjectStorage` path (used by
3627    /// `Arc<TypedObjectStorage>` instances) and this raw-pointer path.
3628    ///
3629    /// Mirrors the `DecimalObj::drop` / `StringObj::drop` precedents.
3630    ///
3631    /// # Safety
3632    /// `ptr` must point to a live `TypedObjectStorage` allocated via
3633    /// `Self::_new` with no remaining references. Must not be called more
3634    /// than once on the same pointer; must not be called on
3635    /// `Arc<TypedObjectStorage>`-allocated instances (those run through
3636    /// Rust's `Arc` drop machinery + `impl Drop for TypedObjectStorage`).
3637    pub unsafe fn _drop(ptr: *mut Self) {
3638        unsafe {
3639            // Run the per-field heap-mask walk, retiring one Arc share per
3640            // heap-kinded slot. Same logic that `impl Drop` runs for the
3641            // legacy `Arc<TypedObjectStorage>` path.
3642            (*ptr).drop_fields();
3643            // Drop the in-place `Box<[ValueSlot]>` and `Arc<[NativeKind]>`
3644            // payloads so their allocations are freed. The `header`,
3645            // `schema_id`, and `heap_mask` fields are POD (`Copy` or
3646            // primitive) — no Drop work owed.
3647            std::ptr::drop_in_place(&mut (*ptr).slots);
3648            std::ptr::drop_in_place(&mut (*ptr).field_kinds);
3649            // Deallocate the struct's heap memory.
3650            let layout = std::alloc::Layout::new::<Self>();
3651            std::alloc::dealloc(ptr as *mut u8, layout);
3652        }
3653    }
3654
3655    /// Wave 2 Agent D1 (2026-05-14): shared per-field heap-mask walk.
3656    ///
3657    /// Walks `heap_mask`, dispatches per-slot on `field_kinds[i]`, and
3658    /// retires one strong-count share per heap-kinded slot via
3659    /// `Arc::decrement_strong_count::<T>` for the matching `T`. Same
3660    /// dispatch as `impl Drop for TypedObjectStorage` (and same as the
3661    /// 4-table-lockstep arms in `kinded_slot.rs::drop` /
3662    /// `vm_impl/stack.rs::drop_with_kind` / `closure_layout.rs::
3663    /// SharedCell::drop`). Called by both `impl Drop` (legacy
3664    /// `Arc<TypedObjectStorage>` path) and `_drop` (raw-pointer path).
3665    ///
3666    /// # Safety
3667    /// Caller must guarantee `self` is in a live state (slots /
3668    /// field_kinds / heap_mask all valid per the `new` / `_new`
3669    /// construction-side contract). Must run at most once per instance.
3670    unsafe fn drop_fields(&mut self) {
3671        use crate::heap_value::HeapKind;
3672        use crate::native_kind::NativeKind;
3673
3674        // Defensive: if construction left a length mismatch (debug_assert
3675        // catches it earlier), drop only the prefix where both bookkeeping
3676        // structures agree. Better a leak than UB.
3677        let n = self.slots.len().min(self.field_kinds.len());
3678        for i in 0..n {
3679            // heap_mask is u64; bits beyond 63 cannot be addressed today.
3680            if i >= 64 {
3681                break;
3682            }
3683            if (self.heap_mask >> i) & 1 == 0 {
3684                continue;
3685            }
3686            let bits = self.slots[i].raw();
3687            if bits == 0 {
3688                continue;
3689            }
3690            // SAFETY (each arm): the construction-side contract guarantees
3691            // that for every set heap_mask bit, the slot's bits are the
3692            // result of `Arc::into_raw::<T>` where `T` matches `field_kinds[i]`.
3693            // We reclaim exactly one strong-count share per slot via
3694            // `Arc::decrement_strong_count::<T>` and then never look at the
3695            // bits again.
3696            unsafe {
3697                match self.field_kinds[i] {
3698                    NativeKind::String => {
3699                        std::sync::Arc::decrement_strong_count(bits as *const String);
3700                    }
3701                    // Wave 2 Agent B (ADR-006 §2.7.5 amendment, 2026-05-14):
3702                    // A TypedObject field of kind `NativeKind::StringV2` /
3703                    // `NativeKind::DecimalV2` holds slot bits = `ptr as u64`
3704                    // where `ptr: *const StringObj` / `*const DecimalObj`
3705                    // — v2-raw carrier shape per the §H.4 H-c decision.
3706                    // Refcount discipline goes through `release_elem`
3707                    // (HeapElement trait — calls `v2_release` against the
3708                    // HeapHeader at offset 0; on refcount=0 the carrier-side
3709                    // `drop` deallocates the repr(C) 24-byte struct). NOT
3710                    // `Arc::decrement_strong_count` — these are manually-
3711                    // allocated carriers, not `Arc<T>` allocations.
3712                    NativeKind::StringV2 => {
3713                        use crate::v2::heap_element::HeapElement;
3714                        crate::v2::string_obj::StringObj::release_elem(
3715                            bits as *const crate::v2::string_obj::StringObj,
3716                        );
3717                    }
3718                    NativeKind::DecimalV2 => {
3719                        use crate::v2::heap_element::HeapElement;
3720                        crate::v2::decimal_obj::DecimalObj::release_elem(
3721                            bits as *const crate::v2::decimal_obj::DecimalObj,
3722                        );
3723                    }
3724                    NativeKind::Ptr(hk) => match hk {
3725                        HeapKind::String => {
3726                            std::sync::Arc::decrement_strong_count(bits as *const String);
3727                        }
3728                        // V3-S5 ckpt-5-prime (2026-05-15): `HeapKind::TypedArray`
3729                        // dispatch arm RETIRED per W12 audit §3.6 + handover §0
3730                        // 4-table lockstep rule. The `TypedArrayData` enum was
3731                        // deleted at ckpt-1; the outer `HeapValue::TypedArray`
3732                        // arm at ckpt-4. Ordinal 8 remains as a vacated marker
3733                        // in `heap_variants.rs::HeapKind` (per ordinal-collision
3734                        // rule — `value_ffi.rs::HK_TYPED_TABLE` asserts the
3735                        // collision lineage). No live slot bits in compiled
3736                        // bytecode carry `NativeKind::Ptr(HeapKind::TypedArray)`
3737                        // post-ckpt-4 (all producers migrated to v2-raw
3738                        // `*mut TypedArray<T>` carriers per ADR-006 §2.7.24
3739                        // Q25.A SUPERSEDED). Refusal #1 binding: do not
3740                        // reintroduce under any rename/shim/bridge.
3741                        HeapKind::TypedArray => {
3742                            unreachable!(
3743                                "HeapKind::TypedArray ordinal 8 is vacated per W12 audit §3.6; \
3744                                 no live slot bits carry this kind post-V3-S5 ckpt-4 (TypedArrayData \
3745                                 enum + outer HeapValue::TypedArray arm deleted; v2-raw \
3746                                 *mut TypedArray<T> carriers per ADR-006 §2.7.24 Q25.A SUPERSEDED)"
3747                            );
3748                        }
3749                        // Wave 2 Agent D4 ckpt-2 (ADR-006 §2.3 / §2.7.5
3750                        // amendment, 2026-05-14): a `TypedObject` field of
3751                        // kind `NativeKind::Ptr(HeapKind::TypedObject)`
3752                        // holds slot bits = `ptr as u64` where
3753                        // `ptr: *const TypedObjectStorage` (v2-raw carrier
3754                        // per Agent D1's `_new` /
3755                        // `impl HeapElement for TypedObjectStorage`).
3756                        // Refcount discipline goes through `release_elem`
3757                        // (HeapElement trait — calls `v2_release` against
3758                        // the HeapHeader at offset 0; on refcount=0 the
3759                        // carrier-side `_drop` runs the per-field
3760                        // heap-mask walk and deallocates the `repr(C)`
3761                        // struct). Mirror of the §2.7.5 StringV2 /
3762                        // DecimalV2 release arms above (Agent B precedent).
3763                        HeapKind::TypedObject => {
3764                            use crate::v2::heap_element::HeapElement;
3765                            TypedObjectStorage::release_elem(
3766                                bits as *const TypedObjectStorage,
3767                            );
3768                        }
3769                        HeapKind::HashMap => {
3770                            // Wave 2 Round 3b C2-joint ckpt-2 (2026-05-14):
3771                            // bits are `Arc::into_raw(Arc<HashMapKindedRef>)`
3772                            // per ADR-006 §2.7.24 Q25.B SUPERSEDED carrier
3773                            // shape. Release dispatches outer Arc decrement;
3774                            // enum Drop chains to per-V `Arc<HashMapData<V>>`
3775                            // release.
3776                            std::sync::Arc::decrement_strong_count(
3777                                bits as *const HashMapKindedRef,
3778                            );
3779                        }
3780                        HeapKind::HashSet => {
3781                            std::sync::Arc::decrement_strong_count(bits as *const HashSetData);
3782                        }
3783                        HeapKind::Deque => {
3784                            std::sync::Arc::decrement_strong_count(bits as *const DequeData);
3785                        }
3786                        HeapKind::Channel => {
3787                            std::sync::Arc::decrement_strong_count(bits as *const ChannelData);
3788                        }
3789                        HeapKind::Mutex => {
3790                            std::sync::Arc::decrement_strong_count(bits as *const MutexData);
3791                        }
3792                        HeapKind::Atomic => {
3793                            std::sync::Arc::decrement_strong_count(bits as *const AtomicData);
3794                        }
3795                        HeapKind::Lazy => {
3796                            std::sync::Arc::decrement_strong_count(bits as *const LazyData);
3797                        }
3798                        // Wave 2 Agent D4 ckpt-2 (ADR-006 §2.7.24 /
3799                        // Q25.C.5 + E close 2026-05-14): TraitObject
3800                        // release via `HeapElement::release_elem` +
3801                        // carrier-side `_drop` (per Agent E's
3802                        // `impl HeapElement for TraitObjectStorage`).
3803                        // Mirror of the TypedObject arm above.
3804                        HeapKind::TraitObject => {
3805                            use crate::v2::heap_element::HeapElement;
3806                            TraitObjectStorage::release_elem(
3807                                bits as *const TraitObjectStorage,
3808                            );
3809                        }
3810                        HeapKind::Decimal => {
3811                            std::sync::Arc::decrement_strong_count(
3812                                bits as *const rust_decimal::Decimal,
3813                            );
3814                        }
3815                        HeapKind::BigInt => {
3816                            std::sync::Arc::decrement_strong_count(bits as *const i64);
3817                        }
3818                        HeapKind::DataTable => {
3819                            std::sync::Arc::decrement_strong_count(
3820                                bits as *const crate::datatable::DataTable,
3821                            );
3822                        }
3823                        HeapKind::IoHandle => {
3824                            std::sync::Arc::decrement_strong_count(bits as *const IoHandleData);
3825                        }
3826                        HeapKind::NativeView => {
3827                            std::sync::Arc::decrement_strong_count(
3828                                bits as *const NativeViewData,
3829                            );
3830                        }
3831                        HeapKind::Content => {
3832                            std::sync::Arc::decrement_strong_count(
3833                                bits as *const crate::content::ContentNode,
3834                            );
3835                        }
3836                        HeapKind::Instant => {
3837                            std::sync::Arc::decrement_strong_count(
3838                                bits as *const std::time::Instant,
3839                            );
3840                        }
3841                        HeapKind::Temporal => {
3842                            std::sync::Arc::decrement_strong_count(bits as *const TemporalData);
3843                        }
3844                        HeapKind::TableView => {
3845                            std::sync::Arc::decrement_strong_count(bits as *const TableViewData);
3846                        }
3847                        HeapKind::TaskGroup => {
3848                            std::sync::Arc::decrement_strong_count(bits as *const TaskGroupData);
3849                        }
3850                        HeapKind::FilterExpr => {
3851                            std::sync::Arc::decrement_strong_count(
3852                                bits as *const crate::value::FilterNode,
3853                            );
3854                        }
3855                        HeapKind::Reference => {
3856                            std::sync::Arc::decrement_strong_count(
3857                                bits as *const crate::reference::RefTarget,
3858                            );
3859                        }
3860                        HeapKind::Iterator => {
3861                            std::sync::Arc::decrement_strong_count(
3862                                bits as *const crate::iterator_state::IteratorState,
3863                            );
3864                        }
3865                        HeapKind::PriorityQueue => {
3866                            std::sync::Arc::decrement_strong_count(
3867                                bits as *const PriorityQueueData,
3868                            );
3869                        }
3870                        HeapKind::Range => {
3871                            std::sync::Arc::decrement_strong_count(bits as *const RangeData);
3872                        }
3873                        HeapKind::Result => {
3874                            std::sync::Arc::decrement_strong_count(bits as *const ResultData);
3875                        }
3876                        HeapKind::Option => {
3877                            std::sync::Arc::decrement_strong_count(bits as *const OptionData);
3878                        }
3879                        HeapKind::Closure => {
3880                            std::sync::Arc::decrement_strong_count(bits as *const HeapValue);
3881                        }
3882                        HeapKind::Future => {
3883                            // No-op: future-id inline scalar.
3884                        }
3885                        HeapKind::ModuleFn => {
3886                            // No-op: module-fn-id inline scalar.
3887                        }
3888                        HeapKind::Matrix => {
3889                            std::sync::Arc::decrement_strong_count(bits as *const MatrixData);
3890                        }
3891                        HeapKind::MatrixSlice => {
3892                            std::sync::Arc::decrement_strong_count(
3893                                bits as *const MatrixSliceData,
3894                            );
3895                        }
3896                        HeapKind::SharedCell => {
3897                            std::sync::Arc::decrement_strong_count(
3898                                bits as *const crate::v2::closure_layout::SharedCell,
3899                            );
3900                        }
3901                        HeapKind::Char => {
3902                            debug_assert!(
3903                                false,
3904                                "TypedObjectStorage::drop_fields: heap_mask bit {} set with \
3905                                 inline-scalar kind Char (schema_id={}); \
3906                                 construction-side soundness violation",
3907                                i, self.schema_id
3908                            );
3909                        }
3910                        HeapKind::NativeScalar => {
3911                            debug_assert!(
3912                                false,
3913                                "TypedObjectStorage::drop_fields: NativeScalar kinded carrier \
3914                                 pending phase-2c kinded redesign (ADR-006 §2.7.4); \
3915                                 schema_id={}, bit {}",
3916                                self.schema_id, i
3917                            );
3918                        }
3919                    },
3920                    other => {
3921                        debug_assert!(
3922                            false,
3923                            "TypedObjectStorage::drop_fields: heap_mask bit {} set with \
3924                             non-heap NativeKind {:?} (schema_id={}); \
3925                             construction-side soundness violation",
3926                            i, other, self.schema_id
3927                        );
3928                    }
3929                }
3930            }
3931        }
3932    }
3933
3934    /// In-place write of slot `idx` through a shared `&TypedObjectStorage`
3935    /// (i.e. through an `Arc<TypedObjectStorage>` with refcount > 1).
3936    /// Returns the prior `(bits, kind)` so the caller can run
3937    /// `drop_with_kind` on the released share. The caller transfers
3938    /// ownership of `new_bits` (one strong-count share for heap kinds) to
3939    /// the slot.
3940    ///
3941    /// This is the Q14 / ADR-006 §2.7.13 in-place write path for
3942    /// `RefTarget::TypedField` projection writes — the receiver `Arc`
3943    /// is shared between the ref carrier and the originating binding,
3944    /// so `Arc::get_mut` / `Arc::make_mut` cannot apply (refcount > 1
3945    /// by construction, and `TypedObjectStorage` is intentionally not
3946    /// `Clone` per the §2.5 documentation). The `Box<[ValueSlot]>`
3947    /// inside the storage is logically owned; the single-word `u64`
3948    /// inside each `ValueSlot` is written atomically (single-word
3949    /// aligned store on every supported architecture).
3950    ///
3951    /// # Safety
3952    ///
3953    /// Callers must guarantee:
3954    ///
3955    /// 1. **Single-threaded write**: the VM is single-threaded, and the
3956    ///    refs that drive this path are constrained by the §3.1
3957    ///    ref-escape analysis to stay within their originating task
3958    ///    (refs cannot cross task boundaries — error B0014
3959    ///    `NonSendableAcrossTaskBoundary`). No other thread may hold an
3960    ///    `&Arc<TypedObjectStorage>` to the same storage at the same
3961    ///    time the write executes.
3962    /// 2. **No aliased `&mut ValueSlot`**: callers must NOT mint a
3963    ///    `&mut ValueSlot` to slot `idx` from any path while this write
3964    ///    is in flight. The Q14 dispatch in `op_deref_store` /
3965    ///    `op_set_index_ref` is the only caller, and it operates on
3966    ///    `&TypedObjectStorage` exclusively.
3967    /// 3. **Kind invariance**: `new_kind` must equal
3968    ///    `self.field_kinds[idx]`. The Q14 RefTarget carries the
3969    ///    projected slot's kind at construction (`MakeFieldRef` sources
3970    ///    it from `field_type_tag`); the post-proof `§2.7.5.1` contract
3971    ///    forbids mid-life kind changes for typed fields. The caller
3972    ///    debug_asserts this before calling.
3973    /// 4. **`heap_mask` bit consistency**: for heap-kinded slots
3974    ///    (NativeKind::String or Ptr(HeapKind::_)), the corresponding
3975    ///    `heap_mask` bit must already be set per the `TypedObjectStorage::new`
3976    ///    construction-side contract, AND the prior bits must be a
3977    ///    valid `Arc::into_raw::<T>` for the slot's kind. The returned
3978    ///    `prior_bits` is exactly that share; the caller releases it via
3979    ///    `drop_with_kind` after running the post-write barrier.
3980    ///
3981    /// Q14 / ADR-006 §2.7.13. Mirror of the `clone_with_kind` /
3982    /// `drop_with_kind` symmetry used by `RefTarget::Local` and
3983    /// `RefTarget::ModuleBinding` writes (`stack_write_kinded` and
3984    /// `module_binding_write_kinded` already encapsulate this pattern
3985    /// for non-projected places; this is the projected-place mirror).
3986    #[inline]
3987    pub unsafe fn write_slot_in_place(
3988        &self,
3989        idx: usize,
3990        new_bits: u64,
3991    ) -> u64 {
3992        debug_assert!(
3993            idx < self.slots.len(),
3994            "TypedObjectStorage::write_slot_in_place: idx {} out of bounds (slots.len = {})",
3995            idx,
3996            self.slots.len(),
3997        );
3998        // SAFETY: see method contract. Single-threaded VM; refs cannot
3999        // escape across task boundaries; no aliased `&mut ValueSlot`
4000        // outstanding by construction; `Box<[ValueSlot]>` is `Sized`-laid-
4001        // out and the slot's `u64` is naturally aligned. We cast through
4002        // `&[ValueSlot]` -> `*const ValueSlot` -> `*mut ValueSlot` to
4003        // perform the single-word write. The slot's `field_kinds[idx]`
4004        // is the kind invariant; the caller already debug_asserted kind
4005        // equality, so the slot's heap-mask bit (if set) still applies
4006        // to the new bits.
4007        let slot_ptr = self.slots.as_ptr().add(idx) as *mut crate::slot::ValueSlot;
4008        let prior = (*slot_ptr).raw();
4009        *slot_ptr = crate::slot::ValueSlot::from_raw(new_bits);
4010        prior
4011    }
4012}
4013
4014impl Drop for TypedObjectStorage {
4015    /// ADR-006 §2.5 + Wave 2 Agent D1 (2026-05-14): delegates to the shared
4016    /// `drop_fields` helper that walks `heap_mask` and dispatches per-slot
4017    /// on `field_kinds[i]`. The same helper powers `_drop` (raw-pointer
4018    /// path) so both the legacy `Arc<TypedObjectStorage>` lifecycle and
4019    /// the v2-raw raw-pointer lifecycle retire heap-slot Arc shares with
4020    /// identical semantics.
4021    ///
4022    /// Soundness contract (must hold by construction; see
4023    /// `TypedObjectStorage::new` / `_new`):
4024    ///
4025    /// - For every `i` where `heap_mask >> i & 1 == 1`, the slot's `u64`
4026    ///   bits are the result of `Arc::into_raw::<T>` where `T` matches
4027    ///   `field_kinds[i]` (per the per-HeapKind table in `drop_fields`).
4028    /// - `NativeKind::Ptr(HeapKind::{Future, ModuleFn, Char, NativeScalar})`
4029    ///   are inline-scalar payloads (no `Arc<T>`); a heap_mask bit set
4030    ///   with one of those kinds is a soundness violation surfaced by
4031    ///   debug_assert in `drop_fields`.
4032    fn drop(&mut self) {
4033        // SAFETY: `drop_fields` walks the heap_mask + field_kinds arrays
4034        // and retires Arc shares per the construction-side contract. Runs
4035        // exactly once per instance (Rust's Drop machinery enforces this
4036        // for `Arc<TypedObjectStorage>` instances; raw-pointer instances
4037        // route through `_drop` which calls `drop_fields` directly and
4038        // never reaches here).
4039        unsafe { self.drop_fields(); }
4040    }
4041}
4042
4043// Wave 2 Agent D1 (2026-05-14): v2-raw HeapElement impl per ADR-006 §2.3
4044// amendment + audit §4.3 Obstacle O-3.a resolution. Constrains
4045// `TypedObjectStorage` to the HeapHeader-at-offset-0 v2-raw element-carrier
4046// contract so future call sites can store raw `*const TypedObjectStorage`
4047// bits in `TypedArray<*const TypedObjectStorage>` (audit §2.2 / §3.3 / S3
4048// territory) and dispatch per-element retain/release via the trait.
4049//
4050// The trait dispatches refcount through the on-header refcount via
4051// `v2_release` — distinct from the legacy `Arc<TypedObjectStorage>` path
4052// which dispatches via Rust `Arc::decrement_strong_count`. Per the struct
4053// docstring, both carrier shapes coexist at the struct level during the
4054// Wave 2 dispatch transition; the slot ABI discriminates them by
4055// allocation provenance (Agent D2's call sites use `_new` and the raw-
4056// pointer slot constructor; existing call sites use `Arc::new` and the
4057// legacy slot constructor).
4058unsafe impl crate::v2::heap_element::HeapElement for TypedObjectStorage {
4059    unsafe fn release_elem(ptr: *const Self) {
4060        if unsafe { crate::v2::refcount::v2_release(&(*ptr).header) } {
4061            unsafe { Self::_drop(ptr as *mut Self) };
4062        }
4063    }
4064}
4065
4066// ── TypedArray buckets (DELETED — V3-S5 ckpt-1, 2026-05-15) ──────────────────
4067//
4068// The `TypedArrayData` enum + impl blocks + `Display for TypedArrayData` +
4069// `typed_array_structural_eq` were DELETED here per ADR-006 §2.7.24 Q25.A
4070// SUPERSEDED + W12-typed-array-data-deletion-audit §3.5. The 22-variant
4071// enum migrates to v2-raw `TypedArray<T>` flat-struct per-T monomorphization.
4072// Consumer cascade lands across ckpt-2 (array_transform/aggregation/sets),
4073// ckpt-3 (array_operations/concat/object_creation), ckpt-4 (TypedBuffer +
4074// HeapValue::TypedArray arm + HeapKind::TypedArray ordinal), ckpt-5 (wire/
4075// json/marshal + 4-table lockstep delete), ckpt-6 (JIT FFI). The
4076// `Arc<TypedArrayData>` payload at heap_variants.rs:476 stays until ckpt-4.
4077//
4078// Refusal #1 binding: do not resurrect TypedArrayData under any rename
4079// (e.g. TypedArrayKind, TypedArrayCarrier, TypedBuffer<T> wrapper enum).
4080
4081// ── Temporal data ───────────────────────────────────────────────────────────
4082
4083/// Temporal data — consolidates Time, Duration, TimeSpan, Timeframe,
4084/// TimeReference, DateTimeExpr, and DataDateTimeRef.
4085#[derive(Debug, Clone)]
4086pub enum TemporalData {
4087    DateTime(chrono::DateTime<chrono::FixedOffset>),
4088    Duration(shape_ast::ast::Duration),
4089    TimeSpan(chrono::Duration),
4090    Timeframe(shape_ast::data::Timeframe),
4091    TimeReference(Box<shape_ast::ast::TimeReference>),
4092    DateTimeExpr(Box<shape_ast::ast::DateTimeExpr>),
4093    DataDateTimeRef(Box<shape_ast::ast::DataDateTimeRef>),
4094}
4095
4096impl TemporalData {
4097    #[inline]
4098    pub fn type_name(&self) -> &'static str {
4099        match self {
4100            TemporalData::DateTime(_) => "time",
4101            TemporalData::Duration(_) => "duration",
4102            TemporalData::TimeSpan(_) => "timespan",
4103            TemporalData::Timeframe(_) => "timeframe",
4104            TemporalData::TimeReference(_) => "time_reference",
4105            TemporalData::DateTimeExpr(_) => "datetime_expr",
4106            TemporalData::DataDateTimeRef(_) => "data_datetime_ref",
4107        }
4108    }
4109
4110    #[inline]
4111    pub fn is_truthy(&self) -> bool {
4112        true
4113    }
4114}
4115
4116impl fmt::Display for TemporalData {
4117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4118        match self {
4119            TemporalData::DateTime(t) => write!(f, "{}", t),
4120            TemporalData::Duration(d) => write!(f, "{:?}", d),
4121            TemporalData::TimeSpan(ts) => write!(f, "{}", ts),
4122            TemporalData::Timeframe(tf) => write!(f, "{:?}", tf),
4123            TemporalData::TimeReference(_) => write!(f, "<time_ref>"),
4124            TemporalData::DateTimeExpr(_) => write!(f, "<datetime_expr>"),
4125            TemporalData::DataDateTimeRef(_) => write!(f, "<data_datetime_ref>"),
4126        }
4127    }
4128}
4129
4130// ── Table view data ─────────────────────────────────────────────────────────
4131
4132/// Table view data — consolidates TypedTable, RowView, ColumnRef, and IndexedTable.
4133#[derive(Debug, Clone)]
4134pub enum TableViewData {
4135    TypedTable {
4136        schema_id: u64,
4137        table: Arc<crate::datatable::DataTable>,
4138    },
4139    RowView {
4140        schema_id: u64,
4141        table: Arc<crate::datatable::DataTable>,
4142        row_idx: usize,
4143    },
4144    ColumnRef {
4145        schema_id: u64,
4146        table: Arc<crate::datatable::DataTable>,
4147        col_id: u32,
4148    },
4149    IndexedTable {
4150        schema_id: u64,
4151        table: Arc<crate::datatable::DataTable>,
4152        index_col: u32,
4153    },
4154}
4155
4156impl TableViewData {
4157    #[inline]
4158    pub fn type_name(&self) -> &'static str {
4159        match self {
4160            TableViewData::TypedTable { .. } => "typed_table",
4161            TableViewData::RowView { .. } => "row",
4162            TableViewData::ColumnRef { .. } => "column",
4163            TableViewData::IndexedTable { .. } => "indexed_table",
4164        }
4165    }
4166
4167    #[inline]
4168    pub fn is_truthy(&self) -> bool {
4169        match self {
4170            TableViewData::TypedTable { table, .. } => table.row_count() > 0,
4171            TableViewData::RowView { .. } => true,
4172            TableViewData::ColumnRef { .. } => true,
4173            TableViewData::IndexedTable { table, .. } => table.row_count() > 0,
4174        }
4175    }
4176}
4177
4178impl fmt::Display for TableViewData {
4179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4180        match self {
4181            TableViewData::TypedTable { table, .. } => write!(
4182                f,
4183                "<typed_table:{}x{}>",
4184                table.row_count(),
4185                table.column_count()
4186            ),
4187            TableViewData::RowView { row_idx, .. } => write!(f, "<row:{}>", row_idx),
4188            TableViewData::ColumnRef { col_id, .. } => write!(f, "<column:{}>", col_id),
4189            TableViewData::IndexedTable { table, .. } => write!(
4190                f,
4191                "<indexed_table:{}x{}>",
4192                table.row_count(),
4193                table.column_count()
4194            ),
4195        }
4196    }
4197}
4198
4199// ── Generate HeapValue, HeapKind, kind(), is_truthy(), type_name() ──────────
4200//
4201// All generated from the single source of truth in define_heap_types!().
4202crate::define_heap_types!();
4203
4204// ── Manual Clone for HeapValue ──────────────────────────────────────────────
4205//
4206// ADR-006 §2.3 + Step 6: every heap-resident variant carries `Arc<T>` so its
4207// clone is one atomic refcount bump — no allocation, no payload copy. Inline
4208// scalars (`Future`, `Char`, `NativeScalar`) clone by `Copy`. `ClosureRaw`
4209// delegates to `OwnedClosureBlock::clone`, which already does a single
4210// `retain_typed_closure` refcount bump on the v2 closure block plus an Arc
4211// bump on the layout pointer.
4212//
4213// This impl is purely mechanical Arc::clone delegation — there is no
4214// `vw_clone` / `vw_drop` bookkeeping (the strict-typed bulldozer deleted
4215// every `ValueWord`-bearing variant).
4216impl Clone for HeapValue {
4217    fn clone(&self) -> Self {
4218        match self {
4219            // ADR-006 §2.3: Arc bump only — no allocation, no payload copy.
4220            HeapValue::String(v) => HeapValue::String(Arc::clone(v)),
4221            HeapValue::Decimal(v) => HeapValue::Decimal(Arc::clone(v)),
4222            HeapValue::BigInt(v) => HeapValue::BigInt(Arc::clone(v)),
4223            HeapValue::Future(v) => HeapValue::Future(*v),
4224            HeapValue::Char(v) => HeapValue::Char(*v),
4225            HeapValue::DataTable(v) => HeapValue::DataTable(Arc::clone(v)),
4226            HeapValue::Content(v) => HeapValue::Content(Arc::clone(v)),
4227            HeapValue::Instant(v) => HeapValue::Instant(Arc::clone(v)),
4228            HeapValue::IoHandle(v) => HeapValue::IoHandle(Arc::clone(v)),
4229            HeapValue::NativeScalar(v) => HeapValue::NativeScalar(*v),
4230            HeapValue::NativeView(v) => HeapValue::NativeView(Arc::clone(v)),
4231            // Wave 2 Round 4 D4 ckpt-final-prime² (2026-05-14): TypedObjectPtr's
4232            // Clone impl bumps the v2-raw HeapHeader-at-offset-0 refcount via
4233            // `v2_retain`, mirroring the typed-Arc clone shape of every other
4234            // heap arm.
4235            HeapValue::TypedObject(s) => HeapValue::TypedObject(s.clone()),
4236            // OwnedClosureBlock::clone is one refcount bump on the typed
4237            // closure block + one Arc bump on the shared layout.
4238            HeapValue::ClosureRaw(v) => HeapValue::ClosureRaw(v.clone()),
4239            HeapValue::TaskGroup(v) => HeapValue::TaskGroup(Arc::clone(v)),
4240            // V3-S5 ckpt-4 (2026-05-15): `HeapValue::TypedArray(v) =>
4241            // HeapValue::TypedArray(Arc::clone(v))` clone arm DELETED in
4242            // lockstep with the variant + the `TypedArrayData` enum
4243            // (ckpt-1) + `TypedBuffer<T>` / `AlignedTypedBuffer` wrapper
4244            // layer (ckpt-4). W12 audit §3.5/§B + ADR-006 §2.7.24 Q25.A
4245            // SUPERSEDED. Refusal #1 binding.
4246            HeapValue::Temporal(v) => HeapValue::Temporal(Arc::clone(v)),
4247            HeapValue::TableView(v) => HeapValue::TableView(Arc::clone(v)),
4248            // Wave 2 Round 3b C2-joint ckpt-2 (2026-05-14): payload is now
4249            // `HashMapKindedRef` (not `Arc<HashMapData>`); the enum's manual
4250            // Clone impl dispatches per-variant `Arc::clone` on the inner
4251            // `Arc<HashMapData<V>>` — preserving structural sharing.
4252            HeapValue::HashMap(v) => HeapValue::HashMap(v.clone()),
4253            // Wave 13 W13-hashset-rebuild (ADR-006 §2.7.15 / Q16,
4254            // 2026-05-10): mirror of HashMap — single strong-count bump
4255            // on the shared `Arc<HashSetData>`, no payload copy.
4256            HeapValue::HashSet(v) => HeapValue::HashSet(Arc::clone(v)),
4257            // Wave 15 W15-deque (ADR-006 §2.7.19 / Q20, 2026-05-10):
4258            // mirror of HashSet — single strong-count bump on the
4259            // shared `Arc<DequeData>`, no payload copy. Per-element
4260            // `Arc<HeapValue>` shares stay shared with the source.
4261            HeapValue::Deque(v) => HeapValue::Deque(Arc::clone(v)),
4262            // Wave-γ G-heap-filter-expr (ADR-006 §2.3 / Q8 amendment):
4263            // FilterExpr Arcs share the typed-Arc clone shape — single
4264            // strong-count bump, no payload copy.
4265            HeapValue::FilterExpr(v) => HeapValue::FilterExpr(Arc::clone(v)),
4266            // Wave 8 W8-T26 (ADR-006 §2.7.13 / Q14, 2026-05-10):
4267            // Reference Arcs share the typed-Arc clone shape — single
4268            // strong-count bump on the shared `Arc<RefTarget>`, no
4269            // payload copy.
4270            HeapValue::Reference(v) => HeapValue::Reference(Arc::clone(v)),
4271            // W13-iterator-state (ADR-006 §2.7.16 / Q17, 2026-05-10):
4272            // Iterator Arcs share the typed-Arc clone shape — single
4273            // strong-count bump on the shared `Arc<IteratorState>`. The
4274            // inner state is `Clone`-by-derive (typed-Arc payloads in
4275            // every field), but the outer `Arc` bump is the canonical
4276            // shared-receiver path.
4277            HeapValue::Iterator(v) => HeapValue::Iterator(Arc::clone(v)),
4278            // Wave 15 W15-channel-rebuild (ADR-006 §2.7.20 / Q21,
4279            // 2026-05-10): Channel Arcs share the typed-Arc clone shape
4280            // — single strong-count bump on the shared
4281            // `Arc<ChannelData>`. The inner `ChannelData` carries a
4282            // `Mutex<ChannelInner>` so two `Arc<ChannelData>` shares
4283            // observe each other's mutations; cloning the outer Arc
4284            // hands out a fresh endpoint of the same channel.
4285            HeapValue::Channel(v) => HeapValue::Channel(Arc::clone(v)),
4286            // Wave 15 W15-priority-queue (ADR-006 §2.7.18 / Q19,
4287            // 2026-05-10): mirror of HashSet — single strong-count bump
4288            // on the shared `Arc<PriorityQueueData>`, no payload copy.
4289            HeapValue::PriorityQueue(v) => HeapValue::PriorityQueue(Arc::clone(v)),
4290            // W15-range (ADR-006 §2.7.23 / Q24, 2026-05-10): Range Arcs
4291            // share the typed-Arc clone shape — single strong-count bump
4292            // on the shared `Arc<RangeData>`, no payload copy. RangeData
4293            // is small ({i64, i64, i64, bool}) so copies would be cheap,
4294            // but the shared-Arc shape matches the dispatch pattern of
4295            // every other heap arm.
4296            HeapValue::Range(v) => HeapValue::Range(Arc::clone(v)),
4297            // Wave 14 W14-variant-codegen (ADR-006 §2.7.17 / Q18,
4298            // 2026-05-10): Result/Option Arcs share the typed-Arc
4299            // clone shape — single strong-count bump on the shared
4300            // `Arc<ResultData>` / `Arc<OptionData>`. The inner
4301            // `KindedSlot` payload's share is preserved by the
4302            // shared Arc; ResultData/OptionData Clone (defined in
4303            // this file) does an inner KindedSlot Clone if the Arc
4304            // is unwrapped via Arc::make_mut.
4305            HeapValue::Result(v) => HeapValue::Result(Arc::clone(v)),
4306            HeapValue::Option(v) => HeapValue::Option(Arc::clone(v)),
4307            // W17-trait-object-storage (ADR-006 §2.7.24 / Q25.C,
4308            // 2026-05-11): TraitObject Arcs share the typed-Arc clone
4309            // shape — single strong-count bump on the shared
4310            // `Arc<TraitObjectStorage>`. Inner Arcs (`value: Arc<TypedObjectStorage>`
4311            // + `vtable: Arc<VTable>`) stay shared with the source;
4312            // `Arc::ptr_eq` on the vtable preserves the §Q25.C.2
4313            // `Self`-arg runtime identity contract across the clone.
4314            // Wave 2 Round 4 D4 ckpt-final-prime² (2026-05-14): TraitObjectPtr's
4315            // Clone impl bumps the v2-raw HeapHeader-at-offset-0 refcount via
4316            // `v2_retain`. Inner `value: *const TypedObjectStorage` and
4317            // `vtable: Arc<VTable>` shares are bumped through the Clone impl
4318            // of TraitObjectStorage itself (called by `_drop` / the per-field
4319            // discipline at refcount=0 release).
4320            HeapValue::TraitObject(v) => HeapValue::TraitObject(v.clone()),
4321            // W17-concurrency (ADR-006 §2.7.25, 2026-05-11): Mutex /
4322            // Atomic / Lazy Arcs share the typed-Arc clone shape —
4323            // single strong-count bump on the shared inner Arc, no
4324            // payload copy. Cloning a Mutex/Lazy yields a fresh
4325            // "endpoint" share of the same protected cell; Atomic
4326            // shares observe each other's load/store/fetch
4327            // operations. Same shape as Channel.
4328            HeapValue::Mutex(v) => HeapValue::Mutex(Arc::clone(v)),
4329            HeapValue::Atomic(v) => HeapValue::Atomic(Arc::clone(v)),
4330            HeapValue::Lazy(v) => HeapValue::Lazy(Arc::clone(v)),
4331            // W17-comptime-vm-dispatch (ADR-006 §2.7.26, 2026-05-12):
4332            // ModuleFn is an inline-scalar payload (no Arc).
4333            HeapValue::ModuleFn(v) => HeapValue::ModuleFn(*v),
4334            // ADR-006 §2.7.22 amendment (Round 18 S3, 2026-05-13): Matrix
4335            // and MatrixSlice arms share the typed-Arc clone shape — single
4336            // strong-count bump on the shared `Arc<MatrixData>` /
4337            // `Arc<MatrixSliceData>`. MatrixSlice's inner `parent: Arc<MatrixData>`
4338            // share stays shared with the source (cloning the slice does not
4339            // copy the parent matrix data). The HeapValue arm exists for the
4340            // ADR-005 §1 / ADR-006 §2.3 `HeapKind`↔`HeapValue` symmetry but
4341            // calling `slot.as_heap_value()` on Matrix/MatrixSlice-labeled
4342            // slot bits is unsound — slot bits are
4343            // `Arc::into_raw(Arc<MatrixData>)` / `Arc::into_raw(Arc<MatrixSliceData>)`,
4344            // NOT `Box<HeapValue>`. Pure-discriminator dispatch shape, mirror
4345            // of §2.7.9 FilterExpr / §2.7.13 Reference.
4346            HeapValue::Matrix(v) => HeapValue::Matrix(Arc::clone(v)),
4347            HeapValue::MatrixSlice(v) => HeapValue::MatrixSlice(Arc::clone(v)),
4348        }
4349    }
4350}
4351
4352// ── Shared comparison helpers ────────────────────────────────────────────────
4353
4354/// Cross-type numeric equality: BigInt vs Decimal.
4355#[inline]
4356fn bigint_decimal_eq(a: &i64, b: &rust_decimal::Decimal) -> bool {
4357    rust_decimal::Decimal::from(*a) == *b
4358}
4359
4360/// Cross-type numeric equality: NativeScalar vs BigInt.
4361#[inline]
4362fn native_scalar_bigint_eq(a: &NativeScalar, b: &i64) -> bool {
4363    a.as_i64().is_some_and(|v| v == *b)
4364}
4365
4366/// Cross-type numeric equality: NativeScalar vs Decimal.
4367#[inline]
4368fn native_scalar_decimal_eq(a: &NativeScalar, b: &rust_decimal::Decimal) -> bool {
4369    match a {
4370        NativeScalar::F32(v) => {
4371            rust_decimal::Decimal::from_f64_retain(*v as f64).is_some_and(|v| v == *b)
4372        }
4373        _ => a
4374            .as_i128()
4375            .map(|n| rust_decimal::Decimal::from_i128_with_scale(n, 0))
4376            .is_some_and(|to_dec| to_dec == *b),
4377    }
4378}
4379
4380/// Cross-type typed array equality: IntArray vs FloatArray (element-wise i64-as-f64).
4381///
4382/// NOTE (V3-S5 ckpt-5-prime²a, 2026-05-15): currently unreachable post-ckpt-4
4383/// HeapValue::TypedArray outer-arm deletion (no callers). Signature migrated
4384/// from `&TypedBuffer<i64>` / `&AlignedTypedBuffer` to `&[i64]` / `&[f64]` per
4385/// Migration shape (a). Retained for the eventual v2-raw `*mut TypedArray<T>`
4386/// per-T monomorphic rebuild (cluster-2 v2-raw-heap-audit territory).
4387#[inline]
4388fn int_float_array_eq(
4389    ints: &[i64],
4390    floats: &[f64],
4391) -> bool {
4392    ints.len() == floats.len()
4393        && ints
4394            .iter()
4395            .zip(floats.iter())
4396            .all(|(x, y)| (*x as f64) == *y)
4397}
4398
4399/// Matrix structural equality (row/col dimensions + element-wise).
4400#[inline]
4401fn matrix_eq(a: &MatrixData, b: &MatrixData) -> bool {
4402    a.rows == b.rows
4403        && a.cols == b.cols
4404        && a.data.len() == b.data.len()
4405        && a.data.iter().zip(b.data.iter()).all(|(x, y)| x == y)
4406}
4407
4408/// NativeView identity comparison.
4409#[inline]
4410fn native_view_eq(a: &NativeViewData, b: &NativeViewData) -> bool {
4411    a.ptr == b.ptr && a.mutable == b.mutable && a.layout.name == b.layout.name
4412}
4413
4414// `typed_array_structural_eq` DELETED — V3-S5 ckpt-1, 2026-05-15.
4415// Per-arm TypedArrayData structural equality dispatch is retired with the
4416// enum. Consumer sites at structural_eq + equals (HeapValue::TypedArray
4417// arm match) cascade-break here and surface for ckpt-4 (HeapValue::TypedArray
4418// arm rebuild atop v2-raw `*mut TypedArray<T>` per-T monomorphic dispatch).
4419
4420// ── Display ─────────────────────────────────────────────────────────────────
4421
4422impl fmt::Display for HeapValue {
4423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4424        match self {
4425            HeapValue::Char(c) => write!(f, "{}", c),
4426            HeapValue::String(s) => write!(f, "{}", s),
4427            HeapValue::TypedObject(_) => write!(f, "{{...}}"),
4428            HeapValue::ClosureRaw(owned) => {
4429                // SAFETY: OwnedClosureBlock's invariant guarantees the
4430                // pointer is live for the duration of `&self`.
4431                let fid = unsafe {
4432                    crate::v2::closure_raw::typed_closure_function_id(owned.as_ptr())
4433                };
4434                write!(f, "<closure:{}>", fid)
4435            }
4436            HeapValue::Decimal(d) => write!(f, "{}", d),
4437            HeapValue::BigInt(i) => write!(f, "{}", i),
4438            HeapValue::DataTable(dt) => {
4439                write!(f, "<datatable:{}x{}>", dt.row_count(), dt.column_count())
4440            }
4441            HeapValue::TableView(tv) => write!(f, "{}", tv),
4442            HeapValue::Content(node) => write!(f, "{}", node),
4443            HeapValue::Instant(t) => write!(f, "<instant:{:?}>", t.elapsed()),
4444            HeapValue::IoHandle(data) => {
4445                let status = if data.is_open() { "open" } else { "closed" };
4446                write!(f, "<io_handle:{}:{}>", data.path, status)
4447            }
4448            HeapValue::Future(id) => write!(f, "<future:{}>", id),
4449            HeapValue::TaskGroup(tg) => {
4450                write!(f, "<task_group:{}>", tg.task_ids.len())
4451            }
4452            HeapValue::Temporal(td) => write!(f, "{}", td),
4453            HeapValue::NativeScalar(v) => write!(f, "{v}"),
4454            HeapValue::NativeView(v) => write!(
4455                f,
4456                "<{}:{}@0x{:x}>",
4457                if v.mutable { "cmut" } else { "cview" },
4458                v.layout.name,
4459                v.ptr
4460            ),
4461            // V3-S5 ckpt-4 (2026-05-15): `HeapValue::TypedArray(ta) =>
4462            // write!(f, "{}", ta)` Display arm DELETED in lockstep with the
4463            // variant + the `TypedArrayData` Display impl (ckpt-1). W12
4464            // audit §3.5/§B + ADR-006 §2.7.24 Q25.A SUPERSEDED.
4465            HeapValue::HashMap(kref) => {
4466                // Wave 2 Round 3b C2-joint ckpt-3 (2026-05-14): full per-V
4467                // entry dump. Walks `*mut TypedArray<*const StringObj>`
4468                // keys + per-V `*mut TypedArray<V>` values; formats as
4469                // `{"k1": v1, "k2": v2}` using each V's natural Display.
4470                // For TypedObject / TraitObject the inner value renders
4471                // as a summary tag — full recursive rendering lives at
4472                // printing.rs (which has the depth-budgeted recursive
4473                // Display via format_heap_value). ADR-006 §2.7.24 Q25.B
4474                // SUPERSEDED + audit §C.4.
4475                hashmap_kref_display(kref, f)
4476            }
4477            // Wave 13 W13-hashset-rebuild (ADR-006 §2.7.15 / Q16,
4478            // 2026-05-10): one-keyspace mirror of HashMap's Display
4479            // shape — `{"a", "b", ...}` braces with comma-separated
4480            // quoted strings, no values.
4481            HeapValue::HashSet(d) => {
4482                write!(f, "{{")?;
4483                for (i, k) in d.keys.iter().enumerate() {
4484                    if i > 0 {
4485                        write!(f, ", ")?;
4486                    }
4487                    write!(f, "\"{}\"", k)?;
4488                }
4489                write!(f, "}}")
4490            }
4491            // Wave 15 W15-deque (ADR-006 §2.7.19 / Q20, 2026-05-10):
4492            // render front-to-back as `Deque[elem1, elem2, ...]` —
4493            // dispatch each element through the canonical ADR-005 §1
4494            // single-discriminator `HeapValue` Display.
4495            HeapValue::Deque(d) => {
4496                write!(f, "Deque[")?;
4497                for (i, v) in d.items.iter().enumerate() {
4498                    if i > 0 {
4499                        write!(f, ", ")?;
4500                    }
4501                    write!(f, "{}", v)?;
4502                }
4503                write!(f, "]")
4504            }
4505            // Wave-γ G-heap-filter-expr (ADR-006 §2.3 amendment): no
4506            // user-facing FilterExpr literal exists; render as an opaque
4507            // tag for diagnostics. Construction-side bug if a FilterExpr
4508            // ever escapes into a user-visible Display path.
4509            HeapValue::FilterExpr(_) => write!(f, "<filter_expr>"),
4510            // Wave 8 W8-T26 (ADR-006 §2.7.13 / Q14, 2026-05-10): no
4511            // user-facing reference literal exists; render as an opaque
4512            // tag for diagnostics. References are within-program data
4513            // and don't cross any user-visible Display surface.
4514            HeapValue::Reference(_) => write!(f, "<ref>"),
4515            // W13-iterator-state (ADR-006 §2.7.16 / Q17, 2026-05-10):
4516            // iterator pipelines have no user-facing literal — render
4517            // as an opaque tag. Terminal operations (collect / forEach
4518            // / reduce / etc.) materialise the elements; an iterator
4519            // reaching the Display surface is "still lazy" by
4520            // construction.
4521            HeapValue::Iterator(_) => write!(f, "<iterator>"),
4522            // Wave 15 W15-channel-rebuild (ADR-006 §2.7.20 / Q21,
4523            // 2026-05-10): channels are concurrency primitives with no
4524            // user-facing literal; render as an opaque tag annotated
4525            // with current queue length and closed flag for
4526            // diagnostics.
4527            HeapValue::Channel(c) => {
4528                let len = c.len();
4529                let state = if c.is_closed() { "closed" } else { "open" };
4530                write!(f, "<channel:{}:{}>", state, len)
4531            }
4532            // Wave 15 W15-priority-queue (ADR-006 §2.7.18 / Q19,
4533            // 2026-05-10): one-keyspace mirror of HashSet's Display
4534            // shape — bracketed comma-separated values in heap-array
4535            // order. NOTE: heap-array order is not sort-order; for
4536            // sorted output the user must call `pq.toSortedArray()`.
4537            HeapValue::PriorityQueue(d) => {
4538                write!(f, "PriorityQueue[")?;
4539                for (i, v) in d.heap.iter().enumerate() {
4540                    if i > 0 {
4541                        write!(f, ", ")?;
4542                    }
4543                    write!(f, "{}", v)?;
4544                }
4545                write!(f, "]")
4546            }
4547            // W15-range (ADR-006 §2.7.23 / Q24, 2026-05-10): user-visible
4548            // literal form — `start..end` for exclusive, `start..=end`
4549            // for inclusive. Step is not part of the surface syntax (no
4550            // explicit step suffix) so it's not rendered. This matches
4551            // the pre-strict-typing print format and the `0..10` /
4552            // `0..=10` source syntax round-trip.
4553            HeapValue::Range(r) => {
4554                if r.inclusive {
4555                    write!(f, "{}..={}", r.start, r.end)
4556                } else {
4557                    write!(f, "{}..{}", r.start, r.end)
4558                }
4559            }
4560            // Wave 14 W14-variant-codegen (ADR-006 §2.7.17 / Q18,
4561            // 2026-05-10): render as `Ok(<inner>)` / `Err(<inner>)` /
4562            // `Some(<inner>)` / `None`. The inner Display goes
4563            // through the runtime kinded value-formatter at the
4564            // VM-tier `printing.rs` site (the heap_value `Display`
4565            // impl is a fallback for diagnostic prints; rich
4566            // formatting goes through the executor's
4567            // `format_kinded`). Renders inner as `<…>` opaque tag
4568            // here — the kinded formatter handles full pretty-print.
4569            HeapValue::Result(r) => {
4570                if r.is_ok {
4571                    write!(f, "Ok(<...>)")
4572                } else {
4573                    write!(f, "Err(<...>)")
4574                }
4575            }
4576            HeapValue::Option(o) => {
4577                if o.is_some {
4578                    write!(f, "Some(<...>)")
4579                } else {
4580                    write!(f, "None")
4581                }
4582            }
4583            // W17-trait-object-storage (ADR-006 §2.7.24 / Q25.C,
4584            // 2026-05-11): `dyn Trait` carriers render as an opaque
4585            // tag annotated with the first trait name (multi-trait
4586            // inheritance shows just the first) and the inner schema
4587            // id of the boxed TypedObject. Pretty-printing via the
4588            // boxed receiver's user-defined `Display`-equivalent is
4589            // a compiler-emission tier concern (call the trait's
4590            // `.format(self)` method through the vtable); the
4591            // storage-tier formatter is diagnostic-only.
4592            HeapValue::TraitObject(t) => {
4593                let trait_name = t
4594                    .vtable
4595                    .trait_names
4596                    .first()
4597                    .map(|s| s.as_str())
4598                    .unwrap_or("?");
4599                // Wave 2 Round 4 D4 ckpt-3 (2026-05-14): `t.value` is
4600                // `*const TypedObjectStorage` (v2-raw shape) — field
4601                // access goes through an unsafe deref.
4602                // SAFETY: `t.value` is non-null per universal-dyn
4603                // construction; the `&HeapValue::TraitObject(t)` borrow
4604                // holds the carrier live for this scope.
4605                let inner_schema_id = unsafe { (*t.value).schema_id };
4606                write!(
4607                    f,
4608                    "<dyn {} #{}>",
4609                    trait_name, inner_schema_id
4610                )
4611            }
4612            // W17-concurrency (ADR-006 §2.7.25, 2026-05-11): concurrency
4613            // primitives have no user-facing literal — render as opaque
4614            // tags annotated with diagnostic state. Mirror of Channel's
4615            // `<channel:state:len>` shape.
4616            HeapValue::Mutex(_) => write!(f, "<mutex>"),
4617            HeapValue::Atomic(a) => write!(f, "<atomic:{}>", a.load()),
4618            HeapValue::Lazy(l) => {
4619                if l.is_initialized() {
4620                    write!(f, "<lazy:initialized>")
4621                } else {
4622                    write!(f, "<lazy:pending>")
4623                }
4624            }
4625            // W17-comptime-vm-dispatch (ADR-006 §2.7.26, 2026-05-12):
4626            // ModuleFn references render as `<module_fn:id>`.
4627            HeapValue::ModuleFn(id) => write!(f, "<module_fn:{}>", id),
4628            // ADR-006 §2.7.22 amendment (Round 18 S3, 2026-05-13):
4629            // Matrix renders as `<Mat<number>:rows x cols>`, mirroring the
4630            // pre-amendment `TypedArrayData::Matrix` Display shape.
4631            // MatrixSlice renders as a flat `Vec<number>[...]` over the
4632            // projection's element slice, mirroring the pre-amendment
4633            // `TypedArrayData::FloatSlice` Display shape. These Display
4634            // surfaces are diagnostic fallbacks; pretty-printing of
4635            // Matrix/MatrixSlice values goes through `printing.rs` at
4636            // the VM tier.
4637            HeapValue::Matrix(m) => {
4638                write!(f, "<Mat<number>:{}x{}>", m.rows, m.cols)
4639            }
4640            HeapValue::MatrixSlice(s) => {
4641                let slice = s.as_slice();
4642                write!(f, "Vec<number>[")?;
4643                for (i, v) in slice.iter().enumerate() {
4644                    if i > 0 {
4645                        write!(f, ", ")?;
4646                    }
4647                    if *v == v.trunc() && v.abs() < 1e15 {
4648                        write!(f, "{}", *v as i64)?;
4649                    } else {
4650                        write!(f, "{}", v)?;
4651                    }
4652                }
4653                write!(f, "]")
4654            }
4655        }
4656    }
4657}
4658
4659// ── Hand-written methods (complex per-variant logic) ────────────────────────
4660
4661impl HeapValue {
4662    /// Obtain a [`crate::vm_closure_handle::VmClosureHandle`] over this
4663    /// heap value, if it is a `HeapValue::ClosureRaw`.
4664    ///
4665    /// Closure spec §14.2: the handle is the stable read API for
4666    /// closure state. Returns `None` for non-closure heap values.
4667    #[inline]
4668    pub fn as_closure_handle(&self) -> Option<crate::vm_closure_handle::VmClosureHandle<'_>> {
4669        match self {
4670            HeapValue::ClosureRaw(owned) => {
4671                // SAFETY: `OwnedClosureBlock::from_raw` upholds that
4672                // `as_header_ptr()` points to a live `TypedClosureHeader`
4673                // whose layout matches `owned.layout()`; both remain valid
4674                // for the duration of the `&self` borrow.
4675                let handle = unsafe {
4676                    crate::vm_closure_handle::VmClosureHandle::raw(
4677                        owned.as_header_ptr(),
4678                        owned.layout().as_ref(),
4679                    )
4680                };
4681                Some(handle)
4682            }
4683            _ => None,
4684        }
4685    }
4686
4687    /// Structural equality comparison for HeapValue.
4688    ///
4689    /// ADR-006 §2.3: `TypedArray` and `Temporal` payloads are now
4690    /// `Arc<TypedArrayData>` / `Arc<TemporalData>`; the per-arm dispatch
4691    /// dereferences the Arc once at the outer match and forwards into the
4692    /// inner enum via `typed_array_structural_eq` / direct `match`.
4693    pub fn structural_eq(&self, other: &HeapValue) -> bool {
4694        match (self, other) {
4695            (HeapValue::Char(a), HeapValue::Char(b)) => a == b,
4696            (HeapValue::String(a), HeapValue::String(b)) => a == b,
4697            // Cross-type: Char from string indexing vs String literal
4698            (HeapValue::Char(c), HeapValue::String(s))
4699            | (HeapValue::String(s), HeapValue::Char(c)) => {
4700                let mut buf = [0u8; 4];
4701                let cs = c.encode_utf8(&mut buf);
4702                cs == s.as_str()
4703            }
4704            (HeapValue::Decimal(a), HeapValue::Decimal(b)) => a == b,
4705            (HeapValue::BigInt(a), HeapValue::BigInt(b)) => a == b,
4706            (HeapValue::NativeScalar(a), HeapValue::NativeScalar(b)) => a == b,
4707            (HeapValue::NativeView(a), HeapValue::NativeView(b)) => native_view_eq(a, b),
4708            (HeapValue::Future(a), HeapValue::Future(b)) => a == b,
4709            (HeapValue::Temporal(a), HeapValue::Temporal(b)) => match (a.as_ref(), b.as_ref()) {
4710                (TemporalData::DateTime(x), TemporalData::DateTime(y)) => x == y,
4711                _ => false,
4712            },
4713            (HeapValue::Content(a), HeapValue::Content(b)) => a == b,
4714            (HeapValue::Instant(a), HeapValue::Instant(b)) => a == b,
4715            (HeapValue::IoHandle(a), HeapValue::IoHandle(b)) => {
4716                std::sync::Arc::ptr_eq(&a.resource, &b.resource)
4717            }
4718            // V3-S5 ckpt-4 (2026-05-15): `(HeapValue::TypedArray(...), ...)`
4719            // structural-eq arm DELETED. The `HeapValue::TypedArray` outer
4720            // arm was retired in lockstep with this ckpt-4 +
4721            // `typed_array_structural_eq` was deleted at V3-S5 ckpt-1
4722            // (heap_value.rs wholesale deletion per W12 audit §3.5 + ADR-006
4723            // §2.7.24 Q25.A SUPERSEDED). Per-arm TypedArrayData structural
4724            // equality dispatch retires with the enum; no replacement (the
4725            // v2-raw `TypedArray<T>` flat struct compares element-wise via
4726            // its own `==` impl, not through `HeapValue::structural_eq`).
4727            // Refusal #1 binding.
4728            // ADR-006 §2.7.22 amendment (Round 18 S3, 2026-05-13): Matrix
4729            // equality is structural (rows + cols + element-wise compare);
4730            // MatrixSlice equality is element-wise over the projection slice
4731            // (parent identity is NOT required — two slices with identical
4732            // elements compare equal even when projecting from different
4733            // parents). Mirror of the pre-amendment TypedArrayData::Matrix /
4734            // FloatSlice equality semantics.
4735            (HeapValue::Matrix(a), HeapValue::Matrix(b)) => matrix_eq(a, b),
4736            (HeapValue::MatrixSlice(a), HeapValue::MatrixSlice(b)) => {
4737                a.as_slice() == b.as_slice()
4738            }
4739            _ => false,
4740        }
4741    }
4742
4743    /// Check equality between two heap values.
4744    #[inline]
4745    pub fn equals(&self, other: &HeapValue) -> bool {
4746        match (self, other) {
4747            (HeapValue::Char(a), HeapValue::Char(b)) => a == b,
4748            (HeapValue::String(a), HeapValue::String(b)) => a == b,
4749            // Cross-type: Char from string indexing vs String literal
4750            (HeapValue::Char(c), HeapValue::String(s))
4751            | (HeapValue::String(s), HeapValue::Char(c)) => {
4752                let mut buf = [0u8; 4];
4753                let cs = c.encode_utf8(&mut buf);
4754                cs == s.as_str()
4755            }
4756            (HeapValue::TypedObject(a), HeapValue::TypedObject(b)) => {
4757                // Wave 2 Round 4 D4 ckpt-final-prime² (2026-05-14): payloads are
4758                // `TypedObjectPtr` (raw `*const TypedObjectStorage`); pointer-
4759                // equality is the fast path for shared storage.
4760                if a.as_ptr() == b.as_ptr() {
4761                    return true;
4762                }
4763                if a.schema_id != b.schema_id
4764                    || a.slots.len() != b.slots.len()
4765                    || a.heap_mask != b.heap_mask
4766                {
4767                    return false;
4768                }
4769                for i in 0..a.slots.len() {
4770                    // Both heap-mask and primitive-mask: compare raw bits
4771                    // for primitives. For heap slots, raw-bit equality is
4772                    // also conservatively correct since `ValueSlot` heap
4773                    // payloads are typed pointers — pointer-equality
4774                    // implies value-equality for shared Arc'd payloads.
4775                    if a.slots[i].raw() != b.slots[i].raw() {
4776                        return false;
4777                    }
4778                }
4779                true
4780            }
4781            // Track A.5: the canonical closure variant compares by function id.
4782            (HeapValue::ClosureRaw(a), HeapValue::ClosureRaw(b)) => {
4783                // SAFETY: both blocks are live per OwnedClosureBlock invariant.
4784                let fa = unsafe { crate::v2::closure_raw::typed_closure_function_id(a.as_ptr()) };
4785                let fb = unsafe { crate::v2::closure_raw::typed_closure_function_id(b.as_ptr()) };
4786                fa == fb
4787            }
4788            (HeapValue::Decimal(a), HeapValue::Decimal(b)) => a == b,
4789            (HeapValue::BigInt(a), HeapValue::BigInt(b)) => a == b,
4790            (HeapValue::BigInt(a), HeapValue::Decimal(b)) => bigint_decimal_eq(a.as_ref(), b.as_ref()),
4791            (HeapValue::Decimal(a), HeapValue::BigInt(b)) => bigint_decimal_eq(b.as_ref(), a.as_ref()),
4792            (HeapValue::DataTable(a), HeapValue::DataTable(b)) => Arc::ptr_eq(a, b),
4793            (HeapValue::TableView(a), HeapValue::TableView(b)) => match (a.as_ref(), b.as_ref()) {
4794                (
4795                    TableViewData::TypedTable { schema_id: s1, table: t1 },
4796                    TableViewData::TypedTable { schema_id: s2, table: t2 },
4797                ) => s1 == s2 && Arc::ptr_eq(t1, t2),
4798                (
4799                    TableViewData::RowView { schema_id: s1, row_idx: r1, table: t1 },
4800                    TableViewData::RowView { schema_id: s2, row_idx: r2, table: t2 },
4801                ) => s1 == s2 && r1 == r2 && Arc::ptr_eq(t1, t2),
4802                (
4803                    TableViewData::ColumnRef { schema_id: s1, col_id: c1, table: t1 },
4804                    TableViewData::ColumnRef { schema_id: s2, col_id: c2, table: t2 },
4805                ) => s1 == s2 && c1 == c2 && Arc::ptr_eq(t1, t2),
4806                (
4807                    TableViewData::IndexedTable { schema_id: s1, index_col: c1, table: t1 },
4808                    TableViewData::IndexedTable { schema_id: s2, index_col: c2, table: t2 },
4809                ) => s1 == s2 && c1 == c2 && Arc::ptr_eq(t1, t2),
4810                _ => false,
4811            },
4812            (HeapValue::Content(a), HeapValue::Content(b)) => a == b,
4813            (HeapValue::Instant(a), HeapValue::Instant(b)) => a == b,
4814            (HeapValue::IoHandle(a), HeapValue::IoHandle(b)) => {
4815                Arc::ptr_eq(&a.resource, &b.resource)
4816            }
4817            (HeapValue::Future(a), HeapValue::Future(b)) => a == b,
4818            (HeapValue::Temporal(a), HeapValue::Temporal(b)) => match (a.as_ref(), b.as_ref()) {
4819                (TemporalData::DateTime(x), TemporalData::DateTime(y)) => x == y,
4820                (TemporalData::Duration(x), TemporalData::Duration(y)) => x == y,
4821                (TemporalData::TimeSpan(x), TemporalData::TimeSpan(y)) => x == y,
4822                (TemporalData::Timeframe(x), TemporalData::Timeframe(y)) => x == y,
4823                _ => false,
4824            },
4825            (HeapValue::NativeScalar(a), HeapValue::NativeScalar(b)) => a == b,
4826            (HeapValue::NativeView(a), HeapValue::NativeView(b)) => native_view_eq(a, b),
4827            // V3-S5 ckpt-4 (2026-05-15): `(HeapValue::TypedArray(...), ...)`
4828            // equals arm DELETED in lockstep with the structural_eq arm
4829            // above + the outer `HeapValue::TypedArray` variant +
4830            // `typed_array_structural_eq` (ckpt-1). W12 audit §3.5 +
4831            // ADR-006 §2.7.24 Q25.A SUPERSEDED. Refusal #1 binding.
4832            // ADR-006 §2.7.22 amendment (Round 18 S3, 2026-05-13): Matrix
4833            // and MatrixSlice equality match the structural_eq shape above.
4834            (HeapValue::Matrix(a), HeapValue::Matrix(b)) => matrix_eq(a, b),
4835            (HeapValue::MatrixSlice(a), HeapValue::MatrixSlice(b)) => {
4836                a.as_slice() == b.as_slice()
4837            }
4838            // Cross-type numeric
4839            (HeapValue::NativeScalar(a), HeapValue::BigInt(b)) => native_scalar_bigint_eq(a, b.as_ref()),
4840            (HeapValue::BigInt(a), HeapValue::NativeScalar(b)) => native_scalar_bigint_eq(b, a.as_ref()),
4841            (HeapValue::NativeScalar(a), HeapValue::Decimal(b)) => {
4842                native_scalar_decimal_eq(a, b.as_ref())
4843            }
4844            (HeapValue::Decimal(a), HeapValue::NativeScalar(b)) => {
4845                native_scalar_decimal_eq(b, a.as_ref())
4846            }
4847            _ => false,
4848        }
4849    }
4850}
4851
4852#[cfg(test)]
4853mod closure_variant_regression {
4854    //! N2 — pin Track A.5's deletion of the legacy `HeapValue::Closure`
4855    //! variant. After the Phase 2b HeapKind trim, the `Closure` ordinal
4856    //! is no longer pre-bulldozer-stable (it moved from 3 to 2 along
4857    //! with the rest of the trim), but the discriminator must still map
4858    //! to the `ClosureRaw` pipeline.
4859    use super::*;
4860
4861    #[test]
4862    fn heap_kind_closure_routes_to_closure_raw() {
4863        // The Closure HeapKind discriminator is what HeapValue::ClosureRaw
4864        // returns from `kind()`; verify the routing is intact.
4865        // (The numeric ordinal is structural — see heap_variants.rs — and
4866        // not load-bearing for any external consumer per the Phase 2b
4867        // audit.)
4868        let _ = HeapKind::Closure;
4869    }
4870}
4871
4872#[cfg(test)]
4873mod typed_object_storage_drop {
4874    //! ADR-006 §2.5 / Step 5: pin the Drop impl's behaviour on
4875    //! `TypedObjectStorage`. The contract tested:
4876    //!
4877    //! 1. Heap-mask bits cause `Arc::decrement_strong_count::<T>` for the
4878    //!    matching `field_kinds[i]` payload type.
4879    //! 2. Non-heap slots (heap_mask bit clear) are no-ops — even with
4880    //!    non-zero raw bits (those bits are scalar field contents, not
4881    //!    typed pointers).
4882    //! 3. `field_kinds` itself is shared via Arc — multiple instances of
4883    //!    the same schema share one `[NativeKind]` allocation.
4884    use super::*;
4885    use crate::native_kind::NativeKind;
4886    use crate::slot::ValueSlot;
4887    use std::sync::Arc;
4888
4889    #[test]
4890    fn drop_decrements_arc_string_for_heap_string_slot() {
4891        let s: Arc<String> = Arc::new("phase-1a".to_string());
4892        // Hold a second strong ref so the test can observe the count drop.
4893        let witness = Arc::clone(&s);
4894        assert_eq!(Arc::strong_count(&witness), 2);
4895
4896        let slot = ValueSlot::from_string_arc(s);
4897        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::String]);
4898        let storage = TypedObjectStorage::new(
4899            42,
4900            vec![slot].into_boxed_slice(),
4901            0b1, // bit 0 set
4902            kinds,
4903        );
4904
4905        // Construction stored the Arc raw pointer; nothing dropped yet.
4906        assert_eq!(Arc::strong_count(&witness), 2);
4907
4908        drop(storage);
4909
4910        // Drop walked heap_mask, dispatched on NativeKind::String, and
4911        // released the slot's strong count via Arc::decrement_strong_count.
4912        assert_eq!(Arc::strong_count(&witness), 1);
4913    }
4914
4915    #[test]
4916    fn drop_is_noop_for_non_heap_slot_with_non_zero_bits() {
4917        // Non-heap slot — heap_mask bit clear. Raw bits are an i64 value;
4918        // Drop must not interpret them as a pointer.
4919        let slot = ValueSlot::from_int(0x1234_5678);
4920        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
4921        let storage = TypedObjectStorage::new(
4922            7,
4923            vec![slot].into_boxed_slice(),
4924            0, // no heap bits
4925            kinds,
4926        );
4927        // Just dropping the storage must not crash / dereference the bits.
4928        drop(storage);
4929    }
4930
4931    #[test]
4932    fn drop_skips_zero_pointer_slots() {
4933        // Heap-mask bit set but the slot was zeroed (e.g. moved-out) —
4934        // Drop must not call Arc::decrement_strong_count on null.
4935        let slot = ValueSlot::from_raw(0);
4936        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::String]);
4937        let storage = TypedObjectStorage::new(
4938            9,
4939            vec![slot].into_boxed_slice(),
4940            0b1,
4941            kinds,
4942        );
4943        drop(storage);
4944    }
4945
4946    #[test]
4947    fn field_kinds_arc_is_shared_across_instances() {
4948        // Option B' invariant: two instances of the same schema clone the
4949        // same Arc<[NativeKind]> (one payload allocation per schema).
4950        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64, NativeKind::Bool]);
4951        let kinds_count_before = Arc::strong_count(&kinds);
4952
4953        let storage_a = TypedObjectStorage::new(
4954            1,
4955            vec![ValueSlot::from_int(0), ValueSlot::from_bool(true)].into_boxed_slice(),
4956            0,
4957            Arc::clone(&kinds),
4958        );
4959        let storage_b = TypedObjectStorage::new(
4960            1,
4961            vec![ValueSlot::from_int(1), ValueSlot::from_bool(false)].into_boxed_slice(),
4962            0,
4963            Arc::clone(&kinds),
4964        );
4965
4966        // Two instances + the test's own handle = +2 over the baseline.
4967        assert_eq!(Arc::strong_count(&kinds), kinds_count_before + 2);
4968
4969        // Both instances point at the same payload (no per-instance copy).
4970        assert!(Arc::ptr_eq(&storage_a.field_kinds, &storage_b.field_kinds));
4971        assert!(Arc::ptr_eq(&storage_a.field_kinds, &kinds));
4972
4973        drop(storage_a);
4974        drop(storage_b);
4975        // Each Drop released its share; only the test's handle remains.
4976        assert_eq!(Arc::strong_count(&kinds), kinds_count_before);
4977    }
4978
4979    #[test]
4980    fn drop_handles_mixed_heap_and_scalar_fields() {
4981        // Realistic shape: int + string + bool. Only the string slot
4982        // participates in refcount; the int/bool slots are scalar bits.
4983        let s: Arc<String> = Arc::new("mixed".to_string());
4984        let witness = Arc::clone(&s);
4985        assert_eq!(Arc::strong_count(&witness), 2);
4986
4987        let slots = vec![
4988            ValueSlot::from_int(99),
4989            ValueSlot::from_string_arc(s),
4990            ValueSlot::from_bool(true),
4991        ]
4992        .into_boxed_slice();
4993        let kinds: Arc<[NativeKind]> = Arc::from(vec![
4994            NativeKind::Int64,
4995            NativeKind::String,
4996            NativeKind::Bool,
4997        ]);
4998        let storage = TypedObjectStorage::new(
4999            13,
5000            slots,
5001            0b010, // only bit 1 (the string) is heap
5002            kinds,
5003        );
5004
5005        drop(storage);
5006        assert_eq!(Arc::strong_count(&witness), 1);
5007    }
5008
5009    #[test]
5010    fn drop_decrements_arc_typed_object_for_heap_pointer_slot() {
5011        // Nested TypedObject: outer storage holds an Arc<TypedObjectStorage>
5012        // in slot 0 via NativeKind::Ptr(HeapKind::TypedObject).
5013        let inner_kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5014        let inner = Arc::new(TypedObjectStorage::new(
5015            100,
5016            vec![ValueSlot::from_int(7)].into_boxed_slice(),
5017            0,
5018            inner_kinds,
5019        ));
5020        let inner_witness = Arc::clone(&inner);
5021        assert_eq!(Arc::strong_count(&inner_witness), 2);
5022
5023        let outer_kinds: Arc<[NativeKind]> =
5024            Arc::from(vec![NativeKind::Ptr(HeapKind::TypedObject)]);
5025        let outer = TypedObjectStorage::new(
5026            101,
5027            vec![ValueSlot::from_typed_object(inner)].into_boxed_slice(),
5028            0b1,
5029            outer_kinds,
5030        );
5031
5032        drop(outer);
5033        assert_eq!(Arc::strong_count(&inner_witness), 1);
5034    }
5035
5036    // ── Wave 2 Agent D1 v2-raw HeapHeader-equipped shape change tests ──────────
5037
5038    #[test]
5039    fn header_initializes_with_typed_object_kind_and_refcount_one() {
5040        // Wave 2 Agent D1: confirm `TypedObjectStorage::new` initializes the
5041        // HeapHeader at offset 0 with HEAP_KIND_V2_TYPED_OBJECT and refcount=1.
5042        // Used by Arc<TypedObjectStorage>-path callers (the legacy path); the
5043        // header's refcount sits unused for the Arc lifetime.
5044        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5045        let storage = TypedObjectStorage::new(
5046            1,
5047            vec![ValueSlot::from_int(0)].into_boxed_slice(),
5048            0,
5049            kinds,
5050        );
5051        assert_eq!(
5052            storage.header.kind(),
5053            crate::v2::heap_header::HEAP_KIND_V2_TYPED_OBJECT,
5054        );
5055        assert_eq!(storage.header.get_refcount(), 1);
5056    }
5057
5058    #[test]
5059    fn v2_raw_new_and_drop_round_trip() {
5060        // Wave 2 Agent D1: confirm the v2-raw allocator + deallocator pair
5061        // (`_new` + `_drop`) round-trips a simple scalar-only TypedObjectStorage
5062        // without leaking. Mirror of DecimalObj::test_drop_does_not_leak —
5063        // Miri / valgrind validate no leak.
5064        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5065        unsafe {
5066            let ptr = TypedObjectStorage::_new(
5067                7,
5068                vec![ValueSlot::from_int(42)].into_boxed_slice(),
5069                0,
5070                kinds,
5071            );
5072            assert!(!ptr.is_null());
5073            assert_eq!(
5074                (*ptr).header.kind(),
5075                crate::v2::heap_header::HEAP_KIND_V2_TYPED_OBJECT,
5076            );
5077            assert_eq!((*ptr).header.get_refcount(), 1);
5078            assert_eq!((*ptr).schema_id, 7);
5079            assert_eq!((&(*ptr).slots).len(), 1);
5080            assert_eq!((&(*ptr).slots)[0].as_i64(), 42);
5081            TypedObjectStorage::_drop(ptr);
5082            // ptr is dangling; cannot dereference further.
5083        }
5084    }
5085
5086    #[test]
5087    fn v2_raw_new_releases_string_share_at_drop() {
5088        // Wave 2 Agent D1: confirm `_drop` runs the heap-mask field walk
5089        // (mirror of `impl Drop`'s legacy behaviour) and retires one Arc
5090        // strong-count share per heap-kinded slot.
5091        let s: Arc<String> = Arc::new("v2-raw-test".to_string());
5092        let witness = Arc::clone(&s);
5093        assert_eq!(Arc::strong_count(&witness), 2);
5094
5095        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::String]);
5096        unsafe {
5097            let ptr = TypedObjectStorage::_new(
5098                17,
5099                vec![ValueSlot::from_string_arc(s)].into_boxed_slice(),
5100                0b1,
5101                kinds,
5102            );
5103            assert_eq!(Arc::strong_count(&witness), 2);
5104            TypedObjectStorage::_drop(ptr);
5105        }
5106        assert_eq!(Arc::strong_count(&witness), 1);
5107    }
5108
5109    #[test]
5110    fn heap_element_release_elem_deallocates_at_refcount_zero() {
5111        // Wave 2 Agent D1: confirm `HeapElement::release_elem` decrements
5112        // via `v2_release` and deallocates at refcount=0. Mirror of
5113        // DecimalObj::test_heap_element_release_elem_to_zero.
5114        use crate::v2::heap_element::HeapElement;
5115        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5116        unsafe {
5117            let ptr = TypedObjectStorage::_new(
5118                3,
5119                vec![ValueSlot::from_int(0)].into_boxed_slice(),
5120                0,
5121                kinds,
5122            );
5123            // refcount=1; release_elem deallocates.
5124            TypedObjectStorage::release_elem(ptr);
5125            // ptr is dangling; valgrind / Miri confirms no leak.
5126        }
5127    }
5128
5129    #[test]
5130    fn heap_element_release_elem_preserves_held_share() {
5131        // Wave 2 Agent D1: confirm `release_elem` decrements but does NOT
5132        // deallocate when refcount > 1. Mirror of
5133        // DecimalObj::test_heap_element_release_elem_held_share.
5134        use crate::v2::heap_element::HeapElement;
5135        use crate::v2::refcount::{v2_get_refcount, v2_retain};
5136        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5137        unsafe {
5138            let ptr = TypedObjectStorage::_new(
5139                5,
5140                vec![ValueSlot::from_int(7)].into_boxed_slice(),
5141                0,
5142                kinds,
5143            );
5144            let header = &(*ptr).header as *const crate::v2::heap_header::HeapHeader;
5145
5146            v2_retain(header); // refcount = 2
5147            TypedObjectStorage::release_elem(ptr); // refcount = 1 (does not deallocate)
5148            assert_eq!(v2_get_refcount(header), 1);
5149
5150            // Clean up the held share.
5151            TypedObjectStorage::_drop(ptr);
5152        }
5153    }
5154
5155    #[test]
5156    fn header_field_is_at_offset_zero() {
5157        // Wave 2 Agent D1: confirm the #[repr(C)] field-order invariant —
5158        // the `header: HeapHeader` field sits at offset 0 of
5159        // TypedObjectStorage. This is the precondition for the
5160        // HeapElement::release_elem body's `v2_release(&(*ptr).header)` call
5161        // to read the refcount at the v2-raw canonical offset (offset 0
5162        // mirrors StringObj / DecimalObj precedents).
5163        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5164        let storage = TypedObjectStorage::new(
5165            1,
5166            vec![ValueSlot::from_int(0)].into_boxed_slice(),
5167            0,
5168            kinds,
5169        );
5170        let base = &storage as *const _ as usize;
5171        let header_offset = &storage.header as *const _ as usize - base;
5172        assert_eq!(header_offset, 0, "header must be at offset 0 (#[repr(C)] contract)");
5173    }
5174}
5175
5176// Wave 2 Round 3b C2-joint ckpt-3 (2026-05-14): rewritten against the
5177// per-V `HashMapData<V>` mutation API (insert / remove / get_share /
5178// merge). The pre-Q25.B-SUPERSEDED non-generic `HashMapData::insert(k,
5179// Arc<HeapValue>) / remove(k) -> bool / get(k) -> Option<Arc<HeapValue>>`
5180// shape is gone; tests below exercise the per-V semantics on the most
5181// common production-V cases:
5182//
5183// - `V = i64` (Q25.B I64 arm): POD/Copy V — pin len/contains/insert/remove
5184//   semantics without refcount-share complications.
5185// - `V = *const StringObj` (Q25.B String arm): HeapElement V — pin the
5186//   v2_retain / release_elem refcount-share threading via the
5187//   `HashMapValueElem::share_clone` + `release_owned` dispatch.
5188//
5189// The previously-introduced undeclared feature gate (Round 3b ckpt-2)
5190// guarding this test module has been REMOVED per ckpt-3 dispatch
5191// Group F (mandatory non-negotiable): the gate was masquerading as a
5192// feature flag while being functionally `#[cfg(false)]` (no Cargo.toml
5193// declaration), matching CLAUDE.md Forbidden Rationalizations.
5194// ADR-006 §2.7.24 Q25.B SUPERSEDED + audit §C.4 option (a.2).
5195#[cfg(test)]
5196mod hashmap_mutation {
5197    //! Wave 2 Round 3b C2-joint ckpt-3 (2026-05-14): pin the
5198    //! `insert` / `remove` / `get_share` / `merge` API contracts on the
5199    //! post-Q25.B-SUPERSEDED `HashMapData<V>`. Storage-layer counterpart
5200    //! of `v2_set` / `v2_delete` / `v2_get` / `v2_merge` in
5201    //! `shape-vm/executor/objects/hashmap_methods.rs`.
5202    use super::*;
5203    use crate::v2::refcount::v2_get_refcount;
5204    use crate::v2::string_obj::StringObj;
5205    use std::sync::Arc;
5206
5207    // ── V = i64 (POD/Copy) ──────────────────────────────────────────────
5208
5209    #[test]
5210    fn i64_insert_appends_new_entry_and_grows_index() {
5211        let mut m: HashMapData<i64> = HashMapData::new();
5212        unsafe {
5213            assert!(m.insert("a", 1));
5214            assert!(m.insert("b", 2));
5215        }
5216        assert_eq!(m.len(), 2);
5217        // Bucket index has registrations for both keys' hashes.
5218        let h_a = fnv1a_hash(b"a");
5219        let h_b = fnv1a_hash(b"b");
5220        assert!(m.index.get(&h_a).is_some());
5221        assert!(m.index.get(&h_b).is_some());
5222    }
5223
5224    #[test]
5225    fn i64_insert_overwrites_existing_value_and_keeps_len() {
5226        let mut m: HashMapData<i64> = HashMapData::new();
5227        unsafe {
5228            assert!(m.insert("a", 1));
5229            // Overwrite returns false (existing key).
5230            assert!(!m.insert("a", 99));
5231        }
5232        assert_eq!(m.len(), 1);
5233        // get_share returns a fresh Copy of the i64 value.
5234        assert_eq!(m.get_share("a"), Some(99));
5235    }
5236
5237    #[test]
5238    fn i64_remove_present_key_returns_value_and_compacts() {
5239        let mut m: HashMapData<i64> = HashMapData::new();
5240        unsafe {
5241            m.insert("a", 1);
5242            m.insert("b", 2);
5243            assert_eq!(m.remove("a"), Some(1));
5244        }
5245        assert_eq!(m.len(), 1);
5246        assert!(m.get_share("a").is_none());
5247        // "b" should still be reachable — bucket index was renumbered.
5248        assert_eq!(m.get_share("b"), Some(2));
5249    }
5250
5251    #[test]
5252    fn i64_remove_missing_key_returns_none_and_is_noop() {
5253        let mut m: HashMapData<i64> = HashMapData::new();
5254        unsafe {
5255            m.insert("a", 1);
5256            assert_eq!(m.remove("nope"), None);
5257        }
5258        assert_eq!(m.len(), 1);
5259        assert_eq!(m.get_share("a"), Some(1));
5260    }
5261
5262    #[test]
5263    fn i64_merge_copies_other_entries_with_last_write_wins() {
5264        let mut a: HashMapData<i64> = HashMapData::new();
5265        let mut b: HashMapData<i64> = HashMapData::new();
5266        unsafe {
5267            a.insert("x", 1);
5268            a.insert("shared", 10);
5269            b.insert("y", 2);
5270            b.insert("shared", 99);
5271            a.merge(&b);
5272        }
5273        assert_eq!(a.len(), 3);
5274        assert_eq!(a.get_share("x"), Some(1));
5275        assert_eq!(a.get_share("y"), Some(2));
5276        // shared overwritten by b's value
5277        assert_eq!(a.get_share("shared"), Some(99));
5278    }
5279
5280    #[test]
5281    fn i64_smoke_set_set_delete_size() {
5282        // Storage-layer counterpart of W13-hashmap-mutation smoke:
5283        //   let m = HashMap(); m.set("a", 1); m.set("b", 2); m.delete("a");
5284        //   m.size() == 1
5285        let mut m: HashMapData<i64> = HashMapData::new();
5286        unsafe {
5287            m.insert("a", 1);
5288            m.insert("b", 2);
5289            assert_eq!(m.remove("a"), Some(1));
5290        }
5291        assert_eq!(m.len(), 1);
5292        assert!(m.get_share("a").is_none());
5293        assert_eq!(m.get_share("b"), Some(2));
5294    }
5295
5296    #[test]
5297    fn i64_arc_make_mut_clone_on_write_does_not_disturb_shared_handle() {
5298        // The shape-vm-side handlers Arc::make_mut the receiver share —
5299        // this exercises the per-V Clone impl which allocates fresh
5300        // keys + values buffers and share-clones each element.
5301        let mut owned: Arc<HashMapData<i64>> = Arc::new(HashMapData::new());
5302        unsafe { Arc::make_mut(&mut owned).insert("a", 1) };
5303        // Snapshot share — second observer.
5304        let snapshot = Arc::clone(&owned);
5305        // Mutate via the local share — should clone-on-write.
5306        unsafe { Arc::make_mut(&mut owned).insert("b", 2) };
5307        assert_eq!(owned.len(), 2);
5308        // Snapshot is undisturbed.
5309        assert_eq!(snapshot.len(), 1);
5310        assert_eq!(snapshot.get_share("a"), Some(1));
5311        assert!(snapshot.get_share("b").is_none());
5312    }
5313
5314    // ── V = *const StringObj (HeapElement) ──────────────────────────────
5315
5316    fn s_obj(s: &str) -> *const StringObj {
5317        StringObj::new(s) as *const StringObj
5318    }
5319
5320    #[test]
5321    fn string_insert_v2_retain_share_threading() {
5322        // Pin: insert transfers one share to the map; the original share
5323        // we keep here is the "witness" that survives.
5324        let mut m: HashMapData<*const StringObj> = HashMapData::new();
5325        let v1 = s_obj("hello");
5326        // Bump witness share so we can observe the map's share separately
5327        // — refcount=2 after retain.
5328        unsafe { crate::v2::refcount::v2_retain(&(*v1).header) };
5329        assert_eq!(unsafe { v2_get_refcount(&(*v1).header) }, 2);
5330        // Insert (transfers one share to the map).
5331        unsafe { m.insert("k", v1) };
5332        // Refcount: witness share + map share = 2 (unchanged because
5333        // we transferred 1 to the map, leaving 1 with the witness).
5334        assert_eq!(unsafe { v2_get_refcount(&(*v1).header) }, 2);
5335        // Map drops at end — retires its share. Manually drop to observe.
5336        drop(m);
5337        assert_eq!(unsafe { v2_get_refcount(&(*v1).header) }, 1);
5338        // Release the witness share.
5339        unsafe {
5340            use crate::v2::heap_element::HeapElement;
5341            StringObj::release_elem(v1);
5342        }
5343    }
5344
5345    #[test]
5346    fn string_insert_overwrite_retires_old_share() {
5347        // Pin: insert with existing key retires the old value's share via
5348        // V::release_owned.
5349        let mut m: HashMapData<*const StringObj> = HashMapData::new();
5350        let old = s_obj("old");
5351        let witness = old;
5352        unsafe { crate::v2::refcount::v2_retain(&(*old).header) }; // witness = 2
5353        unsafe { m.insert("k", old) };
5354        assert_eq!(unsafe { v2_get_refcount(&(*witness).header) }, 2);
5355        // Overwrite with new value; the old share inside the map is retired.
5356        let new_val = s_obj("new");
5357        unsafe { m.insert("k", new_val) };
5358        // Map no longer holds the original value's share — witness alone.
5359        assert_eq!(unsafe { v2_get_refcount(&(*witness).header) }, 1);
5360        // Release witness.
5361        unsafe {
5362            use crate::v2::heap_element::HeapElement;
5363            StringObj::release_elem(witness);
5364        }
5365        // m drops new_val + key allocations naturally at scope end.
5366    }
5367
5368    #[test]
5369    fn string_remove_transfers_share_to_caller() {
5370        // Pin: remove returns the value, transferring its share to the caller.
5371        let mut m: HashMapData<*const StringObj> = HashMapData::new();
5372        let v = s_obj("val");
5373        unsafe { crate::v2::refcount::v2_retain(&(*v).header) }; // witness shares = 2
5374        unsafe { m.insert("k", v) };
5375        assert_eq!(unsafe { v2_get_refcount(&(*v).header) }, 2);
5376        let removed = unsafe { m.remove("k") };
5377        assert!(removed.is_some());
5378        let removed_ptr = removed.unwrap();
5379        assert_eq!(removed_ptr, v);
5380        // Refcount unchanged: map released its share + remove transferred
5381        // a share to the caller (this fn) = net 0 change.
5382        assert_eq!(unsafe { v2_get_refcount(&(*v).header) }, 2);
5383        // Release the witness + the removed share.
5384        unsafe {
5385            use crate::v2::heap_element::HeapElement;
5386            StringObj::release_elem(removed_ptr);
5387            StringObj::release_elem(v);
5388        }
5389    }
5390
5391    #[test]
5392    fn string_get_share_bumps_refcount_for_caller() {
5393        // Pin: get_share returns a fresh refcount-share copy, leaving
5394        // the map's share intact.
5395        let mut m: HashMapData<*const StringObj> = HashMapData::new();
5396        let v = s_obj("val");
5397        unsafe { m.insert("k", v) }; // map owns the only share now.
5398        assert_eq!(unsafe { v2_get_refcount(&(*v).header) }, 1);
5399        let got = m.get_share("k").expect("present");
5400        assert_eq!(got, v);
5401        // Refcount bumped — map (1) + caller's share (1) = 2.
5402        assert_eq!(unsafe { v2_get_refcount(&(*v).header) }, 2);
5403        // Release caller's share.
5404        unsafe {
5405            use crate::v2::heap_element::HeapElement;
5406            StringObj::release_elem(got);
5407        }
5408        assert_eq!(unsafe { v2_get_refcount(&(*v).header) }, 1);
5409        // Map drops at scope end, retiring last share.
5410    }
5411
5412    #[test]
5413    fn string_merge_share_clones_each_other_entry() {
5414        // Pin: merge bumps refcount on each value cloned from other.
5415        let mut a: HashMapData<*const StringObj> = HashMapData::new();
5416        let mut b: HashMapData<*const StringObj> = HashMapData::new();
5417        let v_x = s_obj("x_val");
5418        let v_y = s_obj("y_val");
5419        unsafe {
5420            a.insert("x", v_x);
5421            b.insert("y", v_y);
5422            assert_eq!(v2_get_refcount(&(*v_x).header), 1);
5423            assert_eq!(v2_get_refcount(&(*v_y).header), 1);
5424            a.merge(&b);
5425        }
5426        assert_eq!(a.len(), 2);
5427        // After merge: y is share-cloned into a — refcount = 2 (a + b).
5428        assert_eq!(unsafe { v2_get_refcount(&(*v_y).header) }, 2);
5429        // x is unchanged (only in a).
5430        assert_eq!(unsafe { v2_get_refcount(&(*v_x).header) }, 1);
5431    }
5432
5433    // ── V = HashMapKindedRef (recursive carrier) ──────────────────────────
5434    //
5435    // Wave N hashmap-value-v-arm follow-up (cluster-2 closure-wave-C,
5436    // 2026-05-16). Pin the per-V `insert` / `len` / `get_share` API on
5437    // `HashMapData<HashMapKindedRef>`. Storage-layer counterpart of
5438    // `v2_group_by` in `shape-vm/executor/objects/hashmap_methods.rs`.
5439
5440    #[test]
5441    fn hashmap_value_v_insert_appends_and_grows_index() {
5442        let mut outer: HashMapData<HashMapKindedRef> = HashMapData::new();
5443        // Two inner buckets — one for "small", one for "large".
5444        let mut inner_small: HashMapData<i64> = HashMapData::new();
5445        let mut inner_large: HashMapData<i64> = HashMapData::new();
5446        unsafe {
5447            inner_small.insert("a", 1);
5448            inner_small.insert("b", 2);
5449            inner_large.insert("c", 100);
5450            outer.insert(
5451                "small",
5452                HashMapKindedRef::I64(Arc::new(inner_small)),
5453            );
5454            outer.insert(
5455                "large",
5456                HashMapKindedRef::I64(Arc::new(inner_large)),
5457            );
5458        }
5459        assert_eq!(outer.len(), 2);
5460        // Bucket index has registrations for both group keys.
5461        let h_small = fnv1a_hash(b"small");
5462        let h_large = fnv1a_hash(b"large");
5463        assert!(outer.index.get(&h_small).is_some());
5464        assert!(outer.index.get(&h_large).is_some());
5465        // get_share returns a fresh per-variant Arc::clone-bumped copy.
5466        let small_ref = outer.get_share("small").expect("small bucket present");
5467        match small_ref {
5468            HashMapKindedRef::I64(arc) => assert_eq!(arc.len(), 2),
5469            other => panic!("unexpected variant {:?}", other.values_kind()),
5470        }
5471    }
5472
5473    #[test]
5474    fn hashmap_value_v_remove_returns_inner_and_compacts() {
5475        let mut outer: HashMapData<HashMapKindedRef> = HashMapData::new();
5476        let mut inner_a: HashMapData<i64> = HashMapData::new();
5477        let mut inner_b: HashMapData<i64> = HashMapData::new();
5478        unsafe {
5479            inner_a.insert("x", 1);
5480            inner_b.insert("y", 2);
5481            outer.insert("group-a", HashMapKindedRef::I64(Arc::new(inner_a)));
5482            outer.insert("group-b", HashMapKindedRef::I64(Arc::new(inner_b)));
5483            let removed = outer.remove("group-a");
5484            assert!(removed.is_some());
5485            // Removed bucket has the expected inner shape.
5486            match removed.unwrap() {
5487                HashMapKindedRef::I64(arc) => {
5488                    assert_eq!(arc.len(), 1);
5489                    assert_eq!(arc.get_share("x"), Some(1));
5490                }
5491                other => panic!("unexpected variant {:?}", other.values_kind()),
5492            }
5493        }
5494        assert_eq!(outer.len(), 1);
5495        // "group-b" reachable post-renumber.
5496        let b_ref = outer.get_share("group-b").expect("group-b present");
5497        match b_ref {
5498            HashMapKindedRef::I64(arc) => assert_eq!(arc.get_share("y"), Some(2)),
5499            other => panic!("unexpected variant {:?}", other.values_kind()),
5500        }
5501    }
5502
5503    #[test]
5504    fn hashmap_value_v_clone_share_clones_inner_arcs() {
5505        // HashMapData<V>::Clone walks elements via share_clone — for
5506        // V = HashMapKindedRef this calls HashMapKindedRef::clone which
5507        // is per-variant Arc::clone on the inner Arc<HashMapData<V_inner>>.
5508        // The clone yields fresh buffer allocations holding bumped Arcs.
5509        let mut outer: HashMapData<HashMapKindedRef> = HashMapData::new();
5510        let inner: Arc<HashMapData<i64>> = {
5511            let mut d: HashMapData<i64> = HashMapData::new();
5512            unsafe { d.insert("k", 42) };
5513            Arc::new(d)
5514        };
5515        // Refcount before insert: 1 (only `inner` owns).
5516        assert_eq!(Arc::strong_count(&inner), 1);
5517        unsafe { outer.insert("g", HashMapKindedRef::I64(Arc::clone(&inner))) };
5518        // Refcount after insert: 2 (inner + outer's buffer share).
5519        assert_eq!(Arc::strong_count(&inner), 2);
5520        // Clone outer — share_clone bumps the inner Arc one more time.
5521        let _outer_clone = outer.clone();
5522        assert_eq!(Arc::strong_count(&inner), 3);
5523        // Drop clone; refcount drops back to 2.
5524        drop(_outer_clone);
5525        assert_eq!(Arc::strong_count(&inner), 2);
5526    }
5527
5528    #[test]
5529    fn hashmap_value_v_drop_releases_inner_arcs() {
5530        // HashMapData<HashMapKindedRef>::Drop calls
5531        // <HashMapKindedRef as HashMapValueElem>::release_typed_array,
5532        // which walks the buffer with ptr::read and lets each element
5533        // drop (auto-derived → Arc::drop on inner Arc<HashMapData<V_inner>>).
5534        let inner: Arc<HashMapData<i64>> = {
5535            let mut d: HashMapData<i64> = HashMapData::new();
5536            unsafe { d.insert("k", 7) };
5537            Arc::new(d)
5538        };
5539        assert_eq!(Arc::strong_count(&inner), 1);
5540        {
5541            let mut outer: HashMapData<HashMapKindedRef> = HashMapData::new();
5542            unsafe {
5543                outer.insert("g", HashMapKindedRef::I64(Arc::clone(&inner)));
5544            }
5545            assert_eq!(Arc::strong_count(&inner), 2);
5546            // outer drops at scope-end; the inner Arc share retires.
5547        }
5548        assert_eq!(Arc::strong_count(&inner), 1);
5549    }
5550}
5551
5552#[cfg(test)]
5553mod hashset_mutation {
5554    //! W13-hashset-rebuild (ADR-006 §2.7.15 / Q16, 2026-05-10): pin the
5555    //! `insert` / `remove` / `contains` API contracts on `HashSetData`.
5556    //! Mirror of `hashmap_mutation` with the values column dropped.
5557    use super::*;
5558    use std::sync::Arc;
5559    fn k(s: &str) -> Arc<String> {
5560        Arc::new(s.to_string())
5561    }
5562
5563    #[test]
5564    fn empty_set_has_zero_len_and_is_empty() {
5565        let s = HashSetData::new();
5566        assert_eq!(s.len(), 0);
5567        assert!(s.is_empty());
5568        assert!(!s.contains("a"));
5569    }
5570
5571    #[test]
5572    fn insert_returns_true_for_new_key_false_for_duplicate() {
5573        let mut s = HashSetData::new();
5574        assert!(s.insert(k("a")));
5575        assert!(s.insert(k("b")));
5576        assert_eq!(s.len(), 2);
5577        // Duplicate insert is a no-op.
5578        assert!(!s.insert(k("a")));
5579        assert_eq!(s.len(), 2);
5580    }
5581
5582    #[test]
5583    fn contains_finds_inserted_keys() {
5584        let mut s = HashSetData::new();
5585        s.insert(k("a"));
5586        s.insert(k("b"));
5587        assert!(s.contains("a"));
5588        assert!(s.contains("b"));
5589        assert!(!s.contains("c"));
5590    }
5591
5592    #[test]
5593    fn remove_returns_true_for_present_false_for_missing() {
5594        let mut s = HashSetData::new();
5595        s.insert(k("a"));
5596        s.insert(k("b"));
5597        assert!(s.remove("a"));
5598        assert!(!s.contains("a"));
5599        assert!(s.contains("b"));
5600        assert_eq!(s.len(), 1);
5601        // Missing-key remove is a no-op.
5602        assert!(!s.remove("c"));
5603        assert_eq!(s.len(), 1);
5604    }
5605
5606    #[test]
5607    fn from_keys_collapses_duplicates_first_wins() {
5608        let s = HashSetData::from_keys(vec![k("a"), k("b"), k("a"), k("c")]);
5609        assert_eq!(s.len(), 3);
5610        assert_eq!(s.keys[0].as_str(), "a");
5611        assert_eq!(s.keys[1].as_str(), "b");
5612        assert_eq!(s.keys[2].as_str(), "c");
5613    }
5614
5615    #[test]
5616    fn smoke_target_add_two_then_size() {
5617        // Storage-layer counterpart of the W13-hashset-rebuild smoke
5618        // target: `let s = Set(); s.add("a"); s.add("b"); print(s.size())`
5619        // outputs 2.
5620        let mut s = HashSetData::new();
5621        s.insert(k("a"));
5622        s.insert(k("b"));
5623        assert_eq!(s.len(), 2);
5624    }
5625
5626    #[test]
5627    fn arc_make_mut_clone_on_write_preserves_other_share() {
5628        // Pin the §2.7.4 / playbook clone-on-write invariant: when
5629        // `Arc<HashSetData>` has multiple shares, `Arc::make_mut`
5630        // clones the inner `HashSetData` so the other share stays
5631        // immutable. Mirror of `hashmap_mutation`'s clone-on-write
5632        // test.
5633        let mut a = Arc::new(HashSetData::new());
5634        Arc::make_mut(&mut a).insert(k("a"));
5635        let snapshot = Arc::clone(&a);
5636        // After the snapshot, mutating `a` clones the inner data.
5637        Arc::make_mut(&mut a).insert(k("b"));
5638        assert_eq!(a.len(), 2);
5639        // Snapshot retains the pre-mutation length.
5640        assert_eq!(snapshot.len(), 1);
5641        assert!(snapshot.contains("a"));
5642        assert!(!snapshot.contains("b"));
5643    }
5644}
5645
5646#[cfg(test)]
5647mod deque_mutation {
5648    //! W15-deque (ADR-006 §2.7.19 / Q20, 2026-05-10): pin the
5649    //! `push_front` / `push_back` / `pop_front` / `pop_back` API
5650    //! contracts on `DequeData`. Mirror of `hashset_mutation` with
5651    //! the bucket-index dropped (Deque is order-preserving with no
5652    //! deduplication, so no parallel hash structure is needed).
5653    use super::*;
5654    use std::sync::Arc;
5655
5656    fn s(text: &str) -> Arc<HeapValue> {
5657        Arc::new(HeapValue::String(Arc::new(text.to_string())))
5658    }
5659
5660    fn i(n: i64) -> Arc<HeapValue> {
5661        Arc::new(HeapValue::BigInt(Arc::new(n)))
5662    }
5663
5664    #[test]
5665    fn empty_deque_has_zero_len_and_is_empty() {
5666        let d = DequeData::new();
5667        assert_eq!(d.len(), 0);
5668        assert!(d.is_empty());
5669        assert!(d.peek_front().is_none());
5670        assert!(d.peek_back().is_none());
5671        assert!(d.get(0).is_none());
5672    }
5673
5674    #[test]
5675    fn push_back_and_pop_front_preserve_fifo_order() {
5676        // FIFO: push 1,2,3 to back, pop from front yields 1,2,3.
5677        let mut d = DequeData::new();
5678        d.push_back(i(1));
5679        d.push_back(i(2));
5680        d.push_back(i(3));
5681        assert_eq!(d.len(), 3);
5682        let p1 = d.pop_front().expect("front");
5683        assert!(matches!(p1.as_ref(), HeapValue::BigInt(b) if **b == 1));
5684        let p2 = d.pop_front().expect("front");
5685        assert!(matches!(p2.as_ref(), HeapValue::BigInt(b) if **b == 2));
5686        let p3 = d.pop_front().expect("front");
5687        assert!(matches!(p3.as_ref(), HeapValue::BigInt(b) if **b == 3));
5688        assert!(d.pop_front().is_none());
5689    }
5690
5691    #[test]
5692    fn push_front_and_pop_back_preserve_reverse_order() {
5693        // Reverse: push 1,2,3 to front, pop from back yields 1,2,3.
5694        let mut d = DequeData::new();
5695        d.push_front(i(1));
5696        d.push_front(i(2));
5697        d.push_front(i(3));
5698        // Now layout is [3, 2, 1] front-to-back.
5699        let p1 = d.pop_back().expect("back");
5700        assert!(matches!(p1.as_ref(), HeapValue::BigInt(b) if **b == 1));
5701        let p2 = d.pop_back().expect("back");
5702        assert!(matches!(p2.as_ref(), HeapValue::BigInt(b) if **b == 2));
5703        let p3 = d.pop_back().expect("back");
5704        assert!(matches!(p3.as_ref(), HeapValue::BigInt(b) if **b == 3));
5705    }
5706
5707    #[test]
5708    fn smoke_target_push_back_push_front_pop_back() {
5709        // Storage-layer counterpart of the W15-deque smoke target:
5710        // `let d = Deque(); d.push_back(1); d.push_front(0); d.pop_back()`
5711        // returns `1`. After the two pushes the layout is [0, 1]
5712        // front-to-back; pop_back yields 1.
5713        let mut d = DequeData::new();
5714        d.push_back(i(1));
5715        d.push_front(i(0));
5716        assert_eq!(d.len(), 2);
5717        let popped = d.pop_back().expect("back");
5718        assert!(matches!(popped.as_ref(), HeapValue::BigInt(b) if **b == 1));
5719        // Front element retained.
5720        assert_eq!(d.len(), 1);
5721        let front = d.peek_front().expect("front").clone();
5722        assert!(matches!(front.as_ref(), HeapValue::BigInt(b) if **b == 0));
5723    }
5724
5725    #[test]
5726    fn peek_front_back_and_get_borrow_without_removing() {
5727        let mut d = DequeData::new();
5728        d.push_back(s("a"));
5729        d.push_back(s("b"));
5730        d.push_back(s("c"));
5731        assert_eq!(d.len(), 3);
5732        let front = d.peek_front().expect("front");
5733        assert!(matches!(front.as_ref(), HeapValue::String(t) if t.as_str() == "a"));
5734        let back = d.peek_back().expect("back");
5735        assert!(matches!(back.as_ref(), HeapValue::String(t) if t.as_str() == "c"));
5736        let mid = d.get(1).expect("idx 1");
5737        assert!(matches!(mid.as_ref(), HeapValue::String(t) if t.as_str() == "b"));
5738        // Length unchanged after read-only borrows.
5739        assert_eq!(d.len(), 3);
5740    }
5741
5742    #[test]
5743    fn from_items_preserves_insertion_order() {
5744        let d = DequeData::from_items(vec![s("a"), s("b"), s("c")]);
5745        assert_eq!(d.len(), 3);
5746        assert!(matches!(d.get(0).unwrap().as_ref(), HeapValue::String(t) if t.as_str() == "a"));
5747        assert!(matches!(d.get(1).unwrap().as_ref(), HeapValue::String(t) if t.as_str() == "b"));
5748        assert!(matches!(d.get(2).unwrap().as_ref(), HeapValue::String(t) if t.as_str() == "c"));
5749    }
5750
5751    #[test]
5752    fn arc_make_mut_clone_on_write_preserves_other_share() {
5753        // Pin the §2.7.4 / playbook clone-on-write invariant: when
5754        // `Arc<DequeData>` has multiple shares, `Arc::make_mut` clones
5755        // the inner `DequeData` so the other share stays immutable.
5756        // Mirror of `hashset_mutation`'s clone-on-write test.
5757        let mut a = Arc::new(DequeData::new());
5758        Arc::make_mut(&mut a).push_back(i(1));
5759        let snapshot = Arc::clone(&a);
5760        // After the snapshot, mutating `a` clones the inner data.
5761        Arc::make_mut(&mut a).push_back(i(2));
5762        assert_eq!(a.len(), 2);
5763        // Snapshot retains the pre-mutation length.
5764        assert_eq!(snapshot.len(), 1);
5765    }
5766}
5767
5768mod priority_queue_mutation {
5769    //! W15-priority-queue (ADR-006 §2.7.18 / Q19, 2026-05-10): pin the
5770    //! `push` / `pop` / `peek` / heap-invariant API contracts on
5771    //! `PriorityQueueData`. Mirror of `hashset_mutation` for the
5772    //! cardinality-amendment shape, with i64-priority-only payload
5773    //! semantics per the §2.7.18 ruling.
5774    use super::*;
5775    use std::sync::Arc;
5776
5777    #[test]
5778    fn empty_pq_has_zero_len_and_is_empty() {
5779        let pq = PriorityQueueData::new();
5780        assert_eq!(pq.len(), 0);
5781        assert!(pq.is_empty());
5782        assert_eq!(pq.peek(), None);
5783    }
5784
5785    #[test]
5786    fn push_increases_len_and_pop_returns_min() {
5787        // Storage-layer counterpart of the W15-priority-queue smoke
5788        // target: `pq.push(3); pq.push(1); pq.push(2); pq.pop() == 1`.
5789        let mut pq = PriorityQueueData::new();
5790        pq.push(3);
5791        pq.push(1);
5792        pq.push(2);
5793        assert_eq!(pq.len(), 3);
5794        assert_eq!(pq.peek(), Some(1));
5795        assert_eq!(pq.pop(), Some(1));
5796        assert_eq!(pq.len(), 2);
5797    }
5798
5799    #[test]
5800    fn pop_returns_none_on_empty() {
5801        let mut pq = PriorityQueueData::new();
5802        assert_eq!(pq.pop(), None);
5803    }
5804
5805    #[test]
5806    fn pop_yields_ascending_order() {
5807        // Pin the min-heap invariant: repeated `pop()` yields keys
5808        // in ascending order regardless of insertion order.
5809        let mut pq = PriorityQueueData::new();
5810        for v in [5, 3, 7, 1, 9, 4, 2, 8, 6] {
5811            pq.push(v);
5812        }
5813        let mut out = Vec::new();
5814        while let Some(v) = pq.pop() {
5815            out.push(v);
5816        }
5817        assert_eq!(out, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
5818    }
5819
5820    #[test]
5821    fn to_sorted_vec_returns_ascending_without_consuming() {
5822        let mut pq = PriorityQueueData::new();
5823        for v in [3, 1, 4, 1, 5, 9, 2, 6] {
5824            pq.push(v);
5825        }
5826        let sorted = pq.to_sorted_vec();
5827        assert_eq!(sorted, vec![1, 1, 2, 3, 4, 5, 6, 9]);
5828        // Original PQ is undisturbed.
5829        assert_eq!(pq.len(), 8);
5830    }
5831
5832}
5833
5834mod channel_storage {
5835    //! W15-channel-rebuild (ADR-006 §2.7.20 / Q21, 2026-05-10): pin the
5836    //! `send` / `try_recv` / `close` / `is_closed` / `len` / `is_empty`
5837    //! API contracts on `ChannelData`. Sync same-thread path only —
5838    //! cross-task blocking `recv()` is the §2.7.4 task-scheduler
5839    //! boundary tracked separately.
5840    use super::*;
5841    use crate::kinded_slot::KindedSlot;
5842    use std::sync::Arc;
5843
5844    #[test]
5845    fn empty_channel_has_zero_len_and_is_empty_open() {
5846        let c = ChannelData::new();
5847        assert_eq!(c.len(), 0);
5848        assert!(c.is_empty());
5849        assert!(!c.is_closed());
5850        assert!(c.try_recv().is_none());
5851    }
5852
5853    #[test]
5854    fn send_then_try_recv_round_trips_int() {
5855        // Storage-layer counterpart of the W15-channel-rebuild smoke
5856        // target: `let c = Channel(); c.send(1); c.recv()` returns 1.
5857        let c = ChannelData::new();
5858        c.send(KindedSlot::from_int(1)).expect("send on open channel");
5859        let got = c.try_recv().expect("queued element");
5860        assert_eq!(got.as_i64(), Some(1));
5861        assert!(c.is_empty());
5862    }
5863
5864    #[test]
5865    fn fifo_send_recv_order() {
5866        // Producer pushes 1, 2, 3; consumer drains in the same order.
5867        let c = ChannelData::new();
5868        c.send(KindedSlot::from_int(1)).unwrap();
5869        c.send(KindedSlot::from_int(2)).unwrap();
5870        c.send(KindedSlot::from_int(3)).unwrap();
5871        assert_eq!(c.len(), 3);
5872        assert_eq!(c.try_recv().unwrap().as_i64(), Some(1));
5873        assert_eq!(c.try_recv().unwrap().as_i64(), Some(2));
5874        assert_eq!(c.try_recv().unwrap().as_i64(), Some(3));
5875        assert!(c.try_recv().is_none());
5876    }
5877
5878    #[test]
5879    fn close_blocks_further_sends_but_drains_queued() {
5880        // After close, send returns Err but queued elements still
5881        // recv cleanly (canonical drain-on-close semantics).
5882        let c = ChannelData::new();
5883        c.send(KindedSlot::from_int(7)).unwrap();
5884        c.close();
5885        assert!(c.is_closed());
5886        // Further send is rejected; the rejected slot is dropped
5887        // (refcount discipline preserved through KindedSlot::Drop).
5888        assert!(c.send(KindedSlot::from_int(8)).is_err());
5889        // Queued element still drains.
5890        assert_eq!(c.try_recv().unwrap().as_i64(), Some(7));
5891        assert!(c.try_recv().is_none());
5892    }
5893
5894    #[test]
5895    fn shared_arc_send_recv_observes_other_share() {
5896        // Two `Arc<ChannelData>` shares of the same channel observe
5897        // each other's mutations — the producer/consumer-endpoints
5898        // shape. Distinct from HashSet/HashMap (which are Arc::make_mut
5899        // clone-on-write) — Channel uses interior mutability via
5900        // Mutex.
5901        let producer = Arc::new(ChannelData::new());
5902        let consumer = Arc::clone(&producer);
5903        producer.send(KindedSlot::from_int(42)).unwrap();
5904        assert_eq!(consumer.len(), 1);
5905        let got = consumer.try_recv().unwrap();
5906        assert_eq!(got.as_i64(), Some(42));
5907        // After consumer drained, producer-side observes empty.
5908        assert!(producer.is_empty());
5909    }
5910
5911    #[test]
5912    fn dropping_channel_with_heap_payloads_retires_shares() {
5913        // Refcount discipline: KindedSlot payloads queued in the
5914        // channel own one strong-count share; dropping the channel
5915        // (last `Arc<ChannelData>` share retired) must drop each
5916        // queued slot and retire its inner share. Any Arc-leak would
5917        // surface as a non-zero strong-count after the channel
5918        // dropped.
5919        let s = Arc::new("payload".to_string());
5920        let weak = Arc::downgrade(&s);
5921        let c = ChannelData::new();
5922        c.send(KindedSlot::from_string_arc(s)).unwrap();
5923        // The queued slot owns the only strong share.
5924        assert_eq!(weak.strong_count(), 1);
5925        drop(c);
5926        assert_eq!(
5927            weak.strong_count(),
5928            0,
5929            "dropped Channel must retire queued KindedSlot shares"
5930        );
5931    }
5932
5933    #[test]
5934    fn closed_send_drops_rejected_payload_share() {
5935        // After close, a rejected send must NOT leak the payload
5936        // share — KindedSlot::Drop runs on the rejected slot.
5937        let c = ChannelData::new();
5938        c.close();
5939        let s = Arc::new("rejected".to_string());
5940        let weak = Arc::downgrade(&s);
5941        let slot = KindedSlot::from_string_arc(s);
5942        assert_eq!(weak.strong_count(), 1);
5943        // The rejected send consumes the slot and drops it internally.
5944        assert!(c.send(slot).is_err());
5945        assert_eq!(
5946            weak.strong_count(),
5947            0,
5948            "rejected-send slot must drop, not leak"
5949        );
5950    }
5951}
5952
5953// ── W14-variant-codegen unit tests (ADR-006 §2.7.17 / Q18) ──────────────────
5954
5955#[cfg(test)]
5956mod result_option_storage {
5957    //! W14-variant-codegen (ADR-006 §2.7.17 / Q18, 2026-05-10): pin the
5958    //! `ResultData::ok` / `err` and `OptionData::some` / `none` API
5959    //! contracts.
5960    use super::*;
5961    use crate::kinded_slot::KindedSlot;
5962    use std::sync::Arc;
5963
5964    #[test]
5965    fn ok_carrier_is_ok_true() {
5966        let payload = KindedSlot::from_int(42);
5967        let r = ResultData::ok(payload);
5968        assert!(r.is_ok);
5969        // Payload kind preserved.
5970        assert_eq!(r.payload.as_i64(), Some(42));
5971    }
5972
5973    #[test]
5974    fn err_carrier_is_ok_false() {
5975        let payload = KindedSlot::from_string_arc(Arc::new("oops".to_string()));
5976        let r = ResultData::err(payload);
5977        assert!(!r.is_ok);
5978        // Payload string preserved.
5979        assert_eq!(r.payload.as_str(), Some("oops"));
5980    }
5981
5982    #[test]
5983    fn result_clone_bumps_payload_share() {
5984        // The storage carrier's Clone path goes through KindedSlot's
5985        // explicit Clone impl, which dispatches retain on the payload's
5986        // kind. Verify that cloning the wrapper preserves the payload's
5987        // Arc identity (the inner Arc<String> share is bumped, not
5988        // duplicated).
5989        let payload_arc = Arc::new("hello".to_string());
5990        let payload = KindedSlot::from_string_arc(Arc::clone(&payload_arc));
5991        let r1 = ResultData::ok(payload);
5992        let r2 = r1.clone();
5993        // Pointer equality on the inner String: both wrappers
5994        // reference the same Arc<String> (the kinded clone bumped
5995        // the share, did not deep-copy the string body).
5996        assert_eq!(r1.payload.as_str(), Some("hello"));
5997        assert_eq!(r2.payload.as_str(), Some("hello"));
5998        // Original Arc retains an extra share from each KindedSlot
5999        // (1 own + 2 wrappers = 3 strong refs; the local `payload_arc`
6000        // is the third).
6001        assert!(Arc::strong_count(&payload_arc) >= 2);
6002    }
6003
6004    #[test]
6005    fn some_carrier_is_some_true() {
6006        let payload = KindedSlot::from_bool(true);
6007        let o = OptionData::some(payload);
6008        assert!(o.is_some);
6009        assert_eq!(o.payload.as_bool(), Some(true));
6010    }
6011
6012    #[test]
6013    fn none_carrier_is_some_false() {
6014        let o = OptionData::none();
6015        assert!(!o.is_some);
6016        // None payload is a placeholder Bool-kind zero-bits slot —
6017        // KindedSlot::Drop is a no-op on it (verified by the slot
6018        // raw bits and kind).
6019        assert_eq!(o.payload.slot().raw(), 0);
6020    }
6021
6022    #[test]
6023    fn smoke_target_ok_int_then_unwrap() {
6024        // Storage-layer counterpart of the W14-variant-codegen smoke
6025        // target: `let r = Ok(42); if r.is_ok() { print(r.unwrap_ok()) }`
6026        // outputs 42. The is_ok / unwrap_ok pair surfaces at the
6027        // storage tier as `r.is_ok` + `r.payload.as_i64()`.
6028        let r = ResultData::ok(KindedSlot::from_int(42));
6029        assert!(r.is_ok);
6030        assert_eq!(r.payload.as_i64(), Some(42));
6031    }
6032
6033    #[test]
6034    fn arc_wrap_typed_pointer_round_trip() {
6035        // Pin the typed-Arc raw-pointer dispatch contract: wrap an
6036        // Arc<ResultData> via Arc::into_raw, recover via
6037        // Arc::from_raw, verify pointer identity. This is the slot-
6038        // bits transit path that the §2.7.17 dispatch tables retire
6039        // in `clone_with_kind` / `drop_with_kind`.
6040        let arc = Arc::new(ResultData::ok(KindedSlot::from_int(7)));
6041        let bits = Arc::into_raw(arc) as u64;
6042        // Recover and verify is_ok.
6043        let arc2: Arc<ResultData> =
6044            unsafe { Arc::from_raw(bits as *const ResultData) };
6045        assert!(arc2.is_ok);
6046        assert_eq!(arc2.payload.as_i64(), Some(7));
6047        drop(arc2);
6048    }
6049}
6050
6051#[cfg(test)]
6052mod concurrency_storage {
6053    //! W17-concurrency (ADR-006 §2.7.25, 2026-05-11): pin the `lock`
6054    //! / `try_lock` / `set` / `get` API contracts on `MutexData`, the
6055    //! `load` / `store` / `fetch_add` / `fetch_sub` /
6056    //! `compare_exchange` contracts on `AtomicData`, and the
6057    //! `is_initialized` / `cached` / `take_initializer` /
6058    //! `store_result` contracts on `LazyData`. Storage-tier only —
6059    //! closure-call integration for `Lazy.get` lives at the handler
6060    //! tier (`executor/objects/concurrency_methods.rs`).
6061    use super::*;
6062    use crate::kinded_slot::KindedSlot;
6063    use std::sync::Arc;
6064
6065    // ── MutexData ──────────────────────────────────────────────────
6066
6067    #[test]
6068    fn mutex_new_holds_initial_value() {
6069        let m = MutexData::new(KindedSlot::from_int(42));
6070        assert_eq!(m.get().as_i64(), Some(42));
6071    }
6072
6073    #[test]
6074    fn mutex_lock_is_noop_at_landing() {
6075        let m = MutexData::new(KindedSlot::from_int(0));
6076        m.lock();
6077        // lock returns; observable state unchanged.
6078        assert_eq!(m.get().as_i64(), Some(0));
6079    }
6080
6081    #[test]
6082    fn mutex_try_lock_returns_true_uncontended() {
6083        let m = MutexData::new(KindedSlot::from_int(0));
6084        assert!(m.try_lock());
6085    }
6086
6087    #[test]
6088    fn mutex_set_replaces_value_and_drops_prior() {
6089        // Storage-layer counterpart of the smoke target's
6090        // `m.set(5); print(m.value)`.
6091        let m = MutexData::new(KindedSlot::from_int(0));
6092        m.set(KindedSlot::from_int(5));
6093        assert_eq!(m.get().as_i64(), Some(5));
6094    }
6095
6096    #[test]
6097    fn mutex_set_with_heap_payload_retires_shares() {
6098        // The prior slot drops cleanly when `set` replaces it; no
6099        // Arc-leak. A heap-bearing payload's strong-count returns to
6100        // zero after `set` and `drop(mutex)`.
6101        let s = Arc::new("initial".to_string());
6102        let weak = Arc::downgrade(&s);
6103        let m = MutexData::new(KindedSlot::from_string_arc(s));
6104        assert_eq!(weak.strong_count(), 1);
6105        m.set(KindedSlot::from_int(7));
6106        assert_eq!(
6107            weak.strong_count(),
6108            0,
6109            "Mutex.set must drop prior heap payload share"
6110        );
6111        drop(m);
6112    }
6113
6114    #[test]
6115    fn mutex_shared_arc_observes_set_mutations() {
6116        // Two `Arc<MutexData>` shares of the same mutex observe each
6117        // other's mutations — the producer/consumer-endpoints shape
6118        // (mirror of Channel).
6119        let m1 = Arc::new(MutexData::new(KindedSlot::from_int(0)));
6120        let m2 = Arc::clone(&m1);
6121        m1.set(KindedSlot::from_int(99));
6122        assert_eq!(m2.get().as_i64(), Some(99));
6123    }
6124
6125    // ── AtomicData ─────────────────────────────────────────────────
6126
6127    #[test]
6128    fn atomic_new_holds_initial_value() {
6129        let a = AtomicData::new(7);
6130        assert_eq!(a.load(), 7);
6131    }
6132
6133    #[test]
6134    fn atomic_store_replaces_value() {
6135        let a = AtomicData::new(0);
6136        a.store(42);
6137        assert_eq!(a.load(), 42);
6138    }
6139
6140    #[test]
6141    fn atomic_fetch_add_returns_prior_and_increments() {
6142        // Smoke-target storage layer: a starts at 0, fetch_add(1)
6143        // returns 0 (prior), load() returns 1.
6144        let a = AtomicData::new(0);
6145        let prior = a.fetch_add(1);
6146        assert_eq!(prior, 0);
6147        assert_eq!(a.load(), 1);
6148    }
6149
6150    #[test]
6151    fn atomic_fetch_sub_returns_prior_and_decrements() {
6152        let a = AtomicData::new(10);
6153        let prior = a.fetch_sub(3);
6154        assert_eq!(prior, 10);
6155        assert_eq!(a.load(), 7);
6156    }
6157
6158    #[test]
6159    fn atomic_compare_exchange_swaps_on_match() {
6160        let a = AtomicData::new(5);
6161        let prior = a.compare_exchange(5, 99);
6162        assert_eq!(prior, 5);
6163        assert_eq!(a.load(), 99);
6164    }
6165
6166    #[test]
6167    fn atomic_compare_exchange_keeps_on_mismatch() {
6168        let a = AtomicData::new(5);
6169        let prior = a.compare_exchange(7, 99);
6170        assert_eq!(prior, 5);
6171        assert_eq!(a.load(), 5);
6172    }
6173
6174    #[test]
6175    fn atomic_shared_arc_observes_other_share() {
6176        let a1 = Arc::new(AtomicData::new(0));
6177        let a2 = Arc::clone(&a1);
6178        a1.store(42);
6179        assert_eq!(a2.load(), 42);
6180        a2.fetch_add(8);
6181        assert_eq!(a1.load(), 50);
6182    }
6183
6184    // ── LazyData ───────────────────────────────────────────────────
6185
6186    #[test]
6187    fn lazy_new_is_not_initialized() {
6188        let dummy_closure = KindedSlot::from_int(0);
6189        // Note: at the storage tier we don't actually call the
6190        // closure — that lives at the handler tier. The closure
6191        // payload here is just any `KindedSlot`; `is_initialized`
6192        // looks at the cached value, not the initializer.
6193        let l = LazyData::new(dummy_closure);
6194        assert!(!l.is_initialized());
6195    }
6196
6197    #[test]
6198    fn lazy_take_initializer_then_store_result_marks_initialized() {
6199        // Simulates the handler-tier `lazy.get()` flow: take the
6200        // initializer, "run it" (the test substitutes a result), then
6201        // cache the result. After store_result, is_initialized=true
6202        // and cached() returns the stored value.
6203        let l = LazyData::new(KindedSlot::from_int(0));
6204        let init = l
6205            .take_initializer()
6206            .expect("initializer present before first get");
6207        assert!(!l.is_initialized());
6208        // "Run the initializer" — at storage-tier test we just drop
6209        // the initializer slot and synthesize a result.
6210        drop(init);
6211        l.store_result(KindedSlot::from_int(42));
6212        assert!(l.is_initialized());
6213        let got = l.cached().expect("cached after store_result");
6214        assert_eq!(got.as_i64(), Some(42));
6215    }
6216
6217    #[test]
6218    fn lazy_take_initializer_returns_none_after_caching() {
6219        // After cache is populated, `take_initializer` returns None
6220        // — the handler tier's get() uses this to detect "already
6221        // initialized, use cached() instead".
6222        let l = LazyData::new(KindedSlot::from_int(0));
6223        let _init = l.take_initializer().unwrap();
6224        l.store_result(KindedSlot::from_int(7));
6225        assert!(l.take_initializer().is_none());
6226    }
6227
6228    #[test]
6229    fn lazy_cached_returns_none_before_init() {
6230        let l = LazyData::new(KindedSlot::from_int(0));
6231        assert!(l.cached().is_none());
6232    }
6233
6234    #[test]
6235    fn lazy_dropping_lazy_with_heap_payload_retires_shares() {
6236        // Refcount discipline: the cached `KindedSlot` owns one
6237        // strong-count share; dropping the LazyData retires it.
6238        let s = Arc::new("cached_value".to_string());
6239        let weak = Arc::downgrade(&s);
6240        let l = LazyData::new(KindedSlot::from_int(0));
6241        l.store_result(KindedSlot::from_string_arc(s));
6242        assert_eq!(weak.strong_count(), 1);
6243        drop(l);
6244        assert_eq!(
6245            weak.strong_count(),
6246            0,
6247            "Dropped LazyData must retire cached KindedSlot's share"
6248        );
6249    }
6250}
6251
6252// Wave 2 Round 4 D4 ckpt-3 (2026-05-14): the `trait_object_storage` test mod
6253// was authored against `TraitObjectStorage.value: Arc<TypedObjectStorage>`
6254// + `make_object()` returning `Arc<TypedObjectStorage>`. Post inner-field
6255// shift to `*const TypedObjectStorage`, every test that does `Arc::clone(&obj)`
6256// or `Arc::downgrade(&obj)` no longer compiles, and every test that observes
6257// the inner refcount via the weak count needs to migrate to HeapHeader
6258// `get_refcount()` inspection. Ckpt-final adapts the tests in lockstep with
6259// the full HeapValue::TypedObject variant signature flip; intermediate
6260// close gate (broken cargo check OK per
6261// `docs/cluster-audits/bulldozer-multi-session-chain-pattern.md` §Discipline
6262// relaxed) preserves the test mod source verbatim under a never-match cfg
6263// so the ckpt-final adapter has the original assertions as the migration
6264// target.
6265#[cfg(any())]
6266mod trait_object_storage {
6267    //! W17-trait-object-storage (ADR-006 §2.7.24 / Q25.C, 2026-05-11):
6268    //! pin the `TraitObjectStorage` API + refcount-discipline contracts.
6269    //! Storage-tier only — `OpCode::BoxTraitObject` /
6270    //! `OpCode::DynMethodCall` emission and end-to-end dyn-coerce smoke
6271    //! live in W17-trait-object-emission (round 2 of Wave 2.6).
6272    //!
6273    //! Coverage:
6274    //!   - Construction (`TraitObjectStorage::new`)
6275    //!   - vtable / value field access
6276    //!   - `method()` lookup
6277    //!   - `vtable_eq()` identity contract per §Q25.C.2
6278    //!   - Clone bumps both inner Arc strong counts
6279    //!   - Drop retires both inner Arc strong counts
6280    //!   - `KindedSlot::from_trait_object` retain-on-clone parity
6281    //!   - `KindedSlot::from_trait_object` drop-decrement parity
6282    //!   - `Arc<TraitObjectStorage>` clone roundtrip (via clone_with_kind
6283    //!     contract through the kind label, not through HeapValue)
6284    //!   - End-to-end retain/drop balance over multiple clones
6285    use super::*;
6286    use crate::kinded_slot::KindedSlot;
6287    use crate::native_kind::NativeKind;
6288    use crate::value::{VTable, VTableEntry};
6289    use std::collections::HashMap;
6290    use std::sync::Arc;
6291
6292    /// Build a minimal `TypedObjectStorage` for tests — single i64 field,
6293    /// no heap-typed slots. Mirror of the shape used by concurrency
6294    /// tests' `KindedSlot::from_int` payloads.
6295    ///
6296    /// **Wave 2 Round 4 D4 ckpt-3 (2026-05-14): returns `*mut
6297    /// TypedObjectStorage` (v2-raw `_new` shape)** per the
6298    /// `TraitObjectStorage.value: *const TypedObjectStorage` inner-field
6299    /// shift. Tests that previously observed inner refcount via
6300    /// `Arc::downgrade(&obj)` are surfaced as broken pending test-side
6301    /// migration to HeapHeader refcount inspection
6302    /// (`unsafe { (*ptr).header.get_refcount() }`).
6303    fn make_object(value: i64) -> *mut TypedObjectStorage {
6304        let mut slots: Vec<crate::slot::ValueSlot> = Vec::with_capacity(1);
6305        slots.push(crate::slot::ValueSlot::from_int(value));
6306        let field_kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
6307        TypedObjectStorage::_new(
6308            42, // schema_id — arbitrary
6309            slots.into_boxed_slice(),
6310            0,  // heap_mask: no heap slots
6311            field_kinds,
6312        )
6313    }
6314
6315    /// Build a minimal `VTable` for tests — one `Direct` method entry.
6316    fn make_vtable(trait_name: &str, concrete_type_id: u32, method: &str) -> Arc<VTable> {
6317        let mut methods: HashMap<String, VTableEntry> = HashMap::new();
6318        methods.insert(
6319            method.to_string(),
6320            VTableEntry::Direct { function_id: 7 },
6321        );
6322        Arc::new(VTable {
6323            trait_names: vec![trait_name.to_string()],
6324            concrete_type_id,
6325            methods,
6326        })
6327    }
6328
6329    #[test]
6330    fn new_holds_value_and_vtable_arcs() {
6331        let obj = make_object(1);
6332        let vt = make_vtable("Animal", 100, "name");
6333        let storage = TraitObjectStorage::new(Arc::clone(&obj), Arc::clone(&vt));
6334        // Both halves remain accessible; vtable's concrete_type_id
6335        // matches what the impl declared.
6336        assert_eq!(storage.value.schema_id, 42);
6337        assert_eq!(storage.vtable.concrete_type_id, 100);
6338        assert_eq!(storage.vtable.trait_names[0], "Animal");
6339    }
6340
6341    #[test]
6342    fn method_lookup_returns_entry_for_known_method() {
6343        let obj = make_object(1);
6344        let vt = make_vtable("Animal", 100, "name");
6345        let storage = TraitObjectStorage::new(obj, vt);
6346        let entry = storage
6347            .method("name")
6348            .expect("known method present in vtable");
6349        match entry {
6350            VTableEntry::Direct { function_id } => assert_eq!(*function_id, 7),
6351            _ => panic!("expected Direct entry"),
6352        }
6353    }
6354
6355    #[test]
6356    fn method_lookup_returns_none_for_unknown_method() {
6357        let obj = make_object(1);
6358        let vt = make_vtable("Animal", 100, "name");
6359        let storage = TraitObjectStorage::new(obj, vt);
6360        assert!(storage.method("speak").is_none());
6361    }
6362
6363    #[test]
6364    fn vtable_eq_identifies_same_vtable_share() {
6365        // Per §Q25.C.2, vtable-identity check uses `Arc::ptr_eq`. Two
6366        // carriers built from the same vtable Arc compare equal.
6367        let obj1 = make_object(1);
6368        let obj2 = make_object(2);
6369        let vt = make_vtable("Animal", 100, "name");
6370        let s1 = TraitObjectStorage::new(obj1, Arc::clone(&vt));
6371        let s2 = TraitObjectStorage::new(obj2, Arc::clone(&vt));
6372        assert!(s1.vtable_eq(&s2));
6373    }
6374
6375    #[test]
6376    fn vtable_eq_rejects_distinct_vtables() {
6377        // Two carriers built from distinct vtables — even if the
6378        // trait name matches — fail the identity check.
6379        let obj1 = make_object(1);
6380        let obj2 = make_object(2);
6381        let vt1 = make_vtable("Animal", 100, "name");
6382        let vt2 = make_vtable("Animal", 100, "name");
6383        let s1 = TraitObjectStorage::new(obj1, vt1);
6384        let s2 = TraitObjectStorage::new(obj2, vt2);
6385        assert!(!s1.vtable_eq(&s2));
6386    }
6387
6388    #[test]
6389    fn clone_bumps_both_inner_arcs() {
6390        // TraitObjectStorage::clone is a pair of Arc bumps. Verify
6391        // each inner Arc's strong-count increases by one.
6392        let obj = make_object(1);
6393        let vt = make_vtable("Animal", 100, "name");
6394        let obj_weak = Arc::downgrade(&obj);
6395        let vt_weak = Arc::downgrade(&vt);
6396        let storage = TraitObjectStorage::new(Arc::clone(&obj), Arc::clone(&vt));
6397        // Now: external obj/vt + storage's clones = 2 strong each.
6398        // Drop the externals so we can observe the storage's owned shares.
6399        drop(obj);
6400        drop(vt);
6401        assert_eq!(obj_weak.strong_count(), 1);
6402        assert_eq!(vt_weak.strong_count(), 1);
6403
6404        // Clone storage — both inner Arcs should bump.
6405        let storage2 = storage.clone();
6406        assert_eq!(obj_weak.strong_count(), 2);
6407        assert_eq!(vt_weak.strong_count(), 2);
6408
6409        drop(storage);
6410        assert_eq!(obj_weak.strong_count(), 1);
6411        assert_eq!(vt_weak.strong_count(), 1);
6412
6413        drop(storage2);
6414        assert_eq!(obj_weak.strong_count(), 0);
6415        assert_eq!(vt_weak.strong_count(), 0);
6416    }
6417
6418    #[test]
6419    fn kinded_slot_from_trait_object_drop_decrement() {
6420        // The §2.7.7 retain-on-read protocol via `KindedSlot::Drop`
6421        // must retire one `Arc<TraitObjectStorage>` share when the
6422        // slot drops. Verify the strong-count returns to zero after
6423        // both the original Arc and the KindedSlot drop.
6424        let obj = make_object(99);
6425        let vt = make_vtable("Animal", 100, "name");
6426        let storage = Arc::new(TraitObjectStorage::new(obj, vt));
6427        let weak = Arc::downgrade(&storage);
6428        let slot = KindedSlot::from_trait_object(Arc::clone(&storage));
6429        // External: 1 share. Slot: 1 share. Total: 2.
6430        assert_eq!(weak.strong_count(), 2);
6431
6432        // Drop the external Arc — slot still holds one share.
6433        drop(storage);
6434        assert_eq!(weak.strong_count(), 1);
6435
6436        // Drop the slot — retires the last share.
6437        drop(slot);
6438        assert_eq!(weak.strong_count(), 0);
6439    }
6440
6441    #[test]
6442    fn kinded_slot_clone_bumps_share() {
6443        // KindedSlot::Clone retains via clone_with_kind — verify the
6444        // share count tracks correctly across clones.
6445        let obj = make_object(1);
6446        let vt = make_vtable("Animal", 100, "name");
6447        let storage = Arc::new(TraitObjectStorage::new(obj, vt));
6448        let weak = Arc::downgrade(&storage);
6449        let slot1 = KindedSlot::from_trait_object(Arc::clone(&storage));
6450        let slot2 = slot1.clone();
6451        let slot3 = slot2.clone();
6452        // External + 3 slots = 4 shares.
6453        assert_eq!(weak.strong_count(), 4);
6454
6455        drop(storage);
6456        drop(slot1);
6457        drop(slot2);
6458        // 1 slot remaining.
6459        assert_eq!(weak.strong_count(), 1);
6460        drop(slot3);
6461        assert_eq!(weak.strong_count(), 0);
6462    }
6463
6464    #[test]
6465    fn kinded_slot_kind_label_is_ptr_trait_object() {
6466        // The kind label must be `NativeKind::Ptr(HeapKind::TraitObject)`
6467        // for the §2.7.7 dispatch tables (clone_with_kind /
6468        // drop_with_kind) to find the correct arm. This pins the
6469        // construction-side contract.
6470        let obj = make_object(1);
6471        let vt = make_vtable("Animal", 100, "name");
6472        let storage = Arc::new(TraitObjectStorage::new(obj, vt));
6473        let slot = KindedSlot::from_trait_object(storage);
6474        assert_eq!(slot.kind(), NativeKind::Ptr(HeapKind::TraitObject));
6475    }
6476
6477    #[test]
6478    fn slot_bits_recover_to_typed_arc_via_canonical_pattern() {
6479        // The canonical recovery pattern (per `3ac2f11` precedent):
6480        // bits = slot.raw(), Arc::from_raw, clone, into_raw. Verify
6481        // that round-tripping the raw bits through Arc::from_raw
6482        // recovers an Arc with the expected vtable identity.
6483        let obj = make_object(7);
6484        let vt = make_vtable("Animal", 100, "name");
6485        let storage = Arc::new(TraitObjectStorage::new(obj, vt));
6486        let original_vt_ptr = Arc::as_ptr(&storage.vtable);
6487        let slot = KindedSlot::from_trait_object(Arc::clone(&storage));
6488        let bits = slot.slot().raw();
6489
6490        // SAFETY: bits came from KindedSlot::from_trait_object which
6491        // stores `Arc::into_raw(Arc<TraitObjectStorage>)`. The slot
6492        // owns the share; we leak the recovered Arc back to keep the
6493        // slot's share intact for normal drop discipline.
6494        let recovered: Arc<TraitObjectStorage> =
6495            unsafe { Arc::from_raw(bits as *const TraitObjectStorage) };
6496        let cloned = Arc::clone(&recovered);
6497        let _ = Arc::into_raw(recovered); // restore slot's share
6498
6499        // Recovered Arc points to the same storage — same vtable Arc.
6500        assert!(Arc::ptr_eq(&cloned.vtable, &storage.vtable));
6501        // Pointer-equality on the inner vtable's raw pointer matches.
6502        assert_eq!(Arc::as_ptr(&cloned.vtable), original_vt_ptr);
6503
6504        drop(cloned);
6505        drop(slot);
6506        drop(storage);
6507    }
6508
6509    #[test]
6510    fn dropping_trait_object_with_typed_object_payload_retires_payload_share() {
6511        // Refcount discipline at the carrier level: drop the
6512        // TraitObjectStorage Arc to zero strong count, and verify
6513        // the inner TypedObject Arc's strong count returns to zero
6514        // too. This is the end-to-end retain/drop balance for the
6515        // fat-pointer carrier.
6516        let obj = make_object(99);
6517        let vt = make_vtable("Animal", 100, "name");
6518        let obj_weak = Arc::downgrade(&obj);
6519        let vt_weak = Arc::downgrade(&vt);
6520        let storage = Arc::new(TraitObjectStorage::new(obj, vt));
6521        // After move: storage holds the only shares of obj + vt.
6522        assert_eq!(obj_weak.strong_count(), 1);
6523        assert_eq!(vt_weak.strong_count(), 1);
6524
6525        drop(storage);
6526        assert_eq!(obj_weak.strong_count(), 0, "TypedObject share must retire");
6527        assert_eq!(vt_weak.strong_count(), 0, "VTable share must retire");
6528    }
6529
6530    // ── Wave 2 Agent E (2026-05-14): v2-raw HeapHeader migration tests ──────
6531
6532    #[test]
6533    fn new_initializes_heap_header() {
6534        // Wave 2 Agent E: confirm `TraitObjectStorage::new` initializes the
6535        // HeapHeader at offset 0 with HEAP_KIND_V2_TRAIT_OBJECT and
6536        // refcount=1. The header sits unused for `Arc<TraitObjectStorage>`
6537        // instances (Arc owns the lifecycle).
6538        let obj = make_object(1);
6539        let vt = make_vtable("Animal", 100, "name");
6540        let storage = TraitObjectStorage::new(obj, vt);
6541        assert_eq!(
6542            storage.header.kind(),
6543            crate::v2::heap_header::HEAP_KIND_V2_TRAIT_OBJECT,
6544        );
6545        assert_eq!(storage.header.get_refcount(), 1);
6546    }
6547
6548    #[test]
6549    fn v2_raw_new_drop_round_trip_balances_inner_arcs() {
6550        // Wave 2 Agent E: verify the v2-raw lifecycle (`_new` + `_drop`)
6551        // round-trips cleanly without leaking the inner Arc shares.
6552        // Mirror of D1's `TypedObjectStorage` round-trip test (commit
6553        // 0e4510d4).
6554        let obj = make_object(7);
6555        let vt = make_vtable("Animal", 100, "name");
6556        let obj_weak = Arc::downgrade(&obj);
6557        let vt_weak = Arc::downgrade(&vt);
6558        unsafe {
6559            let ptr = TraitObjectStorage::_new(obj, vt);
6560            assert!(!ptr.is_null());
6561            // Header refcount=1 + inner Arcs hold one share each.
6562            assert_eq!((*ptr).header.get_refcount(), 1);
6563            assert_eq!(obj_weak.strong_count(), 1);
6564            assert_eq!(vt_weak.strong_count(), 1);
6565            assert_eq!(
6566                (*ptr).header.kind(),
6567                crate::v2::heap_header::HEAP_KIND_V2_TRAIT_OBJECT,
6568            );
6569            // `_drop` retires the inner shares + deallocates.
6570            TraitObjectStorage::_drop(ptr);
6571        }
6572        assert_eq!(
6573            obj_weak.strong_count(),
6574            0,
6575            "TypedObject share must retire on _drop",
6576        );
6577        assert_eq!(
6578            vt_weak.strong_count(),
6579            0,
6580            "VTable share must retire on _drop",
6581        );
6582    }
6583
6584    #[test]
6585    fn heap_element_release_elem_to_zero_drops_payload() {
6586        // Wave 2 Agent E: verify the HeapElement trait dispatch
6587        // (`release_elem`) deallocates the carrier at refcount=0 and
6588        // retires the inner Arc shares. Mirror of D1's
6589        // `TypedObjectStorage::test_heap_element_release_elem_to_zero`.
6590        use crate::v2::heap_element::HeapElement;
6591        let obj = make_object(13);
6592        let vt = make_vtable("Animal", 100, "name");
6593        let obj_weak = Arc::downgrade(&obj);
6594        let vt_weak = Arc::downgrade(&vt);
6595        unsafe {
6596            let ptr = TraitObjectStorage::_new(obj, vt);
6597            assert_eq!((*ptr).header.get_refcount(), 1);
6598            // `release_elem` decrements to 0 and runs `_drop`.
6599            TraitObjectStorage::release_elem(ptr);
6600        }
6601        assert_eq!(
6602            obj_weak.strong_count(),
6603            0,
6604            "TypedObject share must retire after release_elem to zero",
6605        );
6606        assert_eq!(
6607            vt_weak.strong_count(),
6608            0,
6609            "VTable share must retire after release_elem to zero",
6610        );
6611    }
6612
6613    #[test]
6614    fn heap_element_release_elem_held_share_preserves_payload() {
6615        // Wave 2 Agent E: verify `release_elem` is a no-op at refcount > 1
6616        // (it decrements but does not deallocate). The inner Arc shares
6617        // remain held until the final `_drop`. Mirror of D1's
6618        // `TypedObjectStorage::test_heap_element_release_elem_held_share`.
6619        use crate::v2::heap_element::HeapElement;
6620        let obj = make_object(17);
6621        let vt = make_vtable("Animal", 100, "name");
6622        let obj_weak = Arc::downgrade(&obj);
6623        let vt_weak = Arc::downgrade(&vt);
6624        unsafe {
6625            let ptr = TraitObjectStorage::_new(obj, vt);
6626            // Bump refcount to 2 (simulate a second slot holding this
6627            // carrier).
6628            (*ptr).header.retain();
6629            assert_eq!((*ptr).header.get_refcount(), 2);
6630            // First release_elem: refcount=2 → 1, no dealloc.
6631            TraitObjectStorage::release_elem(ptr);
6632            assert_eq!((*ptr).header.get_refcount(), 1);
6633            // Inner shares still held.
6634            assert_eq!(obj_weak.strong_count(), 1);
6635            assert_eq!(vt_weak.strong_count(), 1);
6636            // Final _drop retires.
6637            TraitObjectStorage::_drop(ptr);
6638        }
6639        assert_eq!(obj_weak.strong_count(), 0);
6640        assert_eq!(vt_weak.strong_count(), 0);
6641    }
6642
6643    #[test]
6644    fn kinded_slot_from_trait_object_raw_constructor_kind_and_bits() {
6645        // Wave 2 Agent E: `KindedSlot::from_trait_object_raw` stores a
6646        // raw `*const TraitObjectStorage` directly (NOT `Arc::into_raw`)
6647        // with kind `NativeKind::Ptr(HeapKind::TraitObject)`. Mirror of
6648        // D1's `from_typed_object_raw_constructor_kind_and_bits` test.
6649        let obj = make_object(2);
6650        let vt = make_vtable("Animal", 100, "name");
6651        unsafe {
6652            let ptr = TraitObjectStorage::_new(obj, vt);
6653            let slot = KindedSlot::from_trait_object_raw(ptr);
6654            assert_eq!(slot.kind(), NativeKind::Ptr(HeapKind::TraitObject));
6655            // Slot bits are the raw pointer (NOT Arc::into_raw).
6656            assert_eq!(slot.slot().raw(), ptr as u64);
6657            // Forget the slot — Wave 2 transitional Arc-style dispatch
6658            // arms would call `Arc::decrement_strong_count` on raw
6659            // pointer bits (heap corruption) — see the D1 follow-up
6660            // lockstep requirement. Deallocate manually instead.
6661            std::mem::forget(slot);
6662            TraitObjectStorage::_drop(ptr);
6663        }
6664    }
6665
6666    #[test]
6667    fn v2_raw_carrier_size_matches_expected_layout() {
6668        // Wave 2 Agent E: pin the v2-raw struct layout — HeapHeader (8)
6669        // + Arc<TypedObjectStorage> (8) + Arc<VTable> (8) = 24 bytes
6670        // (matching the audit §E.3 24-byte size contract for the E-a
6671        // path). The inner Arcs stay 8-byte each in E's Round 2 scope;
6672        // D2's lockstep flip migrates the inner pointers but the
6673        // outer size contract is set here.
6674        assert_eq!(
6675            std::mem::size_of::<TraitObjectStorage>(),
6676            24,
6677            "TraitObjectStorage v2-raw layout must be 24 bytes \
6678             (HeapHeader 8 + Arc<TypedObjectStorage> 8 + Arc<VTable> 8)",
6679        );
6680    }
6681}