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                        // r5c-2-β-δ-(α) (2026-05-20): `HeapKind::TypedArray`
3729                        // dispatch arm RE-INSTATED. The V3-S5 ckpt-5-prime
3730                        // retirement claimed "no live slot bits carry this
3731                        // kind", but a struct with an `Array<T>` field is
3732                        // exactly such a site — `field_tag_to_native_kind`
3733                        // maps `FIELD_TAG_ARRAY` → `Ptr(HeapKind::TypedArray)`,
3734                        // so the field slot stores a v2-raw `*mut TypedArray<T>`
3735                        // carrier (HeapHeader at offset 0, refcount). When the
3736                        // enclosing `TypedObjectStorage` is freed this walk
3737                        // must release the field's share. `release_v2_typed_array`
3738                        // retires one refcount share and, on the last share,
3739                        // frees the array via the stamped-element-type
3740                        // `drop_array` / `drop_array_heap`. Mirror of the
3741                        // `TypedObject` field arm below (4-table lockstep,
3742                        // ADR-006 §2.3 / §2.7.7).
3743                        HeapKind::TypedArray => {
3744                            crate::v2::typed_array::release_v2_typed_array(bits as *mut u8);
3745                        }
3746                        // Wave 2 Agent D4 ckpt-2 (ADR-006 §2.3 / §2.7.5
3747                        // amendment, 2026-05-14): a `TypedObject` field of
3748                        // kind `NativeKind::Ptr(HeapKind::TypedObject)`
3749                        // holds slot bits = `ptr as u64` where
3750                        // `ptr: *const TypedObjectStorage` (v2-raw carrier
3751                        // per Agent D1's `_new` /
3752                        // `impl HeapElement for TypedObjectStorage`).
3753                        // Refcount discipline goes through `release_elem`
3754                        // (HeapElement trait — calls `v2_release` against
3755                        // the HeapHeader at offset 0; on refcount=0 the
3756                        // carrier-side `_drop` runs the per-field
3757                        // heap-mask walk and deallocates the `repr(C)`
3758                        // struct). Mirror of the §2.7.5 StringV2 /
3759                        // DecimalV2 release arms above (Agent B precedent).
3760                        HeapKind::TypedObject => {
3761                            use crate::v2::heap_element::HeapElement;
3762                            TypedObjectStorage::release_elem(
3763                                bits as *const TypedObjectStorage,
3764                            );
3765                        }
3766                        HeapKind::HashMap => {
3767                            // Wave 2 Round 3b C2-joint ckpt-2 (2026-05-14):
3768                            // bits are `Arc::into_raw(Arc<HashMapKindedRef>)`
3769                            // per ADR-006 §2.7.24 Q25.B SUPERSEDED carrier
3770                            // shape. Release dispatches outer Arc decrement;
3771                            // enum Drop chains to per-V `Arc<HashMapData<V>>`
3772                            // release.
3773                            std::sync::Arc::decrement_strong_count(
3774                                bits as *const HashMapKindedRef,
3775                            );
3776                        }
3777                        HeapKind::HashSet => {
3778                            std::sync::Arc::decrement_strong_count(bits as *const HashSetData);
3779                        }
3780                        HeapKind::Deque => {
3781                            std::sync::Arc::decrement_strong_count(bits as *const DequeData);
3782                        }
3783                        HeapKind::Channel => {
3784                            std::sync::Arc::decrement_strong_count(bits as *const ChannelData);
3785                        }
3786                        HeapKind::Mutex => {
3787                            std::sync::Arc::decrement_strong_count(bits as *const MutexData);
3788                        }
3789                        HeapKind::Atomic => {
3790                            std::sync::Arc::decrement_strong_count(bits as *const AtomicData);
3791                        }
3792                        HeapKind::Lazy => {
3793                            std::sync::Arc::decrement_strong_count(bits as *const LazyData);
3794                        }
3795                        // Wave 2 Agent D4 ckpt-2 (ADR-006 §2.7.24 /
3796                        // Q25.C.5 + E close 2026-05-14): TraitObject
3797                        // release via `HeapElement::release_elem` +
3798                        // carrier-side `_drop` (per Agent E's
3799                        // `impl HeapElement for TraitObjectStorage`).
3800                        // Mirror of the TypedObject arm above.
3801                        HeapKind::TraitObject => {
3802                            use crate::v2::heap_element::HeapElement;
3803                            TraitObjectStorage::release_elem(
3804                                bits as *const TraitObjectStorage,
3805                            );
3806                        }
3807                        HeapKind::Decimal => {
3808                            std::sync::Arc::decrement_strong_count(
3809                                bits as *const rust_decimal::Decimal,
3810                            );
3811                        }
3812                        HeapKind::BigInt => {
3813                            std::sync::Arc::decrement_strong_count(bits as *const i64);
3814                        }
3815                        HeapKind::DataTable => {
3816                            std::sync::Arc::decrement_strong_count(
3817                                bits as *const crate::datatable::DataTable,
3818                            );
3819                        }
3820                        HeapKind::IoHandle => {
3821                            std::sync::Arc::decrement_strong_count(bits as *const IoHandleData);
3822                        }
3823                        HeapKind::NativeView => {
3824                            std::sync::Arc::decrement_strong_count(
3825                                bits as *const NativeViewData,
3826                            );
3827                        }
3828                        HeapKind::Content => {
3829                            std::sync::Arc::decrement_strong_count(
3830                                bits as *const crate::content::ContentNode,
3831                            );
3832                        }
3833                        HeapKind::Instant => {
3834                            std::sync::Arc::decrement_strong_count(
3835                                bits as *const std::time::Instant,
3836                            );
3837                        }
3838                        HeapKind::Temporal => {
3839                            std::sync::Arc::decrement_strong_count(bits as *const TemporalData);
3840                        }
3841                        HeapKind::TableView => {
3842                            std::sync::Arc::decrement_strong_count(bits as *const TableViewData);
3843                        }
3844                        HeapKind::TaskGroup => {
3845                            std::sync::Arc::decrement_strong_count(bits as *const TaskGroupData);
3846                        }
3847                        HeapKind::FilterExpr => {
3848                            std::sync::Arc::decrement_strong_count(
3849                                bits as *const crate::value::FilterNode,
3850                            );
3851                        }
3852                        HeapKind::Reference => {
3853                            std::sync::Arc::decrement_strong_count(
3854                                bits as *const crate::reference::RefTarget,
3855                            );
3856                        }
3857                        HeapKind::Iterator => {
3858                            std::sync::Arc::decrement_strong_count(
3859                                bits as *const crate::iterator_state::IteratorState,
3860                            );
3861                        }
3862                        HeapKind::PriorityQueue => {
3863                            std::sync::Arc::decrement_strong_count(
3864                                bits as *const PriorityQueueData,
3865                            );
3866                        }
3867                        HeapKind::Range => {
3868                            std::sync::Arc::decrement_strong_count(bits as *const RangeData);
3869                        }
3870                        HeapKind::Result => {
3871                            std::sync::Arc::decrement_strong_count(bits as *const ResultData);
3872                        }
3873                        HeapKind::Option => {
3874                            std::sync::Arc::decrement_strong_count(bits as *const OptionData);
3875                        }
3876                        HeapKind::Closure => {
3877                            std::sync::Arc::decrement_strong_count(bits as *const HeapValue);
3878                        }
3879                        HeapKind::Future => {
3880                            // No-op: future-id inline scalar.
3881                        }
3882                        HeapKind::ModuleFn => {
3883                            // No-op: module-fn-id inline scalar.
3884                        }
3885                        HeapKind::Matrix => {
3886                            std::sync::Arc::decrement_strong_count(bits as *const MatrixData);
3887                        }
3888                        HeapKind::MatrixSlice => {
3889                            std::sync::Arc::decrement_strong_count(
3890                                bits as *const MatrixSliceData,
3891                            );
3892                        }
3893                        HeapKind::SharedCell => {
3894                            std::sync::Arc::decrement_strong_count(
3895                                bits as *const crate::v2::closure_layout::SharedCell,
3896                            );
3897                        }
3898                        HeapKind::Char => {
3899                            debug_assert!(
3900                                false,
3901                                "TypedObjectStorage::drop_fields: heap_mask bit {} set with \
3902                                 inline-scalar kind Char (schema_id={}); \
3903                                 construction-side soundness violation",
3904                                i, self.schema_id
3905                            );
3906                        }
3907                        HeapKind::NativeScalar => {
3908                            debug_assert!(
3909                                false,
3910                                "TypedObjectStorage::drop_fields: NativeScalar kinded carrier \
3911                                 pending phase-2c kinded redesign (ADR-006 §2.7.4); \
3912                                 schema_id={}, bit {}",
3913                                self.schema_id, i
3914                            );
3915                        }
3916                    },
3917                    other => {
3918                        debug_assert!(
3919                            false,
3920                            "TypedObjectStorage::drop_fields: heap_mask bit {} set with \
3921                             non-heap NativeKind {:?} (schema_id={}); \
3922                             construction-side soundness violation",
3923                            i, other, self.schema_id
3924                        );
3925                    }
3926                }
3927            }
3928        }
3929    }
3930
3931    /// In-place write of slot `idx` through a shared `&TypedObjectStorage`
3932    /// (i.e. through an `Arc<TypedObjectStorage>` with refcount > 1).
3933    /// Returns the prior `(bits, kind)` so the caller can run
3934    /// `drop_with_kind` on the released share. The caller transfers
3935    /// ownership of `new_bits` (one strong-count share for heap kinds) to
3936    /// the slot.
3937    ///
3938    /// This is the Q14 / ADR-006 §2.7.13 in-place write path for
3939    /// `RefTarget::TypedField` projection writes — the receiver `Arc`
3940    /// is shared between the ref carrier and the originating binding,
3941    /// so `Arc::get_mut` / `Arc::make_mut` cannot apply (refcount > 1
3942    /// by construction, and `TypedObjectStorage` is intentionally not
3943    /// `Clone` per the §2.5 documentation). The `Box<[ValueSlot]>`
3944    /// inside the storage is logically owned; the single-word `u64`
3945    /// inside each `ValueSlot` is written atomically (single-word
3946    /// aligned store on every supported architecture).
3947    ///
3948    /// # Safety
3949    ///
3950    /// Callers must guarantee:
3951    ///
3952    /// 1. **Single-threaded write**: the VM is single-threaded, and the
3953    ///    refs that drive this path are constrained by the §3.1
3954    ///    ref-escape analysis to stay within their originating task
3955    ///    (refs cannot cross task boundaries — error B0014
3956    ///    `NonSendableAcrossTaskBoundary`). No other thread may hold an
3957    ///    `&Arc<TypedObjectStorage>` to the same storage at the same
3958    ///    time the write executes.
3959    /// 2. **No aliased `&mut ValueSlot`**: callers must NOT mint a
3960    ///    `&mut ValueSlot` to slot `idx` from any path while this write
3961    ///    is in flight. The Q14 dispatch in `op_deref_store` /
3962    ///    `op_set_index_ref` is the only caller, and it operates on
3963    ///    `&TypedObjectStorage` exclusively.
3964    /// 3. **Kind invariance**: `new_kind` must equal
3965    ///    `self.field_kinds[idx]`. The Q14 RefTarget carries the
3966    ///    projected slot's kind at construction (`MakeFieldRef` sources
3967    ///    it from `field_type_tag`); the post-proof `§2.7.5.1` contract
3968    ///    forbids mid-life kind changes for typed fields. The caller
3969    ///    debug_asserts this before calling.
3970    /// 4. **`heap_mask` bit consistency**: for heap-kinded slots
3971    ///    (NativeKind::String or Ptr(HeapKind::_)), the corresponding
3972    ///    `heap_mask` bit must already be set per the `TypedObjectStorage::new`
3973    ///    construction-side contract, AND the prior bits must be a
3974    ///    valid `Arc::into_raw::<T>` for the slot's kind. The returned
3975    ///    `prior_bits` is exactly that share; the caller releases it via
3976    ///    `drop_with_kind` after running the post-write barrier.
3977    ///
3978    /// Q14 / ADR-006 §2.7.13. Mirror of the `clone_with_kind` /
3979    /// `drop_with_kind` symmetry used by `RefTarget::Local` and
3980    /// `RefTarget::ModuleBinding` writes (`stack_write_kinded` and
3981    /// `module_binding_write_kinded` already encapsulate this pattern
3982    /// for non-projected places; this is the projected-place mirror).
3983    #[inline]
3984    pub unsafe fn write_slot_in_place(
3985        &self,
3986        idx: usize,
3987        new_bits: u64,
3988    ) -> u64 {
3989        debug_assert!(
3990            idx < self.slots.len(),
3991            "TypedObjectStorage::write_slot_in_place: idx {} out of bounds (slots.len = {})",
3992            idx,
3993            self.slots.len(),
3994        );
3995        // SAFETY: see method contract. Single-threaded VM; refs cannot
3996        // escape across task boundaries; no aliased `&mut ValueSlot`
3997        // outstanding by construction; `Box<[ValueSlot]>` is `Sized`-laid-
3998        // out and the slot's `u64` is naturally aligned. We cast through
3999        // `&[ValueSlot]` -> `*const ValueSlot` -> `*mut ValueSlot` to
4000        // perform the single-word write. The slot's `field_kinds[idx]`
4001        // is the kind invariant; the caller already debug_asserted kind
4002        // equality, so the slot's heap-mask bit (if set) still applies
4003        // to the new bits.
4004        let slot_ptr = self.slots.as_ptr().add(idx) as *mut crate::slot::ValueSlot;
4005        let prior = (*slot_ptr).raw();
4006        *slot_ptr = crate::slot::ValueSlot::from_raw(new_bits);
4007        prior
4008    }
4009}
4010
4011impl Drop for TypedObjectStorage {
4012    /// ADR-006 §2.5 + Wave 2 Agent D1 (2026-05-14): delegates to the shared
4013    /// `drop_fields` helper that walks `heap_mask` and dispatches per-slot
4014    /// on `field_kinds[i]`. The same helper powers `_drop` (raw-pointer
4015    /// path) so both the legacy `Arc<TypedObjectStorage>` lifecycle and
4016    /// the v2-raw raw-pointer lifecycle retire heap-slot Arc shares with
4017    /// identical semantics.
4018    ///
4019    /// Soundness contract (must hold by construction; see
4020    /// `TypedObjectStorage::new` / `_new`):
4021    ///
4022    /// - For every `i` where `heap_mask >> i & 1 == 1`, the slot's `u64`
4023    ///   bits are the result of `Arc::into_raw::<T>` where `T` matches
4024    ///   `field_kinds[i]` (per the per-HeapKind table in `drop_fields`).
4025    /// - `NativeKind::Ptr(HeapKind::{Future, ModuleFn, Char, NativeScalar})`
4026    ///   are inline-scalar payloads (no `Arc<T>`); a heap_mask bit set
4027    ///   with one of those kinds is a soundness violation surfaced by
4028    ///   debug_assert in `drop_fields`.
4029    fn drop(&mut self) {
4030        // SAFETY: `drop_fields` walks the heap_mask + field_kinds arrays
4031        // and retires Arc shares per the construction-side contract. Runs
4032        // exactly once per instance (Rust's Drop machinery enforces this
4033        // for `Arc<TypedObjectStorage>` instances; raw-pointer instances
4034        // route through `_drop` which calls `drop_fields` directly and
4035        // never reaches here).
4036        unsafe { self.drop_fields(); }
4037    }
4038}
4039
4040// Wave 2 Agent D1 (2026-05-14): v2-raw HeapElement impl per ADR-006 §2.3
4041// amendment + audit §4.3 Obstacle O-3.a resolution. Constrains
4042// `TypedObjectStorage` to the HeapHeader-at-offset-0 v2-raw element-carrier
4043// contract so future call sites can store raw `*const TypedObjectStorage`
4044// bits in `TypedArray<*const TypedObjectStorage>` (audit §2.2 / §3.3 / S3
4045// territory) and dispatch per-element retain/release via the trait.
4046//
4047// The trait dispatches refcount through the on-header refcount via
4048// `v2_release` — distinct from the legacy `Arc<TypedObjectStorage>` path
4049// which dispatches via Rust `Arc::decrement_strong_count`. Per the struct
4050// docstring, both carrier shapes coexist at the struct level during the
4051// Wave 2 dispatch transition; the slot ABI discriminates them by
4052// allocation provenance (Agent D2's call sites use `_new` and the raw-
4053// pointer slot constructor; existing call sites use `Arc::new` and the
4054// legacy slot constructor).
4055unsafe impl crate::v2::heap_element::HeapElement for TypedObjectStorage {
4056    unsafe fn release_elem(ptr: *const Self) {
4057        if unsafe { crate::v2::refcount::v2_release(&(*ptr).header) } {
4058            unsafe { Self::_drop(ptr as *mut Self) };
4059        }
4060    }
4061}
4062
4063// ── TypedArray buckets (DELETED — V3-S5 ckpt-1, 2026-05-15) ──────────────────
4064//
4065// The `TypedArrayData` enum + impl blocks + `Display for TypedArrayData` +
4066// `typed_array_structural_eq` were DELETED here per ADR-006 §2.7.24 Q25.A
4067// SUPERSEDED + W12-typed-array-data-deletion-audit §3.5. The 22-variant
4068// enum migrates to v2-raw `TypedArray<T>` flat-struct per-T monomorphization.
4069// Consumer cascade lands across ckpt-2 (array_transform/aggregation/sets),
4070// ckpt-3 (array_operations/concat/object_creation), ckpt-4 (TypedBuffer +
4071// HeapValue::TypedArray arm + HeapKind::TypedArray ordinal), ckpt-5 (wire/
4072// json/marshal + 4-table lockstep delete), ckpt-6 (JIT FFI). The
4073// `Arc<TypedArrayData>` payload at heap_variants.rs:476 stays until ckpt-4.
4074//
4075// Refusal #1 binding: do not resurrect TypedArrayData under any rename
4076// (e.g. TypedArrayKind, TypedArrayCarrier, TypedBuffer<T> wrapper enum).
4077
4078// ── Temporal data ───────────────────────────────────────────────────────────
4079
4080/// Temporal data — consolidates Time, Duration, TimeSpan, Timeframe,
4081/// TimeReference, DateTimeExpr, and DataDateTimeRef.
4082#[derive(Debug, Clone)]
4083pub enum TemporalData {
4084    DateTime(chrono::DateTime<chrono::FixedOffset>),
4085    Duration(shape_ast::ast::Duration),
4086    TimeSpan(chrono::Duration),
4087    Timeframe(shape_ast::data::Timeframe),
4088    TimeReference(Box<shape_ast::ast::TimeReference>),
4089    DateTimeExpr(Box<shape_ast::ast::DateTimeExpr>),
4090    DataDateTimeRef(Box<shape_ast::ast::DataDateTimeRef>),
4091}
4092
4093impl TemporalData {
4094    #[inline]
4095    pub fn type_name(&self) -> &'static str {
4096        match self {
4097            TemporalData::DateTime(_) => "time",
4098            TemporalData::Duration(_) => "duration",
4099            TemporalData::TimeSpan(_) => "timespan",
4100            TemporalData::Timeframe(_) => "timeframe",
4101            TemporalData::TimeReference(_) => "time_reference",
4102            TemporalData::DateTimeExpr(_) => "datetime_expr",
4103            TemporalData::DataDateTimeRef(_) => "data_datetime_ref",
4104        }
4105    }
4106
4107    #[inline]
4108    pub fn is_truthy(&self) -> bool {
4109        true
4110    }
4111}
4112
4113impl fmt::Display for TemporalData {
4114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4115        match self {
4116            TemporalData::DateTime(t) => write!(f, "{}", t),
4117            TemporalData::Duration(d) => write!(f, "{:?}", d),
4118            TemporalData::TimeSpan(ts) => write!(f, "{}", ts),
4119            TemporalData::Timeframe(tf) => write!(f, "{:?}", tf),
4120            TemporalData::TimeReference(_) => write!(f, "<time_ref>"),
4121            TemporalData::DateTimeExpr(_) => write!(f, "<datetime_expr>"),
4122            TemporalData::DataDateTimeRef(_) => write!(f, "<data_datetime_ref>"),
4123        }
4124    }
4125}
4126
4127// ── Table view data ─────────────────────────────────────────────────────────
4128
4129/// Table view data — consolidates TypedTable, RowView, ColumnRef, and IndexedTable.
4130#[derive(Debug, Clone)]
4131pub enum TableViewData {
4132    TypedTable {
4133        schema_id: u64,
4134        table: Arc<crate::datatable::DataTable>,
4135    },
4136    RowView {
4137        schema_id: u64,
4138        table: Arc<crate::datatable::DataTable>,
4139        row_idx: usize,
4140    },
4141    ColumnRef {
4142        schema_id: u64,
4143        table: Arc<crate::datatable::DataTable>,
4144        col_id: u32,
4145    },
4146    IndexedTable {
4147        schema_id: u64,
4148        table: Arc<crate::datatable::DataTable>,
4149        index_col: u32,
4150    },
4151}
4152
4153impl TableViewData {
4154    #[inline]
4155    pub fn type_name(&self) -> &'static str {
4156        match self {
4157            TableViewData::TypedTable { .. } => "typed_table",
4158            TableViewData::RowView { .. } => "row",
4159            TableViewData::ColumnRef { .. } => "column",
4160            TableViewData::IndexedTable { .. } => "indexed_table",
4161        }
4162    }
4163
4164    #[inline]
4165    pub fn is_truthy(&self) -> bool {
4166        match self {
4167            TableViewData::TypedTable { table, .. } => table.row_count() > 0,
4168            TableViewData::RowView { .. } => true,
4169            TableViewData::ColumnRef { .. } => true,
4170            TableViewData::IndexedTable { table, .. } => table.row_count() > 0,
4171        }
4172    }
4173}
4174
4175impl fmt::Display for TableViewData {
4176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4177        match self {
4178            TableViewData::TypedTable { table, .. } => write!(
4179                f,
4180                "<typed_table:{}x{}>",
4181                table.row_count(),
4182                table.column_count()
4183            ),
4184            TableViewData::RowView { row_idx, .. } => write!(f, "<row:{}>", row_idx),
4185            TableViewData::ColumnRef { col_id, .. } => write!(f, "<column:{}>", col_id),
4186            TableViewData::IndexedTable { table, .. } => write!(
4187                f,
4188                "<indexed_table:{}x{}>",
4189                table.row_count(),
4190                table.column_count()
4191            ),
4192        }
4193    }
4194}
4195
4196// ── Generate HeapValue, HeapKind, kind(), is_truthy(), type_name() ──────────
4197//
4198// All generated from the single source of truth in define_heap_types!().
4199crate::define_heap_types!();
4200
4201// ── Manual Clone for HeapValue ──────────────────────────────────────────────
4202//
4203// ADR-006 §2.3 + Step 6: every heap-resident variant carries `Arc<T>` so its
4204// clone is one atomic refcount bump — no allocation, no payload copy. Inline
4205// scalars (`Future`, `Char`, `NativeScalar`) clone by `Copy`. `ClosureRaw`
4206// delegates to `OwnedClosureBlock::clone`, which already does a single
4207// `retain_typed_closure` refcount bump on the v2 closure block plus an Arc
4208// bump on the layout pointer.
4209//
4210// This impl is purely mechanical Arc::clone delegation — there is no
4211// `vw_clone` / `vw_drop` bookkeeping (the strict-typed bulldozer deleted
4212// every `ValueWord`-bearing variant).
4213impl Clone for HeapValue {
4214    fn clone(&self) -> Self {
4215        match self {
4216            // ADR-006 §2.3: Arc bump only — no allocation, no payload copy.
4217            HeapValue::String(v) => HeapValue::String(Arc::clone(v)),
4218            HeapValue::Decimal(v) => HeapValue::Decimal(Arc::clone(v)),
4219            HeapValue::BigInt(v) => HeapValue::BigInt(Arc::clone(v)),
4220            HeapValue::Future(v) => HeapValue::Future(*v),
4221            HeapValue::Char(v) => HeapValue::Char(*v),
4222            HeapValue::DataTable(v) => HeapValue::DataTable(Arc::clone(v)),
4223            HeapValue::Content(v) => HeapValue::Content(Arc::clone(v)),
4224            HeapValue::Instant(v) => HeapValue::Instant(Arc::clone(v)),
4225            HeapValue::IoHandle(v) => HeapValue::IoHandle(Arc::clone(v)),
4226            HeapValue::NativeScalar(v) => HeapValue::NativeScalar(*v),
4227            HeapValue::NativeView(v) => HeapValue::NativeView(Arc::clone(v)),
4228            // Wave 2 Round 4 D4 ckpt-final-prime² (2026-05-14): TypedObjectPtr's
4229            // Clone impl bumps the v2-raw HeapHeader-at-offset-0 refcount via
4230            // `v2_retain`, mirroring the typed-Arc clone shape of every other
4231            // heap arm.
4232            HeapValue::TypedObject(s) => HeapValue::TypedObject(s.clone()),
4233            // OwnedClosureBlock::clone is one refcount bump on the typed
4234            // closure block + one Arc bump on the shared layout.
4235            HeapValue::ClosureRaw(v) => HeapValue::ClosureRaw(v.clone()),
4236            HeapValue::TaskGroup(v) => HeapValue::TaskGroup(Arc::clone(v)),
4237            // V3-S5 ckpt-4 (2026-05-15): `HeapValue::TypedArray(v) =>
4238            // HeapValue::TypedArray(Arc::clone(v))` clone arm DELETED in
4239            // lockstep with the variant + the `TypedArrayData` enum
4240            // (ckpt-1) + `TypedBuffer<T>` / `AlignedTypedBuffer` wrapper
4241            // layer (ckpt-4). W12 audit §3.5/§B + ADR-006 §2.7.24 Q25.A
4242            // SUPERSEDED. Refusal #1 binding.
4243            HeapValue::Temporal(v) => HeapValue::Temporal(Arc::clone(v)),
4244            HeapValue::TableView(v) => HeapValue::TableView(Arc::clone(v)),
4245            // Wave 2 Round 3b C2-joint ckpt-2 (2026-05-14): payload is now
4246            // `HashMapKindedRef` (not `Arc<HashMapData>`); the enum's manual
4247            // Clone impl dispatches per-variant `Arc::clone` on the inner
4248            // `Arc<HashMapData<V>>` — preserving structural sharing.
4249            HeapValue::HashMap(v) => HeapValue::HashMap(v.clone()),
4250            // Wave 13 W13-hashset-rebuild (ADR-006 §2.7.15 / Q16,
4251            // 2026-05-10): mirror of HashMap — single strong-count bump
4252            // on the shared `Arc<HashSetData>`, no payload copy.
4253            HeapValue::HashSet(v) => HeapValue::HashSet(Arc::clone(v)),
4254            // Wave 15 W15-deque (ADR-006 §2.7.19 / Q20, 2026-05-10):
4255            // mirror of HashSet — single strong-count bump on the
4256            // shared `Arc<DequeData>`, no payload copy. Per-element
4257            // `Arc<HeapValue>` shares stay shared with the source.
4258            HeapValue::Deque(v) => HeapValue::Deque(Arc::clone(v)),
4259            // Wave-γ G-heap-filter-expr (ADR-006 §2.3 / Q8 amendment):
4260            // FilterExpr Arcs share the typed-Arc clone shape — single
4261            // strong-count bump, no payload copy.
4262            HeapValue::FilterExpr(v) => HeapValue::FilterExpr(Arc::clone(v)),
4263            // Wave 8 W8-T26 (ADR-006 §2.7.13 / Q14, 2026-05-10):
4264            // Reference Arcs share the typed-Arc clone shape — single
4265            // strong-count bump on the shared `Arc<RefTarget>`, no
4266            // payload copy.
4267            HeapValue::Reference(v) => HeapValue::Reference(Arc::clone(v)),
4268            // W13-iterator-state (ADR-006 §2.7.16 / Q17, 2026-05-10):
4269            // Iterator Arcs share the typed-Arc clone shape — single
4270            // strong-count bump on the shared `Arc<IteratorState>`. The
4271            // inner state is `Clone`-by-derive (typed-Arc payloads in
4272            // every field), but the outer `Arc` bump is the canonical
4273            // shared-receiver path.
4274            HeapValue::Iterator(v) => HeapValue::Iterator(Arc::clone(v)),
4275            // Wave 15 W15-channel-rebuild (ADR-006 §2.7.20 / Q21,
4276            // 2026-05-10): Channel Arcs share the typed-Arc clone shape
4277            // — single strong-count bump on the shared
4278            // `Arc<ChannelData>`. The inner `ChannelData` carries a
4279            // `Mutex<ChannelInner>` so two `Arc<ChannelData>` shares
4280            // observe each other's mutations; cloning the outer Arc
4281            // hands out a fresh endpoint of the same channel.
4282            HeapValue::Channel(v) => HeapValue::Channel(Arc::clone(v)),
4283            // Wave 15 W15-priority-queue (ADR-006 §2.7.18 / Q19,
4284            // 2026-05-10): mirror of HashSet — single strong-count bump
4285            // on the shared `Arc<PriorityQueueData>`, no payload copy.
4286            HeapValue::PriorityQueue(v) => HeapValue::PriorityQueue(Arc::clone(v)),
4287            // W15-range (ADR-006 §2.7.23 / Q24, 2026-05-10): Range Arcs
4288            // share the typed-Arc clone shape — single strong-count bump
4289            // on the shared `Arc<RangeData>`, no payload copy. RangeData
4290            // is small ({i64, i64, i64, bool}) so copies would be cheap,
4291            // but the shared-Arc shape matches the dispatch pattern of
4292            // every other heap arm.
4293            HeapValue::Range(v) => HeapValue::Range(Arc::clone(v)),
4294            // Wave 14 W14-variant-codegen (ADR-006 §2.7.17 / Q18,
4295            // 2026-05-10): Result/Option Arcs share the typed-Arc
4296            // clone shape — single strong-count bump on the shared
4297            // `Arc<ResultData>` / `Arc<OptionData>`. The inner
4298            // `KindedSlot` payload's share is preserved by the
4299            // shared Arc; ResultData/OptionData Clone (defined in
4300            // this file) does an inner KindedSlot Clone if the Arc
4301            // is unwrapped via Arc::make_mut.
4302            HeapValue::Result(v) => HeapValue::Result(Arc::clone(v)),
4303            HeapValue::Option(v) => HeapValue::Option(Arc::clone(v)),
4304            // W17-trait-object-storage (ADR-006 §2.7.24 / Q25.C,
4305            // 2026-05-11): TraitObject Arcs share the typed-Arc clone
4306            // shape — single strong-count bump on the shared
4307            // `Arc<TraitObjectStorage>`. Inner Arcs (`value: Arc<TypedObjectStorage>`
4308            // + `vtable: Arc<VTable>`) stay shared with the source;
4309            // `Arc::ptr_eq` on the vtable preserves the §Q25.C.2
4310            // `Self`-arg runtime identity contract across the clone.
4311            // Wave 2 Round 4 D4 ckpt-final-prime² (2026-05-14): TraitObjectPtr's
4312            // Clone impl bumps the v2-raw HeapHeader-at-offset-0 refcount via
4313            // `v2_retain`. Inner `value: *const TypedObjectStorage` and
4314            // `vtable: Arc<VTable>` shares are bumped through the Clone impl
4315            // of TraitObjectStorage itself (called by `_drop` / the per-field
4316            // discipline at refcount=0 release).
4317            HeapValue::TraitObject(v) => HeapValue::TraitObject(v.clone()),
4318            // W17-concurrency (ADR-006 §2.7.25, 2026-05-11): Mutex /
4319            // Atomic / Lazy Arcs share the typed-Arc clone shape —
4320            // single strong-count bump on the shared inner Arc, no
4321            // payload copy. Cloning a Mutex/Lazy yields a fresh
4322            // "endpoint" share of the same protected cell; Atomic
4323            // shares observe each other's load/store/fetch
4324            // operations. Same shape as Channel.
4325            HeapValue::Mutex(v) => HeapValue::Mutex(Arc::clone(v)),
4326            HeapValue::Atomic(v) => HeapValue::Atomic(Arc::clone(v)),
4327            HeapValue::Lazy(v) => HeapValue::Lazy(Arc::clone(v)),
4328            // W17-comptime-vm-dispatch (ADR-006 §2.7.26, 2026-05-12):
4329            // ModuleFn is an inline-scalar payload (no Arc).
4330            HeapValue::ModuleFn(v) => HeapValue::ModuleFn(*v),
4331            // ADR-006 §2.7.22 amendment (Round 18 S3, 2026-05-13): Matrix
4332            // and MatrixSlice arms share the typed-Arc clone shape — single
4333            // strong-count bump on the shared `Arc<MatrixData>` /
4334            // `Arc<MatrixSliceData>`. MatrixSlice's inner `parent: Arc<MatrixData>`
4335            // share stays shared with the source (cloning the slice does not
4336            // copy the parent matrix data). The HeapValue arm exists for the
4337            // ADR-005 §1 / ADR-006 §2.3 `HeapKind`↔`HeapValue` symmetry but
4338            // calling `slot.as_heap_value()` on Matrix/MatrixSlice-labeled
4339            // slot bits is unsound — slot bits are
4340            // `Arc::into_raw(Arc<MatrixData>)` / `Arc::into_raw(Arc<MatrixSliceData>)`,
4341            // NOT `Box<HeapValue>`. Pure-discriminator dispatch shape, mirror
4342            // of §2.7.9 FilterExpr / §2.7.13 Reference.
4343            HeapValue::Matrix(v) => HeapValue::Matrix(Arc::clone(v)),
4344            HeapValue::MatrixSlice(v) => HeapValue::MatrixSlice(Arc::clone(v)),
4345        }
4346    }
4347}
4348
4349// ── Shared comparison helpers ────────────────────────────────────────────────
4350
4351/// Cross-type numeric equality: BigInt vs Decimal.
4352#[inline]
4353fn bigint_decimal_eq(a: &i64, b: &rust_decimal::Decimal) -> bool {
4354    rust_decimal::Decimal::from(*a) == *b
4355}
4356
4357/// Cross-type numeric equality: NativeScalar vs BigInt.
4358#[inline]
4359fn native_scalar_bigint_eq(a: &NativeScalar, b: &i64) -> bool {
4360    a.as_i64().is_some_and(|v| v == *b)
4361}
4362
4363/// Cross-type numeric equality: NativeScalar vs Decimal.
4364#[inline]
4365fn native_scalar_decimal_eq(a: &NativeScalar, b: &rust_decimal::Decimal) -> bool {
4366    match a {
4367        NativeScalar::F32(v) => {
4368            rust_decimal::Decimal::from_f64_retain(*v as f64).is_some_and(|v| v == *b)
4369        }
4370        _ => a
4371            .as_i128()
4372            .map(|n| rust_decimal::Decimal::from_i128_with_scale(n, 0))
4373            .is_some_and(|to_dec| to_dec == *b),
4374    }
4375}
4376
4377/// Cross-type typed array equality: IntArray vs FloatArray (element-wise i64-as-f64).
4378///
4379/// NOTE (V3-S5 ckpt-5-prime²a, 2026-05-15): currently unreachable post-ckpt-4
4380/// HeapValue::TypedArray outer-arm deletion (no callers). Signature migrated
4381/// from `&TypedBuffer<i64>` / `&AlignedTypedBuffer` to `&[i64]` / `&[f64]` per
4382/// Migration shape (a). Retained for the eventual v2-raw `*mut TypedArray<T>`
4383/// per-T monomorphic rebuild (cluster-2 v2-raw-heap-audit territory).
4384#[inline]
4385fn int_float_array_eq(
4386    ints: &[i64],
4387    floats: &[f64],
4388) -> bool {
4389    ints.len() == floats.len()
4390        && ints
4391            .iter()
4392            .zip(floats.iter())
4393            .all(|(x, y)| (*x as f64) == *y)
4394}
4395
4396/// Matrix structural equality (row/col dimensions + element-wise).
4397#[inline]
4398fn matrix_eq(a: &MatrixData, b: &MatrixData) -> bool {
4399    a.rows == b.rows
4400        && a.cols == b.cols
4401        && a.data.len() == b.data.len()
4402        && a.data.iter().zip(b.data.iter()).all(|(x, y)| x == y)
4403}
4404
4405/// NativeView identity comparison.
4406#[inline]
4407fn native_view_eq(a: &NativeViewData, b: &NativeViewData) -> bool {
4408    a.ptr == b.ptr && a.mutable == b.mutable && a.layout.name == b.layout.name
4409}
4410
4411// `typed_array_structural_eq` DELETED — V3-S5 ckpt-1, 2026-05-15.
4412// Per-arm TypedArrayData structural equality dispatch is retired with the
4413// enum. Consumer sites at structural_eq + equals (HeapValue::TypedArray
4414// arm match) cascade-break here and surface for ckpt-4 (HeapValue::TypedArray
4415// arm rebuild atop v2-raw `*mut TypedArray<T>` per-T monomorphic dispatch).
4416
4417// ── Display ─────────────────────────────────────────────────────────────────
4418
4419impl fmt::Display for HeapValue {
4420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4421        match self {
4422            HeapValue::Char(c) => write!(f, "{}", c),
4423            HeapValue::String(s) => write!(f, "{}", s),
4424            HeapValue::TypedObject(_) => write!(f, "{{...}}"),
4425            HeapValue::ClosureRaw(owned) => {
4426                // SAFETY: OwnedClosureBlock's invariant guarantees the
4427                // pointer is live for the duration of `&self`.
4428                let fid = unsafe {
4429                    crate::v2::closure_raw::typed_closure_function_id(owned.as_ptr())
4430                };
4431                write!(f, "<closure:{}>", fid)
4432            }
4433            HeapValue::Decimal(d) => write!(f, "{}", d),
4434            HeapValue::BigInt(i) => write!(f, "{}", i),
4435            HeapValue::DataTable(dt) => {
4436                write!(f, "<datatable:{}x{}>", dt.row_count(), dt.column_count())
4437            }
4438            HeapValue::TableView(tv) => write!(f, "{}", tv),
4439            HeapValue::Content(node) => write!(f, "{}", node),
4440            HeapValue::Instant(t) => write!(f, "<instant:{:?}>", t.elapsed()),
4441            HeapValue::IoHandle(data) => {
4442                let status = if data.is_open() { "open" } else { "closed" };
4443                write!(f, "<io_handle:{}:{}>", data.path, status)
4444            }
4445            HeapValue::Future(id) => write!(f, "<future:{}>", id),
4446            HeapValue::TaskGroup(tg) => {
4447                write!(f, "<task_group:{}>", tg.task_ids.len())
4448            }
4449            HeapValue::Temporal(td) => write!(f, "{}", td),
4450            HeapValue::NativeScalar(v) => write!(f, "{v}"),
4451            HeapValue::NativeView(v) => write!(
4452                f,
4453                "<{}:{}@0x{:x}>",
4454                if v.mutable { "cmut" } else { "cview" },
4455                v.layout.name,
4456                v.ptr
4457            ),
4458            // V3-S5 ckpt-4 (2026-05-15): `HeapValue::TypedArray(ta) =>
4459            // write!(f, "{}", ta)` Display arm DELETED in lockstep with the
4460            // variant + the `TypedArrayData` Display impl (ckpt-1). W12
4461            // audit §3.5/§B + ADR-006 §2.7.24 Q25.A SUPERSEDED.
4462            HeapValue::HashMap(kref) => {
4463                // Wave 2 Round 3b C2-joint ckpt-3 (2026-05-14): full per-V
4464                // entry dump. Walks `*mut TypedArray<*const StringObj>`
4465                // keys + per-V `*mut TypedArray<V>` values; formats as
4466                // `{"k1": v1, "k2": v2}` using each V's natural Display.
4467                // For TypedObject / TraitObject the inner value renders
4468                // as a summary tag — full recursive rendering lives at
4469                // printing.rs (which has the depth-budgeted recursive
4470                // Display via format_heap_value). ADR-006 §2.7.24 Q25.B
4471                // SUPERSEDED + audit §C.4.
4472                hashmap_kref_display(kref, f)
4473            }
4474            // Wave 13 W13-hashset-rebuild (ADR-006 §2.7.15 / Q16,
4475            // 2026-05-10): one-keyspace mirror of HashMap's Display
4476            // shape — `{"a", "b", ...}` braces with comma-separated
4477            // quoted strings, no values.
4478            HeapValue::HashSet(d) => {
4479                write!(f, "{{")?;
4480                for (i, k) in d.keys.iter().enumerate() {
4481                    if i > 0 {
4482                        write!(f, ", ")?;
4483                    }
4484                    write!(f, "\"{}\"", k)?;
4485                }
4486                write!(f, "}}")
4487            }
4488            // Wave 15 W15-deque (ADR-006 §2.7.19 / Q20, 2026-05-10):
4489            // render front-to-back as `Deque[elem1, elem2, ...]` —
4490            // dispatch each element through the canonical ADR-005 §1
4491            // single-discriminator `HeapValue` Display.
4492            HeapValue::Deque(d) => {
4493                write!(f, "Deque[")?;
4494                for (i, v) in d.items.iter().enumerate() {
4495                    if i > 0 {
4496                        write!(f, ", ")?;
4497                    }
4498                    write!(f, "{}", v)?;
4499                }
4500                write!(f, "]")
4501            }
4502            // Wave-γ G-heap-filter-expr (ADR-006 §2.3 amendment): no
4503            // user-facing FilterExpr literal exists; render as an opaque
4504            // tag for diagnostics. Construction-side bug if a FilterExpr
4505            // ever escapes into a user-visible Display path.
4506            HeapValue::FilterExpr(_) => write!(f, "<filter_expr>"),
4507            // Wave 8 W8-T26 (ADR-006 §2.7.13 / Q14, 2026-05-10): no
4508            // user-facing reference literal exists; render as an opaque
4509            // tag for diagnostics. References are within-program data
4510            // and don't cross any user-visible Display surface.
4511            HeapValue::Reference(_) => write!(f, "<ref>"),
4512            // W13-iterator-state (ADR-006 §2.7.16 / Q17, 2026-05-10):
4513            // iterator pipelines have no user-facing literal — render
4514            // as an opaque tag. Terminal operations (collect / forEach
4515            // / reduce / etc.) materialise the elements; an iterator
4516            // reaching the Display surface is "still lazy" by
4517            // construction.
4518            HeapValue::Iterator(_) => write!(f, "<iterator>"),
4519            // Wave 15 W15-channel-rebuild (ADR-006 §2.7.20 / Q21,
4520            // 2026-05-10): channels are concurrency primitives with no
4521            // user-facing literal; render as an opaque tag annotated
4522            // with current queue length and closed flag for
4523            // diagnostics.
4524            HeapValue::Channel(c) => {
4525                let len = c.len();
4526                let state = if c.is_closed() { "closed" } else { "open" };
4527                write!(f, "<channel:{}:{}>", state, len)
4528            }
4529            // Wave 15 W15-priority-queue (ADR-006 §2.7.18 / Q19,
4530            // 2026-05-10): one-keyspace mirror of HashSet's Display
4531            // shape — bracketed comma-separated values in heap-array
4532            // order. NOTE: heap-array order is not sort-order; for
4533            // sorted output the user must call `pq.toSortedArray()`.
4534            HeapValue::PriorityQueue(d) => {
4535                write!(f, "PriorityQueue[")?;
4536                for (i, v) in d.heap.iter().enumerate() {
4537                    if i > 0 {
4538                        write!(f, ", ")?;
4539                    }
4540                    write!(f, "{}", v)?;
4541                }
4542                write!(f, "]")
4543            }
4544            // W15-range (ADR-006 §2.7.23 / Q24, 2026-05-10): user-visible
4545            // literal form — `start..end` for exclusive, `start..=end`
4546            // for inclusive. Step is not part of the surface syntax (no
4547            // explicit step suffix) so it's not rendered. This matches
4548            // the pre-strict-typing print format and the `0..10` /
4549            // `0..=10` source syntax round-trip.
4550            HeapValue::Range(r) => {
4551                if r.inclusive {
4552                    write!(f, "{}..={}", r.start, r.end)
4553                } else {
4554                    write!(f, "{}..{}", r.start, r.end)
4555                }
4556            }
4557            // Wave 14 W14-variant-codegen (ADR-006 §2.7.17 / Q18,
4558            // 2026-05-10): render as `Ok(<inner>)` / `Err(<inner>)` /
4559            // `Some(<inner>)` / `None`. The inner Display goes
4560            // through the runtime kinded value-formatter at the
4561            // VM-tier `printing.rs` site (the heap_value `Display`
4562            // impl is a fallback for diagnostic prints; rich
4563            // formatting goes through the executor's
4564            // `format_kinded`). Renders inner as `<…>` opaque tag
4565            // here — the kinded formatter handles full pretty-print.
4566            HeapValue::Result(r) => {
4567                if r.is_ok {
4568                    write!(f, "Ok(<...>)")
4569                } else {
4570                    write!(f, "Err(<...>)")
4571                }
4572            }
4573            HeapValue::Option(o) => {
4574                if o.is_some {
4575                    write!(f, "Some(<...>)")
4576                } else {
4577                    write!(f, "None")
4578                }
4579            }
4580            // W17-trait-object-storage (ADR-006 §2.7.24 / Q25.C,
4581            // 2026-05-11): `dyn Trait` carriers render as an opaque
4582            // tag annotated with the first trait name (multi-trait
4583            // inheritance shows just the first) and the inner schema
4584            // id of the boxed TypedObject. Pretty-printing via the
4585            // boxed receiver's user-defined `Display`-equivalent is
4586            // a compiler-emission tier concern (call the trait's
4587            // `.format(self)` method through the vtable); the
4588            // storage-tier formatter is diagnostic-only.
4589            HeapValue::TraitObject(t) => {
4590                let trait_name = t
4591                    .vtable
4592                    .trait_names
4593                    .first()
4594                    .map(|s| s.as_str())
4595                    .unwrap_or("?");
4596                // Wave 2 Round 4 D4 ckpt-3 (2026-05-14): `t.value` is
4597                // `*const TypedObjectStorage` (v2-raw shape) — field
4598                // access goes through an unsafe deref.
4599                // SAFETY: `t.value` is non-null per universal-dyn
4600                // construction; the `&HeapValue::TraitObject(t)` borrow
4601                // holds the carrier live for this scope.
4602                let inner_schema_id = unsafe { (*t.value).schema_id };
4603                write!(
4604                    f,
4605                    "<dyn {} #{}>",
4606                    trait_name, inner_schema_id
4607                )
4608            }
4609            // W17-concurrency (ADR-006 §2.7.25, 2026-05-11): concurrency
4610            // primitives have no user-facing literal — render as opaque
4611            // tags annotated with diagnostic state. Mirror of Channel's
4612            // `<channel:state:len>` shape.
4613            HeapValue::Mutex(_) => write!(f, "<mutex>"),
4614            HeapValue::Atomic(a) => write!(f, "<atomic:{}>", a.load()),
4615            HeapValue::Lazy(l) => {
4616                if l.is_initialized() {
4617                    write!(f, "<lazy:initialized>")
4618                } else {
4619                    write!(f, "<lazy:pending>")
4620                }
4621            }
4622            // W17-comptime-vm-dispatch (ADR-006 §2.7.26, 2026-05-12):
4623            // ModuleFn references render as `<module_fn:id>`.
4624            HeapValue::ModuleFn(id) => write!(f, "<module_fn:{}>", id),
4625            // ADR-006 §2.7.22 amendment (Round 18 S3, 2026-05-13):
4626            // Matrix renders as `<Mat<number>:rows x cols>`, mirroring the
4627            // pre-amendment `TypedArrayData::Matrix` Display shape.
4628            // MatrixSlice renders as a flat `Vec<number>[...]` over the
4629            // projection's element slice, mirroring the pre-amendment
4630            // `TypedArrayData::FloatSlice` Display shape. These Display
4631            // surfaces are diagnostic fallbacks; pretty-printing of
4632            // Matrix/MatrixSlice values goes through `printing.rs` at
4633            // the VM tier.
4634            HeapValue::Matrix(m) => {
4635                write!(f, "<Mat<number>:{}x{}>", m.rows, m.cols)
4636            }
4637            HeapValue::MatrixSlice(s) => {
4638                let slice = s.as_slice();
4639                write!(f, "Vec<number>[")?;
4640                for (i, v) in slice.iter().enumerate() {
4641                    if i > 0 {
4642                        write!(f, ", ")?;
4643                    }
4644                    if *v == v.trunc() && v.abs() < 1e15 {
4645                        write!(f, "{}", *v as i64)?;
4646                    } else {
4647                        write!(f, "{}", v)?;
4648                    }
4649                }
4650                write!(f, "]")
4651            }
4652        }
4653    }
4654}
4655
4656// ── Hand-written methods (complex per-variant logic) ────────────────────────
4657
4658impl HeapValue {
4659    /// Obtain a [`crate::vm_closure_handle::VmClosureHandle`] over this
4660    /// heap value, if it is a `HeapValue::ClosureRaw`.
4661    ///
4662    /// Closure spec §14.2: the handle is the stable read API for
4663    /// closure state. Returns `None` for non-closure heap values.
4664    #[inline]
4665    pub fn as_closure_handle(&self) -> Option<crate::vm_closure_handle::VmClosureHandle<'_>> {
4666        match self {
4667            HeapValue::ClosureRaw(owned) => {
4668                // SAFETY: `OwnedClosureBlock::from_raw` upholds that
4669                // `as_header_ptr()` points to a live `TypedClosureHeader`
4670                // whose layout matches `owned.layout()`; both remain valid
4671                // for the duration of the `&self` borrow.
4672                let handle = unsafe {
4673                    crate::vm_closure_handle::VmClosureHandle::raw(
4674                        owned.as_header_ptr(),
4675                        owned.layout().as_ref(),
4676                    )
4677                };
4678                Some(handle)
4679            }
4680            _ => None,
4681        }
4682    }
4683
4684    /// Structural equality comparison for HeapValue.
4685    ///
4686    /// ADR-006 §2.3: `TypedArray` and `Temporal` payloads are now
4687    /// `Arc<TypedArrayData>` / `Arc<TemporalData>`; the per-arm dispatch
4688    /// dereferences the Arc once at the outer match and forwards into the
4689    /// inner enum via `typed_array_structural_eq` / direct `match`.
4690    pub fn structural_eq(&self, other: &HeapValue) -> bool {
4691        match (self, other) {
4692            (HeapValue::Char(a), HeapValue::Char(b)) => a == b,
4693            (HeapValue::String(a), HeapValue::String(b)) => a == b,
4694            // Cross-type: Char from string indexing vs String literal
4695            (HeapValue::Char(c), HeapValue::String(s))
4696            | (HeapValue::String(s), HeapValue::Char(c)) => {
4697                let mut buf = [0u8; 4];
4698                let cs = c.encode_utf8(&mut buf);
4699                cs == s.as_str()
4700            }
4701            (HeapValue::Decimal(a), HeapValue::Decimal(b)) => a == b,
4702            (HeapValue::BigInt(a), HeapValue::BigInt(b)) => a == b,
4703            (HeapValue::NativeScalar(a), HeapValue::NativeScalar(b)) => a == b,
4704            (HeapValue::NativeView(a), HeapValue::NativeView(b)) => native_view_eq(a, b),
4705            (HeapValue::Future(a), HeapValue::Future(b)) => a == b,
4706            (HeapValue::Temporal(a), HeapValue::Temporal(b)) => match (a.as_ref(), b.as_ref()) {
4707                (TemporalData::DateTime(x), TemporalData::DateTime(y)) => x == y,
4708                _ => false,
4709            },
4710            (HeapValue::Content(a), HeapValue::Content(b)) => a == b,
4711            (HeapValue::Instant(a), HeapValue::Instant(b)) => a == b,
4712            (HeapValue::IoHandle(a), HeapValue::IoHandle(b)) => {
4713                std::sync::Arc::ptr_eq(&a.resource, &b.resource)
4714            }
4715            // V3-S5 ckpt-4 (2026-05-15): `(HeapValue::TypedArray(...), ...)`
4716            // structural-eq arm DELETED. The `HeapValue::TypedArray` outer
4717            // arm was retired in lockstep with this ckpt-4 +
4718            // `typed_array_structural_eq` was deleted at V3-S5 ckpt-1
4719            // (heap_value.rs wholesale deletion per W12 audit §3.5 + ADR-006
4720            // §2.7.24 Q25.A SUPERSEDED). Per-arm TypedArrayData structural
4721            // equality dispatch retires with the enum; no replacement (the
4722            // v2-raw `TypedArray<T>` flat struct compares element-wise via
4723            // its own `==` impl, not through `HeapValue::structural_eq`).
4724            // Refusal #1 binding.
4725            // ADR-006 §2.7.22 amendment (Round 18 S3, 2026-05-13): Matrix
4726            // equality is structural (rows + cols + element-wise compare);
4727            // MatrixSlice equality is element-wise over the projection slice
4728            // (parent identity is NOT required — two slices with identical
4729            // elements compare equal even when projecting from different
4730            // parents). Mirror of the pre-amendment TypedArrayData::Matrix /
4731            // FloatSlice equality semantics.
4732            (HeapValue::Matrix(a), HeapValue::Matrix(b)) => matrix_eq(a, b),
4733            (HeapValue::MatrixSlice(a), HeapValue::MatrixSlice(b)) => {
4734                a.as_slice() == b.as_slice()
4735            }
4736            _ => false,
4737        }
4738    }
4739
4740    /// Check equality between two heap values.
4741    #[inline]
4742    pub fn equals(&self, other: &HeapValue) -> bool {
4743        match (self, other) {
4744            (HeapValue::Char(a), HeapValue::Char(b)) => a == b,
4745            (HeapValue::String(a), HeapValue::String(b)) => a == b,
4746            // Cross-type: Char from string indexing vs String literal
4747            (HeapValue::Char(c), HeapValue::String(s))
4748            | (HeapValue::String(s), HeapValue::Char(c)) => {
4749                let mut buf = [0u8; 4];
4750                let cs = c.encode_utf8(&mut buf);
4751                cs == s.as_str()
4752            }
4753            (HeapValue::TypedObject(a), HeapValue::TypedObject(b)) => {
4754                // Wave 2 Round 4 D4 ckpt-final-prime² (2026-05-14): payloads are
4755                // `TypedObjectPtr` (raw `*const TypedObjectStorage`); pointer-
4756                // equality is the fast path for shared storage.
4757                if a.as_ptr() == b.as_ptr() {
4758                    return true;
4759                }
4760                if a.schema_id != b.schema_id
4761                    || a.slots.len() != b.slots.len()
4762                    || a.heap_mask != b.heap_mask
4763                {
4764                    return false;
4765                }
4766                for i in 0..a.slots.len() {
4767                    // Both heap-mask and primitive-mask: compare raw bits
4768                    // for primitives. For heap slots, raw-bit equality is
4769                    // also conservatively correct since `ValueSlot` heap
4770                    // payloads are typed pointers — pointer-equality
4771                    // implies value-equality for shared Arc'd payloads.
4772                    if a.slots[i].raw() != b.slots[i].raw() {
4773                        return false;
4774                    }
4775                }
4776                true
4777            }
4778            // Track A.5: the canonical closure variant compares by function id.
4779            (HeapValue::ClosureRaw(a), HeapValue::ClosureRaw(b)) => {
4780                // SAFETY: both blocks are live per OwnedClosureBlock invariant.
4781                let fa = unsafe { crate::v2::closure_raw::typed_closure_function_id(a.as_ptr()) };
4782                let fb = unsafe { crate::v2::closure_raw::typed_closure_function_id(b.as_ptr()) };
4783                fa == fb
4784            }
4785            (HeapValue::Decimal(a), HeapValue::Decimal(b)) => a == b,
4786            (HeapValue::BigInt(a), HeapValue::BigInt(b)) => a == b,
4787            (HeapValue::BigInt(a), HeapValue::Decimal(b)) => bigint_decimal_eq(a.as_ref(), b.as_ref()),
4788            (HeapValue::Decimal(a), HeapValue::BigInt(b)) => bigint_decimal_eq(b.as_ref(), a.as_ref()),
4789            (HeapValue::DataTable(a), HeapValue::DataTable(b)) => Arc::ptr_eq(a, b),
4790            (HeapValue::TableView(a), HeapValue::TableView(b)) => match (a.as_ref(), b.as_ref()) {
4791                (
4792                    TableViewData::TypedTable { schema_id: s1, table: t1 },
4793                    TableViewData::TypedTable { schema_id: s2, table: t2 },
4794                ) => s1 == s2 && Arc::ptr_eq(t1, t2),
4795                (
4796                    TableViewData::RowView { schema_id: s1, row_idx: r1, table: t1 },
4797                    TableViewData::RowView { schema_id: s2, row_idx: r2, table: t2 },
4798                ) => s1 == s2 && r1 == r2 && Arc::ptr_eq(t1, t2),
4799                (
4800                    TableViewData::ColumnRef { schema_id: s1, col_id: c1, table: t1 },
4801                    TableViewData::ColumnRef { schema_id: s2, col_id: c2, table: t2 },
4802                ) => s1 == s2 && c1 == c2 && Arc::ptr_eq(t1, t2),
4803                (
4804                    TableViewData::IndexedTable { schema_id: s1, index_col: c1, table: t1 },
4805                    TableViewData::IndexedTable { schema_id: s2, index_col: c2, table: t2 },
4806                ) => s1 == s2 && c1 == c2 && Arc::ptr_eq(t1, t2),
4807                _ => false,
4808            },
4809            (HeapValue::Content(a), HeapValue::Content(b)) => a == b,
4810            (HeapValue::Instant(a), HeapValue::Instant(b)) => a == b,
4811            (HeapValue::IoHandle(a), HeapValue::IoHandle(b)) => {
4812                Arc::ptr_eq(&a.resource, &b.resource)
4813            }
4814            (HeapValue::Future(a), HeapValue::Future(b)) => a == b,
4815            (HeapValue::Temporal(a), HeapValue::Temporal(b)) => match (a.as_ref(), b.as_ref()) {
4816                (TemporalData::DateTime(x), TemporalData::DateTime(y)) => x == y,
4817                (TemporalData::Duration(x), TemporalData::Duration(y)) => x == y,
4818                (TemporalData::TimeSpan(x), TemporalData::TimeSpan(y)) => x == y,
4819                (TemporalData::Timeframe(x), TemporalData::Timeframe(y)) => x == y,
4820                _ => false,
4821            },
4822            (HeapValue::NativeScalar(a), HeapValue::NativeScalar(b)) => a == b,
4823            (HeapValue::NativeView(a), HeapValue::NativeView(b)) => native_view_eq(a, b),
4824            // V3-S5 ckpt-4 (2026-05-15): `(HeapValue::TypedArray(...), ...)`
4825            // equals arm DELETED in lockstep with the structural_eq arm
4826            // above + the outer `HeapValue::TypedArray` variant +
4827            // `typed_array_structural_eq` (ckpt-1). W12 audit §3.5 +
4828            // ADR-006 §2.7.24 Q25.A SUPERSEDED. Refusal #1 binding.
4829            // ADR-006 §2.7.22 amendment (Round 18 S3, 2026-05-13): Matrix
4830            // and MatrixSlice equality match the structural_eq shape above.
4831            (HeapValue::Matrix(a), HeapValue::Matrix(b)) => matrix_eq(a, b),
4832            (HeapValue::MatrixSlice(a), HeapValue::MatrixSlice(b)) => {
4833                a.as_slice() == b.as_slice()
4834            }
4835            // Cross-type numeric
4836            (HeapValue::NativeScalar(a), HeapValue::BigInt(b)) => native_scalar_bigint_eq(a, b.as_ref()),
4837            (HeapValue::BigInt(a), HeapValue::NativeScalar(b)) => native_scalar_bigint_eq(b, a.as_ref()),
4838            (HeapValue::NativeScalar(a), HeapValue::Decimal(b)) => {
4839                native_scalar_decimal_eq(a, b.as_ref())
4840            }
4841            (HeapValue::Decimal(a), HeapValue::NativeScalar(b)) => {
4842                native_scalar_decimal_eq(b, a.as_ref())
4843            }
4844            _ => false,
4845        }
4846    }
4847}
4848
4849#[cfg(test)]
4850mod closure_variant_regression {
4851    //! N2 — pin Track A.5's deletion of the legacy `HeapValue::Closure`
4852    //! variant. After the Phase 2b HeapKind trim, the `Closure` ordinal
4853    //! is no longer pre-bulldozer-stable (it moved from 3 to 2 along
4854    //! with the rest of the trim), but the discriminator must still map
4855    //! to the `ClosureRaw` pipeline.
4856    use super::*;
4857
4858    #[test]
4859    fn heap_kind_closure_routes_to_closure_raw() {
4860        // The Closure HeapKind discriminator is what HeapValue::ClosureRaw
4861        // returns from `kind()`; verify the routing is intact.
4862        // (The numeric ordinal is structural — see heap_variants.rs — and
4863        // not load-bearing for any external consumer per the Phase 2b
4864        // audit.)
4865        let _ = HeapKind::Closure;
4866    }
4867}
4868
4869#[cfg(test)]
4870mod typed_object_storage_drop {
4871    //! ADR-006 §2.5 / Step 5: pin the Drop impl's behaviour on
4872    //! `TypedObjectStorage`. The contract tested:
4873    //!
4874    //! 1. Heap-mask bits cause `Arc::decrement_strong_count::<T>` for the
4875    //!    matching `field_kinds[i]` payload type.
4876    //! 2. Non-heap slots (heap_mask bit clear) are no-ops — even with
4877    //!    non-zero raw bits (those bits are scalar field contents, not
4878    //!    typed pointers).
4879    //! 3. `field_kinds` itself is shared via Arc — multiple instances of
4880    //!    the same schema share one `[NativeKind]` allocation.
4881    use super::*;
4882    use crate::native_kind::NativeKind;
4883    use crate::slot::ValueSlot;
4884    use std::sync::Arc;
4885
4886    #[test]
4887    fn drop_decrements_arc_string_for_heap_string_slot() {
4888        let s: Arc<String> = Arc::new("phase-1a".to_string());
4889        // Hold a second strong ref so the test can observe the count drop.
4890        let witness = Arc::clone(&s);
4891        assert_eq!(Arc::strong_count(&witness), 2);
4892
4893        let slot = ValueSlot::from_string_arc(s);
4894        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::String]);
4895        let storage = TypedObjectStorage::new(
4896            42,
4897            vec![slot].into_boxed_slice(),
4898            0b1, // bit 0 set
4899            kinds,
4900        );
4901
4902        // Construction stored the Arc raw pointer; nothing dropped yet.
4903        assert_eq!(Arc::strong_count(&witness), 2);
4904
4905        drop(storage);
4906
4907        // Drop walked heap_mask, dispatched on NativeKind::String, and
4908        // released the slot's strong count via Arc::decrement_strong_count.
4909        assert_eq!(Arc::strong_count(&witness), 1);
4910    }
4911
4912    #[test]
4913    fn drop_is_noop_for_non_heap_slot_with_non_zero_bits() {
4914        // Non-heap slot — heap_mask bit clear. Raw bits are an i64 value;
4915        // Drop must not interpret them as a pointer.
4916        let slot = ValueSlot::from_int(0x1234_5678);
4917        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
4918        let storage = TypedObjectStorage::new(
4919            7,
4920            vec![slot].into_boxed_slice(),
4921            0, // no heap bits
4922            kinds,
4923        );
4924        // Just dropping the storage must not crash / dereference the bits.
4925        drop(storage);
4926    }
4927
4928    #[test]
4929    fn drop_skips_zero_pointer_slots() {
4930        // Heap-mask bit set but the slot was zeroed (e.g. moved-out) —
4931        // Drop must not call Arc::decrement_strong_count on null.
4932        let slot = ValueSlot::from_raw(0);
4933        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::String]);
4934        let storage = TypedObjectStorage::new(
4935            9,
4936            vec![slot].into_boxed_slice(),
4937            0b1,
4938            kinds,
4939        );
4940        drop(storage);
4941    }
4942
4943    #[test]
4944    fn field_kinds_arc_is_shared_across_instances() {
4945        // Option B' invariant: two instances of the same schema clone the
4946        // same Arc<[NativeKind]> (one payload allocation per schema).
4947        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64, NativeKind::Bool]);
4948        let kinds_count_before = Arc::strong_count(&kinds);
4949
4950        let storage_a = TypedObjectStorage::new(
4951            1,
4952            vec![ValueSlot::from_int(0), ValueSlot::from_bool(true)].into_boxed_slice(),
4953            0,
4954            Arc::clone(&kinds),
4955        );
4956        let storage_b = TypedObjectStorage::new(
4957            1,
4958            vec![ValueSlot::from_int(1), ValueSlot::from_bool(false)].into_boxed_slice(),
4959            0,
4960            Arc::clone(&kinds),
4961        );
4962
4963        // Two instances + the test's own handle = +2 over the baseline.
4964        assert_eq!(Arc::strong_count(&kinds), kinds_count_before + 2);
4965
4966        // Both instances point at the same payload (no per-instance copy).
4967        assert!(Arc::ptr_eq(&storage_a.field_kinds, &storage_b.field_kinds));
4968        assert!(Arc::ptr_eq(&storage_a.field_kinds, &kinds));
4969
4970        drop(storage_a);
4971        drop(storage_b);
4972        // Each Drop released its share; only the test's handle remains.
4973        assert_eq!(Arc::strong_count(&kinds), kinds_count_before);
4974    }
4975
4976    #[test]
4977    fn drop_handles_mixed_heap_and_scalar_fields() {
4978        // Realistic shape: int + string + bool. Only the string slot
4979        // participates in refcount; the int/bool slots are scalar bits.
4980        let s: Arc<String> = Arc::new("mixed".to_string());
4981        let witness = Arc::clone(&s);
4982        assert_eq!(Arc::strong_count(&witness), 2);
4983
4984        let slots = vec![
4985            ValueSlot::from_int(99),
4986            ValueSlot::from_string_arc(s),
4987            ValueSlot::from_bool(true),
4988        ]
4989        .into_boxed_slice();
4990        let kinds: Arc<[NativeKind]> = Arc::from(vec![
4991            NativeKind::Int64,
4992            NativeKind::String,
4993            NativeKind::Bool,
4994        ]);
4995        let storage = TypedObjectStorage::new(
4996            13,
4997            slots,
4998            0b010, // only bit 1 (the string) is heap
4999            kinds,
5000        );
5001
5002        drop(storage);
5003        assert_eq!(Arc::strong_count(&witness), 1);
5004    }
5005
5006    #[test]
5007    fn drop_decrements_arc_typed_object_for_heap_pointer_slot() {
5008        // Nested TypedObject: outer storage holds a v2-raw
5009        // `*const TypedObjectStorage` in slot 0 via
5010        // `NativeKind::Ptr(HeapKind::TypedObject)`.
5011        //
5012        // W5 v0.3 fix (2026-05-17): migrated the inner construction to
5013        // the v2-raw `_new` allocator per
5014        // `executor/objects/property_access.rs::length_typed_object_empty`
5015        // rationale. The previous shape stored `ValueSlot::from_typed_object(
5016        // Arc<TypedObjectStorage>)` bits in a slot whose field_kinds entry
5017        // was `Ptr(HeapKind::TypedObject)`. The outer storage's
5018        // `drop_fields` dispatch on that entry calls
5019        // `TypedObjectStorage::release_elem` → `v2_release` → `_drop` →
5020        // `std::alloc::dealloc(ptr, Layout::new::<TypedObjectStorage>)`
5021        // on the Arc-allocated inner. That's a wrong-allocator-pair
5022        // free (Arc layout has `ArcInner` header before `T`) → SIGABRT.
5023        //
5024        // The witness probe is rewritten to the v2-raw equivalent: peek
5025        // the header refcount via raw pointer borrow.
5026        use std::sync::atomic::Ordering;
5027
5028        let inner_kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5029        // SAFETY: `_new` returns a refcount=1 raw pointer; the outer
5030        // storage takes that share, and `drop(outer)` runs drop_fields
5031        // which releases it via release_elem → _drop.
5032        unsafe {
5033            let inner_ptr = TypedObjectStorage::_new(
5034                100,
5035                vec![ValueSlot::from_int(7)].into_boxed_slice(),
5036                0,
5037                inner_kinds,
5038            );
5039            // Bump the inner refcount once so we have a witness share
5040            // that observes the outer's release. After outer drop the
5041            // refcount should be back to 1 (our witness only).
5042            crate::v2::refcount::v2_retain(&(*inner_ptr).header);
5043            assert_eq!((*inner_ptr).header.refcount.load(Ordering::SeqCst), 2);
5044
5045            let outer_kinds: Arc<[NativeKind]> =
5046                Arc::from(vec![NativeKind::Ptr(HeapKind::TypedObject)]);
5047            let outer = TypedObjectStorage::new(
5048                101,
5049                vec![ValueSlot::from_typed_object_raw(inner_ptr)].into_boxed_slice(),
5050                0b1,
5051                outer_kinds,
5052            );
5053
5054            drop(outer);
5055            assert_eq!((*inner_ptr).header.refcount.load(Ordering::SeqCst), 1);
5056
5057            // Witness cleanup: retire the share we minted above via
5058            // `release_elem` (refcount=1→0 → `_drop` runs internally) so
5059            // the inner allocation is freed and Miri reports no leak.
5060            use crate::v2::heap_element::HeapElement;
5061            TypedObjectStorage::release_elem(inner_ptr);
5062        }
5063    }
5064
5065    // ── Wave 2 Agent D1 v2-raw HeapHeader-equipped shape change tests ──────────
5066
5067    #[test]
5068    fn header_initializes_with_typed_object_kind_and_refcount_one() {
5069        // Wave 2 Agent D1: confirm `TypedObjectStorage::new` initializes the
5070        // HeapHeader at offset 0 with HEAP_KIND_V2_TYPED_OBJECT and refcount=1.
5071        // Used by Arc<TypedObjectStorage>-path callers (the legacy path); the
5072        // header's refcount sits unused for the Arc lifetime.
5073        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5074        let storage = TypedObjectStorage::new(
5075            1,
5076            vec![ValueSlot::from_int(0)].into_boxed_slice(),
5077            0,
5078            kinds,
5079        );
5080        assert_eq!(
5081            storage.header.kind(),
5082            crate::v2::heap_header::HEAP_KIND_V2_TYPED_OBJECT,
5083        );
5084        assert_eq!(storage.header.get_refcount(), 1);
5085    }
5086
5087    #[test]
5088    fn v2_raw_new_and_drop_round_trip() {
5089        // Wave 2 Agent D1: confirm the v2-raw allocator + deallocator pair
5090        // (`_new` + `_drop`) round-trips a simple scalar-only TypedObjectStorage
5091        // without leaking. Mirror of DecimalObj::test_drop_does_not_leak —
5092        // Miri / valgrind validate no leak.
5093        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5094        unsafe {
5095            let ptr = TypedObjectStorage::_new(
5096                7,
5097                vec![ValueSlot::from_int(42)].into_boxed_slice(),
5098                0,
5099                kinds,
5100            );
5101            assert!(!ptr.is_null());
5102            assert_eq!(
5103                (*ptr).header.kind(),
5104                crate::v2::heap_header::HEAP_KIND_V2_TYPED_OBJECT,
5105            );
5106            assert_eq!((*ptr).header.get_refcount(), 1);
5107            assert_eq!((*ptr).schema_id, 7);
5108            assert_eq!((&(*ptr).slots).len(), 1);
5109            assert_eq!((&(*ptr).slots)[0].as_i64(), 42);
5110            TypedObjectStorage::_drop(ptr);
5111            // ptr is dangling; cannot dereference further.
5112        }
5113    }
5114
5115    #[test]
5116    fn v2_raw_new_releases_string_share_at_drop() {
5117        // Wave 2 Agent D1: confirm `_drop` runs the heap-mask field walk
5118        // (mirror of `impl Drop`'s legacy behaviour) and retires one Arc
5119        // strong-count share per heap-kinded slot.
5120        let s: Arc<String> = Arc::new("v2-raw-test".to_string());
5121        let witness = Arc::clone(&s);
5122        assert_eq!(Arc::strong_count(&witness), 2);
5123
5124        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::String]);
5125        unsafe {
5126            let ptr = TypedObjectStorage::_new(
5127                17,
5128                vec![ValueSlot::from_string_arc(s)].into_boxed_slice(),
5129                0b1,
5130                kinds,
5131            );
5132            assert_eq!(Arc::strong_count(&witness), 2);
5133            TypedObjectStorage::_drop(ptr);
5134        }
5135        assert_eq!(Arc::strong_count(&witness), 1);
5136    }
5137
5138    #[test]
5139    fn heap_element_release_elem_deallocates_at_refcount_zero() {
5140        // Wave 2 Agent D1: confirm `HeapElement::release_elem` decrements
5141        // via `v2_release` and deallocates at refcount=0. Mirror of
5142        // DecimalObj::test_heap_element_release_elem_to_zero.
5143        use crate::v2::heap_element::HeapElement;
5144        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5145        unsafe {
5146            let ptr = TypedObjectStorage::_new(
5147                3,
5148                vec![ValueSlot::from_int(0)].into_boxed_slice(),
5149                0,
5150                kinds,
5151            );
5152            // refcount=1; release_elem deallocates.
5153            TypedObjectStorage::release_elem(ptr);
5154            // ptr is dangling; valgrind / Miri confirms no leak.
5155        }
5156    }
5157
5158    #[test]
5159    fn heap_element_release_elem_preserves_held_share() {
5160        // Wave 2 Agent D1: confirm `release_elem` decrements but does NOT
5161        // deallocate when refcount > 1. Mirror of
5162        // DecimalObj::test_heap_element_release_elem_held_share.
5163        use crate::v2::heap_element::HeapElement;
5164        use crate::v2::refcount::{v2_get_refcount, v2_retain};
5165        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5166        unsafe {
5167            let ptr = TypedObjectStorage::_new(
5168                5,
5169                vec![ValueSlot::from_int(7)].into_boxed_slice(),
5170                0,
5171                kinds,
5172            );
5173            let header = &(*ptr).header as *const crate::v2::heap_header::HeapHeader;
5174
5175            v2_retain(header); // refcount = 2
5176            TypedObjectStorage::release_elem(ptr); // refcount = 1 (does not deallocate)
5177            assert_eq!(v2_get_refcount(header), 1);
5178
5179            // Clean up the held share.
5180            TypedObjectStorage::_drop(ptr);
5181        }
5182    }
5183
5184    #[test]
5185    fn header_field_is_at_offset_zero() {
5186        // Wave 2 Agent D1: confirm the #[repr(C)] field-order invariant —
5187        // the `header: HeapHeader` field sits at offset 0 of
5188        // TypedObjectStorage. This is the precondition for the
5189        // HeapElement::release_elem body's `v2_release(&(*ptr).header)` call
5190        // to read the refcount at the v2-raw canonical offset (offset 0
5191        // mirrors StringObj / DecimalObj precedents).
5192        let kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
5193        let storage = TypedObjectStorage::new(
5194            1,
5195            vec![ValueSlot::from_int(0)].into_boxed_slice(),
5196            0,
5197            kinds,
5198        );
5199        let base = &storage as *const _ as usize;
5200        let header_offset = &storage.header as *const _ as usize - base;
5201        assert_eq!(header_offset, 0, "header must be at offset 0 (#[repr(C)] contract)");
5202    }
5203}
5204
5205// Wave 2 Round 3b C2-joint ckpt-3 (2026-05-14): rewritten against the
5206// per-V `HashMapData<V>` mutation API (insert / remove / get_share /
5207// merge). The pre-Q25.B-SUPERSEDED non-generic `HashMapData::insert(k,
5208// Arc<HeapValue>) / remove(k) -> bool / get(k) -> Option<Arc<HeapValue>>`
5209// shape is gone; tests below exercise the per-V semantics on the most
5210// common production-V cases:
5211//
5212// - `V = i64` (Q25.B I64 arm): POD/Copy V — pin len/contains/insert/remove
5213//   semantics without refcount-share complications.
5214// - `V = *const StringObj` (Q25.B String arm): HeapElement V — pin the
5215//   v2_retain / release_elem refcount-share threading via the
5216//   `HashMapValueElem::share_clone` + `release_owned` dispatch.
5217//
5218// The previously-introduced undeclared feature gate (Round 3b ckpt-2)
5219// guarding this test module has been REMOVED per ckpt-3 dispatch
5220// Group F (mandatory non-negotiable): the gate was masquerading as a
5221// feature flag while being functionally `#[cfg(false)]` (no Cargo.toml
5222// declaration), matching CLAUDE.md Forbidden Rationalizations.
5223// ADR-006 §2.7.24 Q25.B SUPERSEDED + audit §C.4 option (a.2).
5224#[cfg(test)]
5225mod hashmap_mutation {
5226    //! Wave 2 Round 3b C2-joint ckpt-3 (2026-05-14): pin the
5227    //! `insert` / `remove` / `get_share` / `merge` API contracts on the
5228    //! post-Q25.B-SUPERSEDED `HashMapData<V>`. Storage-layer counterpart
5229    //! of `v2_set` / `v2_delete` / `v2_get` / `v2_merge` in
5230    //! `shape-vm/executor/objects/hashmap_methods.rs`.
5231    use super::*;
5232    use crate::v2::refcount::v2_get_refcount;
5233    use crate::v2::string_obj::StringObj;
5234    use std::sync::Arc;
5235
5236    // ── V = i64 (POD/Copy) ──────────────────────────────────────────────
5237
5238    #[test]
5239    fn i64_insert_appends_new_entry_and_grows_index() {
5240        let mut m: HashMapData<i64> = HashMapData::new();
5241        unsafe {
5242            assert!(m.insert("a", 1));
5243            assert!(m.insert("b", 2));
5244        }
5245        assert_eq!(m.len(), 2);
5246        // Bucket index has registrations for both keys' hashes.
5247        let h_a = fnv1a_hash(b"a");
5248        let h_b = fnv1a_hash(b"b");
5249        assert!(m.index.get(&h_a).is_some());
5250        assert!(m.index.get(&h_b).is_some());
5251    }
5252
5253    #[test]
5254    fn i64_insert_overwrites_existing_value_and_keeps_len() {
5255        let mut m: HashMapData<i64> = HashMapData::new();
5256        unsafe {
5257            assert!(m.insert("a", 1));
5258            // Overwrite returns false (existing key).
5259            assert!(!m.insert("a", 99));
5260        }
5261        assert_eq!(m.len(), 1);
5262        // get_share returns a fresh Copy of the i64 value.
5263        assert_eq!(m.get_share("a"), Some(99));
5264    }
5265
5266    #[test]
5267    fn i64_remove_present_key_returns_value_and_compacts() {
5268        let mut m: HashMapData<i64> = HashMapData::new();
5269        unsafe {
5270            m.insert("a", 1);
5271            m.insert("b", 2);
5272            assert_eq!(m.remove("a"), Some(1));
5273        }
5274        assert_eq!(m.len(), 1);
5275        assert!(m.get_share("a").is_none());
5276        // "b" should still be reachable — bucket index was renumbered.
5277        assert_eq!(m.get_share("b"), Some(2));
5278    }
5279
5280    #[test]
5281    fn i64_remove_missing_key_returns_none_and_is_noop() {
5282        let mut m: HashMapData<i64> = HashMapData::new();
5283        unsafe {
5284            m.insert("a", 1);
5285            assert_eq!(m.remove("nope"), None);
5286        }
5287        assert_eq!(m.len(), 1);
5288        assert_eq!(m.get_share("a"), Some(1));
5289    }
5290
5291    #[test]
5292    fn i64_merge_copies_other_entries_with_last_write_wins() {
5293        let mut a: HashMapData<i64> = HashMapData::new();
5294        let mut b: HashMapData<i64> = HashMapData::new();
5295        unsafe {
5296            a.insert("x", 1);
5297            a.insert("shared", 10);
5298            b.insert("y", 2);
5299            b.insert("shared", 99);
5300            a.merge(&b);
5301        }
5302        assert_eq!(a.len(), 3);
5303        assert_eq!(a.get_share("x"), Some(1));
5304        assert_eq!(a.get_share("y"), Some(2));
5305        // shared overwritten by b's value
5306        assert_eq!(a.get_share("shared"), Some(99));
5307    }
5308
5309    #[test]
5310    fn i64_smoke_set_set_delete_size() {
5311        // Storage-layer counterpart of W13-hashmap-mutation smoke:
5312        //   let m = HashMap(); m.set("a", 1); m.set("b", 2); m.delete("a");
5313        //   m.size() == 1
5314        let mut m: HashMapData<i64> = HashMapData::new();
5315        unsafe {
5316            m.insert("a", 1);
5317            m.insert("b", 2);
5318            assert_eq!(m.remove("a"), Some(1));
5319        }
5320        assert_eq!(m.len(), 1);
5321        assert!(m.get_share("a").is_none());
5322        assert_eq!(m.get_share("b"), Some(2));
5323    }
5324
5325    #[test]
5326    fn i64_arc_make_mut_clone_on_write_does_not_disturb_shared_handle() {
5327        // The shape-vm-side handlers Arc::make_mut the receiver share —
5328        // this exercises the per-V Clone impl which allocates fresh
5329        // keys + values buffers and share-clones each element.
5330        let mut owned: Arc<HashMapData<i64>> = Arc::new(HashMapData::new());
5331        unsafe { Arc::make_mut(&mut owned).insert("a", 1) };
5332        // Snapshot share — second observer.
5333        let snapshot = Arc::clone(&owned);
5334        // Mutate via the local share — should clone-on-write.
5335        unsafe { Arc::make_mut(&mut owned).insert("b", 2) };
5336        assert_eq!(owned.len(), 2);
5337        // Snapshot is undisturbed.
5338        assert_eq!(snapshot.len(), 1);
5339        assert_eq!(snapshot.get_share("a"), Some(1));
5340        assert!(snapshot.get_share("b").is_none());
5341    }
5342
5343    // ── V = *const StringObj (HeapElement) ──────────────────────────────
5344
5345    fn s_obj(s: &str) -> *const StringObj {
5346        StringObj::new(s) as *const StringObj
5347    }
5348
5349    #[test]
5350    fn string_insert_v2_retain_share_threading() {
5351        // Pin: insert transfers one share to the map; the original share
5352        // we keep here is the "witness" that survives.
5353        let mut m: HashMapData<*const StringObj> = HashMapData::new();
5354        let v1 = s_obj("hello");
5355        // Bump witness share so we can observe the map's share separately
5356        // — refcount=2 after retain.
5357        unsafe { crate::v2::refcount::v2_retain(&(*v1).header) };
5358        assert_eq!(unsafe { v2_get_refcount(&(*v1).header) }, 2);
5359        // Insert (transfers one share to the map).
5360        unsafe { m.insert("k", v1) };
5361        // Refcount: witness share + map share = 2 (unchanged because
5362        // we transferred 1 to the map, leaving 1 with the witness).
5363        assert_eq!(unsafe { v2_get_refcount(&(*v1).header) }, 2);
5364        // Map drops at end — retires its share. Manually drop to observe.
5365        drop(m);
5366        assert_eq!(unsafe { v2_get_refcount(&(*v1).header) }, 1);
5367        // Release the witness share.
5368        unsafe {
5369            use crate::v2::heap_element::HeapElement;
5370            StringObj::release_elem(v1);
5371        }
5372    }
5373
5374    #[test]
5375    fn string_insert_overwrite_retires_old_share() {
5376        // Pin: insert with existing key retires the old value's share via
5377        // V::release_owned.
5378        let mut m: HashMapData<*const StringObj> = HashMapData::new();
5379        let old = s_obj("old");
5380        let witness = old;
5381        unsafe { crate::v2::refcount::v2_retain(&(*old).header) }; // witness = 2
5382        unsafe { m.insert("k", old) };
5383        assert_eq!(unsafe { v2_get_refcount(&(*witness).header) }, 2);
5384        // Overwrite with new value; the old share inside the map is retired.
5385        let new_val = s_obj("new");
5386        unsafe { m.insert("k", new_val) };
5387        // Map no longer holds the original value's share — witness alone.
5388        assert_eq!(unsafe { v2_get_refcount(&(*witness).header) }, 1);
5389        // Release witness.
5390        unsafe {
5391            use crate::v2::heap_element::HeapElement;
5392            StringObj::release_elem(witness);
5393        }
5394        // m drops new_val + key allocations naturally at scope end.
5395    }
5396
5397    #[test]
5398    fn string_remove_transfers_share_to_caller() {
5399        // Pin: remove returns the value, transferring its share to the caller.
5400        let mut m: HashMapData<*const StringObj> = HashMapData::new();
5401        let v = s_obj("val");
5402        unsafe { crate::v2::refcount::v2_retain(&(*v).header) }; // witness shares = 2
5403        unsafe { m.insert("k", v) };
5404        assert_eq!(unsafe { v2_get_refcount(&(*v).header) }, 2);
5405        let removed = unsafe { m.remove("k") };
5406        assert!(removed.is_some());
5407        let removed_ptr = removed.unwrap();
5408        assert_eq!(removed_ptr, v);
5409        // Refcount unchanged: map released its share + remove transferred
5410        // a share to the caller (this fn) = net 0 change.
5411        assert_eq!(unsafe { v2_get_refcount(&(*v).header) }, 2);
5412        // Release the witness + the removed share.
5413        unsafe {
5414            use crate::v2::heap_element::HeapElement;
5415            StringObj::release_elem(removed_ptr);
5416            StringObj::release_elem(v);
5417        }
5418    }
5419
5420    #[test]
5421    fn string_get_share_bumps_refcount_for_caller() {
5422        // Pin: get_share returns a fresh refcount-share copy, leaving
5423        // the map's share intact.
5424        let mut m: HashMapData<*const StringObj> = HashMapData::new();
5425        let v = s_obj("val");
5426        unsafe { m.insert("k", v) }; // map owns the only share now.
5427        assert_eq!(unsafe { v2_get_refcount(&(*v).header) }, 1);
5428        let got = m.get_share("k").expect("present");
5429        assert_eq!(got, v);
5430        // Refcount bumped — map (1) + caller's share (1) = 2.
5431        assert_eq!(unsafe { v2_get_refcount(&(*v).header) }, 2);
5432        // Release caller's share.
5433        unsafe {
5434            use crate::v2::heap_element::HeapElement;
5435            StringObj::release_elem(got);
5436        }
5437        assert_eq!(unsafe { v2_get_refcount(&(*v).header) }, 1);
5438        // Map drops at scope end, retiring last share.
5439    }
5440
5441    #[test]
5442    fn string_merge_share_clones_each_other_entry() {
5443        // Pin: merge bumps refcount on each value cloned from other.
5444        let mut a: HashMapData<*const StringObj> = HashMapData::new();
5445        let mut b: HashMapData<*const StringObj> = HashMapData::new();
5446        let v_x = s_obj("x_val");
5447        let v_y = s_obj("y_val");
5448        unsafe {
5449            a.insert("x", v_x);
5450            b.insert("y", v_y);
5451            assert_eq!(v2_get_refcount(&(*v_x).header), 1);
5452            assert_eq!(v2_get_refcount(&(*v_y).header), 1);
5453            a.merge(&b);
5454        }
5455        assert_eq!(a.len(), 2);
5456        // After merge: y is share-cloned into a — refcount = 2 (a + b).
5457        assert_eq!(unsafe { v2_get_refcount(&(*v_y).header) }, 2);
5458        // x is unchanged (only in a).
5459        assert_eq!(unsafe { v2_get_refcount(&(*v_x).header) }, 1);
5460    }
5461
5462    // ── V = HashMapKindedRef (recursive carrier) ──────────────────────────
5463    //
5464    // Wave N hashmap-value-v-arm follow-up (cluster-2 closure-wave-C,
5465    // 2026-05-16). Pin the per-V `insert` / `len` / `get_share` API on
5466    // `HashMapData<HashMapKindedRef>`. Storage-layer counterpart of
5467    // `v2_group_by` in `shape-vm/executor/objects/hashmap_methods.rs`.
5468
5469    #[test]
5470    fn hashmap_value_v_insert_appends_and_grows_index() {
5471        let mut outer: HashMapData<HashMapKindedRef> = HashMapData::new();
5472        // Two inner buckets — one for "small", one for "large".
5473        let mut inner_small: HashMapData<i64> = HashMapData::new();
5474        let mut inner_large: HashMapData<i64> = HashMapData::new();
5475        unsafe {
5476            inner_small.insert("a", 1);
5477            inner_small.insert("b", 2);
5478            inner_large.insert("c", 100);
5479            outer.insert(
5480                "small",
5481                HashMapKindedRef::I64(Arc::new(inner_small)),
5482            );
5483            outer.insert(
5484                "large",
5485                HashMapKindedRef::I64(Arc::new(inner_large)),
5486            );
5487        }
5488        assert_eq!(outer.len(), 2);
5489        // Bucket index has registrations for both group keys.
5490        let h_small = fnv1a_hash(b"small");
5491        let h_large = fnv1a_hash(b"large");
5492        assert!(outer.index.get(&h_small).is_some());
5493        assert!(outer.index.get(&h_large).is_some());
5494        // get_share returns a fresh per-variant Arc::clone-bumped copy.
5495        let small_ref = outer.get_share("small").expect("small bucket present");
5496        match small_ref {
5497            HashMapKindedRef::I64(arc) => assert_eq!(arc.len(), 2),
5498            other => panic!("unexpected variant {:?}", other.values_kind()),
5499        }
5500    }
5501
5502    #[test]
5503    fn hashmap_value_v_remove_returns_inner_and_compacts() {
5504        let mut outer: HashMapData<HashMapKindedRef> = HashMapData::new();
5505        let mut inner_a: HashMapData<i64> = HashMapData::new();
5506        let mut inner_b: HashMapData<i64> = HashMapData::new();
5507        unsafe {
5508            inner_a.insert("x", 1);
5509            inner_b.insert("y", 2);
5510            outer.insert("group-a", HashMapKindedRef::I64(Arc::new(inner_a)));
5511            outer.insert("group-b", HashMapKindedRef::I64(Arc::new(inner_b)));
5512            let removed = outer.remove("group-a");
5513            assert!(removed.is_some());
5514            // Removed bucket has the expected inner shape.
5515            match removed.unwrap() {
5516                HashMapKindedRef::I64(arc) => {
5517                    assert_eq!(arc.len(), 1);
5518                    assert_eq!(arc.get_share("x"), Some(1));
5519                }
5520                other => panic!("unexpected variant {:?}", other.values_kind()),
5521            }
5522        }
5523        assert_eq!(outer.len(), 1);
5524        // "group-b" reachable post-renumber.
5525        let b_ref = outer.get_share("group-b").expect("group-b present");
5526        match b_ref {
5527            HashMapKindedRef::I64(arc) => assert_eq!(arc.get_share("y"), Some(2)),
5528            other => panic!("unexpected variant {:?}", other.values_kind()),
5529        }
5530    }
5531
5532    #[test]
5533    fn hashmap_value_v_clone_share_clones_inner_arcs() {
5534        // HashMapData<V>::Clone walks elements via share_clone — for
5535        // V = HashMapKindedRef this calls HashMapKindedRef::clone which
5536        // is per-variant Arc::clone on the inner Arc<HashMapData<V_inner>>.
5537        // The clone yields fresh buffer allocations holding bumped Arcs.
5538        let mut outer: HashMapData<HashMapKindedRef> = HashMapData::new();
5539        let inner: Arc<HashMapData<i64>> = {
5540            let mut d: HashMapData<i64> = HashMapData::new();
5541            unsafe { d.insert("k", 42) };
5542            Arc::new(d)
5543        };
5544        // Refcount before insert: 1 (only `inner` owns).
5545        assert_eq!(Arc::strong_count(&inner), 1);
5546        unsafe { outer.insert("g", HashMapKindedRef::I64(Arc::clone(&inner))) };
5547        // Refcount after insert: 2 (inner + outer's buffer share).
5548        assert_eq!(Arc::strong_count(&inner), 2);
5549        // Clone outer — share_clone bumps the inner Arc one more time.
5550        let _outer_clone = outer.clone();
5551        assert_eq!(Arc::strong_count(&inner), 3);
5552        // Drop clone; refcount drops back to 2.
5553        drop(_outer_clone);
5554        assert_eq!(Arc::strong_count(&inner), 2);
5555    }
5556
5557    #[test]
5558    fn hashmap_value_v_drop_releases_inner_arcs() {
5559        // HashMapData<HashMapKindedRef>::Drop calls
5560        // <HashMapKindedRef as HashMapValueElem>::release_typed_array,
5561        // which walks the buffer with ptr::read and lets each element
5562        // drop (auto-derived → Arc::drop on inner Arc<HashMapData<V_inner>>).
5563        let inner: Arc<HashMapData<i64>> = {
5564            let mut d: HashMapData<i64> = HashMapData::new();
5565            unsafe { d.insert("k", 7) };
5566            Arc::new(d)
5567        };
5568        assert_eq!(Arc::strong_count(&inner), 1);
5569        {
5570            let mut outer: HashMapData<HashMapKindedRef> = HashMapData::new();
5571            unsafe {
5572                outer.insert("g", HashMapKindedRef::I64(Arc::clone(&inner)));
5573            }
5574            assert_eq!(Arc::strong_count(&inner), 2);
5575            // outer drops at scope-end; the inner Arc share retires.
5576        }
5577        assert_eq!(Arc::strong_count(&inner), 1);
5578    }
5579}
5580
5581#[cfg(test)]
5582mod hashset_mutation {
5583    //! W13-hashset-rebuild (ADR-006 §2.7.15 / Q16, 2026-05-10): pin the
5584    //! `insert` / `remove` / `contains` API contracts on `HashSetData`.
5585    //! Mirror of `hashmap_mutation` with the values column dropped.
5586    use super::*;
5587    use std::sync::Arc;
5588    fn k(s: &str) -> Arc<String> {
5589        Arc::new(s.to_string())
5590    }
5591
5592    #[test]
5593    fn empty_set_has_zero_len_and_is_empty() {
5594        let s = HashSetData::new();
5595        assert_eq!(s.len(), 0);
5596        assert!(s.is_empty());
5597        assert!(!s.contains("a"));
5598    }
5599
5600    #[test]
5601    fn insert_returns_true_for_new_key_false_for_duplicate() {
5602        let mut s = HashSetData::new();
5603        assert!(s.insert(k("a")));
5604        assert!(s.insert(k("b")));
5605        assert_eq!(s.len(), 2);
5606        // Duplicate insert is a no-op.
5607        assert!(!s.insert(k("a")));
5608        assert_eq!(s.len(), 2);
5609    }
5610
5611    #[test]
5612    fn contains_finds_inserted_keys() {
5613        let mut s = HashSetData::new();
5614        s.insert(k("a"));
5615        s.insert(k("b"));
5616        assert!(s.contains("a"));
5617        assert!(s.contains("b"));
5618        assert!(!s.contains("c"));
5619    }
5620
5621    #[test]
5622    fn remove_returns_true_for_present_false_for_missing() {
5623        let mut s = HashSetData::new();
5624        s.insert(k("a"));
5625        s.insert(k("b"));
5626        assert!(s.remove("a"));
5627        assert!(!s.contains("a"));
5628        assert!(s.contains("b"));
5629        assert_eq!(s.len(), 1);
5630        // Missing-key remove is a no-op.
5631        assert!(!s.remove("c"));
5632        assert_eq!(s.len(), 1);
5633    }
5634
5635    #[test]
5636    fn from_keys_collapses_duplicates_first_wins() {
5637        let s = HashSetData::from_keys(vec![k("a"), k("b"), k("a"), k("c")]);
5638        assert_eq!(s.len(), 3);
5639        assert_eq!(s.keys[0].as_str(), "a");
5640        assert_eq!(s.keys[1].as_str(), "b");
5641        assert_eq!(s.keys[2].as_str(), "c");
5642    }
5643
5644    #[test]
5645    fn smoke_target_add_two_then_size() {
5646        // Storage-layer counterpart of the W13-hashset-rebuild smoke
5647        // target: `let s = Set(); s.add("a"); s.add("b"); print(s.size())`
5648        // outputs 2.
5649        let mut s = HashSetData::new();
5650        s.insert(k("a"));
5651        s.insert(k("b"));
5652        assert_eq!(s.len(), 2);
5653    }
5654
5655    #[test]
5656    fn arc_make_mut_clone_on_write_preserves_other_share() {
5657        // Pin the §2.7.4 / playbook clone-on-write invariant: when
5658        // `Arc<HashSetData>` has multiple shares, `Arc::make_mut`
5659        // clones the inner `HashSetData` so the other share stays
5660        // immutable. Mirror of `hashmap_mutation`'s clone-on-write
5661        // test.
5662        let mut a = Arc::new(HashSetData::new());
5663        Arc::make_mut(&mut a).insert(k("a"));
5664        let snapshot = Arc::clone(&a);
5665        // After the snapshot, mutating `a` clones the inner data.
5666        Arc::make_mut(&mut a).insert(k("b"));
5667        assert_eq!(a.len(), 2);
5668        // Snapshot retains the pre-mutation length.
5669        assert_eq!(snapshot.len(), 1);
5670        assert!(snapshot.contains("a"));
5671        assert!(!snapshot.contains("b"));
5672    }
5673}
5674
5675#[cfg(test)]
5676mod deque_mutation {
5677    //! W15-deque (ADR-006 §2.7.19 / Q20, 2026-05-10): pin the
5678    //! `push_front` / `push_back` / `pop_front` / `pop_back` API
5679    //! contracts on `DequeData`. Mirror of `hashset_mutation` with
5680    //! the bucket-index dropped (Deque is order-preserving with no
5681    //! deduplication, so no parallel hash structure is needed).
5682    use super::*;
5683    use std::sync::Arc;
5684
5685    fn s(text: &str) -> Arc<HeapValue> {
5686        Arc::new(HeapValue::String(Arc::new(text.to_string())))
5687    }
5688
5689    fn i(n: i64) -> Arc<HeapValue> {
5690        Arc::new(HeapValue::BigInt(Arc::new(n)))
5691    }
5692
5693    #[test]
5694    fn empty_deque_has_zero_len_and_is_empty() {
5695        let d = DequeData::new();
5696        assert_eq!(d.len(), 0);
5697        assert!(d.is_empty());
5698        assert!(d.peek_front().is_none());
5699        assert!(d.peek_back().is_none());
5700        assert!(d.get(0).is_none());
5701    }
5702
5703    #[test]
5704    fn push_back_and_pop_front_preserve_fifo_order() {
5705        // FIFO: push 1,2,3 to back, pop from front yields 1,2,3.
5706        let mut d = DequeData::new();
5707        d.push_back(i(1));
5708        d.push_back(i(2));
5709        d.push_back(i(3));
5710        assert_eq!(d.len(), 3);
5711        let p1 = d.pop_front().expect("front");
5712        assert!(matches!(p1.as_ref(), HeapValue::BigInt(b) if **b == 1));
5713        let p2 = d.pop_front().expect("front");
5714        assert!(matches!(p2.as_ref(), HeapValue::BigInt(b) if **b == 2));
5715        let p3 = d.pop_front().expect("front");
5716        assert!(matches!(p3.as_ref(), HeapValue::BigInt(b) if **b == 3));
5717        assert!(d.pop_front().is_none());
5718    }
5719
5720    #[test]
5721    fn push_front_and_pop_back_preserve_reverse_order() {
5722        // Reverse: push 1,2,3 to front, pop from back yields 1,2,3.
5723        let mut d = DequeData::new();
5724        d.push_front(i(1));
5725        d.push_front(i(2));
5726        d.push_front(i(3));
5727        // Now layout is [3, 2, 1] front-to-back.
5728        let p1 = d.pop_back().expect("back");
5729        assert!(matches!(p1.as_ref(), HeapValue::BigInt(b) if **b == 1));
5730        let p2 = d.pop_back().expect("back");
5731        assert!(matches!(p2.as_ref(), HeapValue::BigInt(b) if **b == 2));
5732        let p3 = d.pop_back().expect("back");
5733        assert!(matches!(p3.as_ref(), HeapValue::BigInt(b) if **b == 3));
5734    }
5735
5736    #[test]
5737    fn smoke_target_push_back_push_front_pop_back() {
5738        // Storage-layer counterpart of the W15-deque smoke target:
5739        // `let d = Deque(); d.push_back(1); d.push_front(0); d.pop_back()`
5740        // returns `1`. After the two pushes the layout is [0, 1]
5741        // front-to-back; pop_back yields 1.
5742        let mut d = DequeData::new();
5743        d.push_back(i(1));
5744        d.push_front(i(0));
5745        assert_eq!(d.len(), 2);
5746        let popped = d.pop_back().expect("back");
5747        assert!(matches!(popped.as_ref(), HeapValue::BigInt(b) if **b == 1));
5748        // Front element retained.
5749        assert_eq!(d.len(), 1);
5750        let front = d.peek_front().expect("front").clone();
5751        assert!(matches!(front.as_ref(), HeapValue::BigInt(b) if **b == 0));
5752    }
5753
5754    #[test]
5755    fn peek_front_back_and_get_borrow_without_removing() {
5756        let mut d = DequeData::new();
5757        d.push_back(s("a"));
5758        d.push_back(s("b"));
5759        d.push_back(s("c"));
5760        assert_eq!(d.len(), 3);
5761        let front = d.peek_front().expect("front");
5762        assert!(matches!(front.as_ref(), HeapValue::String(t) if t.as_str() == "a"));
5763        let back = d.peek_back().expect("back");
5764        assert!(matches!(back.as_ref(), HeapValue::String(t) if t.as_str() == "c"));
5765        let mid = d.get(1).expect("idx 1");
5766        assert!(matches!(mid.as_ref(), HeapValue::String(t) if t.as_str() == "b"));
5767        // Length unchanged after read-only borrows.
5768        assert_eq!(d.len(), 3);
5769    }
5770
5771    #[test]
5772    fn from_items_preserves_insertion_order() {
5773        let d = DequeData::from_items(vec![s("a"), s("b"), s("c")]);
5774        assert_eq!(d.len(), 3);
5775        assert!(matches!(d.get(0).unwrap().as_ref(), HeapValue::String(t) if t.as_str() == "a"));
5776        assert!(matches!(d.get(1).unwrap().as_ref(), HeapValue::String(t) if t.as_str() == "b"));
5777        assert!(matches!(d.get(2).unwrap().as_ref(), HeapValue::String(t) if t.as_str() == "c"));
5778    }
5779
5780    #[test]
5781    fn arc_make_mut_clone_on_write_preserves_other_share() {
5782        // Pin the §2.7.4 / playbook clone-on-write invariant: when
5783        // `Arc<DequeData>` has multiple shares, `Arc::make_mut` clones
5784        // the inner `DequeData` so the other share stays immutable.
5785        // Mirror of `hashset_mutation`'s clone-on-write test.
5786        let mut a = Arc::new(DequeData::new());
5787        Arc::make_mut(&mut a).push_back(i(1));
5788        let snapshot = Arc::clone(&a);
5789        // After the snapshot, mutating `a` clones the inner data.
5790        Arc::make_mut(&mut a).push_back(i(2));
5791        assert_eq!(a.len(), 2);
5792        // Snapshot retains the pre-mutation length.
5793        assert_eq!(snapshot.len(), 1);
5794    }
5795}
5796
5797mod priority_queue_mutation {
5798    //! W15-priority-queue (ADR-006 §2.7.18 / Q19, 2026-05-10): pin the
5799    //! `push` / `pop` / `peek` / heap-invariant API contracts on
5800    //! `PriorityQueueData`. Mirror of `hashset_mutation` for the
5801    //! cardinality-amendment shape, with i64-priority-only payload
5802    //! semantics per the §2.7.18 ruling.
5803    use super::*;
5804    use std::sync::Arc;
5805
5806    #[test]
5807    fn empty_pq_has_zero_len_and_is_empty() {
5808        let pq = PriorityQueueData::new();
5809        assert_eq!(pq.len(), 0);
5810        assert!(pq.is_empty());
5811        assert_eq!(pq.peek(), None);
5812    }
5813
5814    #[test]
5815    fn push_increases_len_and_pop_returns_min() {
5816        // Storage-layer counterpart of the W15-priority-queue smoke
5817        // target: `pq.push(3); pq.push(1); pq.push(2); pq.pop() == 1`.
5818        let mut pq = PriorityQueueData::new();
5819        pq.push(3);
5820        pq.push(1);
5821        pq.push(2);
5822        assert_eq!(pq.len(), 3);
5823        assert_eq!(pq.peek(), Some(1));
5824        assert_eq!(pq.pop(), Some(1));
5825        assert_eq!(pq.len(), 2);
5826    }
5827
5828    #[test]
5829    fn pop_returns_none_on_empty() {
5830        let mut pq = PriorityQueueData::new();
5831        assert_eq!(pq.pop(), None);
5832    }
5833
5834    #[test]
5835    fn pop_yields_ascending_order() {
5836        // Pin the min-heap invariant: repeated `pop()` yields keys
5837        // in ascending order regardless of insertion order.
5838        let mut pq = PriorityQueueData::new();
5839        for v in [5, 3, 7, 1, 9, 4, 2, 8, 6] {
5840            pq.push(v);
5841        }
5842        let mut out = Vec::new();
5843        while let Some(v) = pq.pop() {
5844            out.push(v);
5845        }
5846        assert_eq!(out, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
5847    }
5848
5849    #[test]
5850    fn to_sorted_vec_returns_ascending_without_consuming() {
5851        let mut pq = PriorityQueueData::new();
5852        for v in [3, 1, 4, 1, 5, 9, 2, 6] {
5853            pq.push(v);
5854        }
5855        let sorted = pq.to_sorted_vec();
5856        assert_eq!(sorted, vec![1, 1, 2, 3, 4, 5, 6, 9]);
5857        // Original PQ is undisturbed.
5858        assert_eq!(pq.len(), 8);
5859    }
5860
5861}
5862
5863mod channel_storage {
5864    //! W15-channel-rebuild (ADR-006 §2.7.20 / Q21, 2026-05-10): pin the
5865    //! `send` / `try_recv` / `close` / `is_closed` / `len` / `is_empty`
5866    //! API contracts on `ChannelData`. Sync same-thread path only —
5867    //! cross-task blocking `recv()` is the §2.7.4 task-scheduler
5868    //! boundary tracked separately.
5869    use super::*;
5870    use crate::kinded_slot::KindedSlot;
5871    use std::sync::Arc;
5872
5873    #[test]
5874    fn empty_channel_has_zero_len_and_is_empty_open() {
5875        let c = ChannelData::new();
5876        assert_eq!(c.len(), 0);
5877        assert!(c.is_empty());
5878        assert!(!c.is_closed());
5879        assert!(c.try_recv().is_none());
5880    }
5881
5882    #[test]
5883    fn send_then_try_recv_round_trips_int() {
5884        // Storage-layer counterpart of the W15-channel-rebuild smoke
5885        // target: `let c = Channel(); c.send(1); c.recv()` returns 1.
5886        let c = ChannelData::new();
5887        c.send(KindedSlot::from_int(1)).expect("send on open channel");
5888        let got = c.try_recv().expect("queued element");
5889        assert_eq!(got.as_i64(), Some(1));
5890        assert!(c.is_empty());
5891    }
5892
5893    #[test]
5894    fn fifo_send_recv_order() {
5895        // Producer pushes 1, 2, 3; consumer drains in the same order.
5896        let c = ChannelData::new();
5897        c.send(KindedSlot::from_int(1)).unwrap();
5898        c.send(KindedSlot::from_int(2)).unwrap();
5899        c.send(KindedSlot::from_int(3)).unwrap();
5900        assert_eq!(c.len(), 3);
5901        assert_eq!(c.try_recv().unwrap().as_i64(), Some(1));
5902        assert_eq!(c.try_recv().unwrap().as_i64(), Some(2));
5903        assert_eq!(c.try_recv().unwrap().as_i64(), Some(3));
5904        assert!(c.try_recv().is_none());
5905    }
5906
5907    #[test]
5908    fn close_blocks_further_sends_but_drains_queued() {
5909        // After close, send returns Err but queued elements still
5910        // recv cleanly (canonical drain-on-close semantics).
5911        let c = ChannelData::new();
5912        c.send(KindedSlot::from_int(7)).unwrap();
5913        c.close();
5914        assert!(c.is_closed());
5915        // Further send is rejected; the rejected slot is dropped
5916        // (refcount discipline preserved through KindedSlot::Drop).
5917        assert!(c.send(KindedSlot::from_int(8)).is_err());
5918        // Queued element still drains.
5919        assert_eq!(c.try_recv().unwrap().as_i64(), Some(7));
5920        assert!(c.try_recv().is_none());
5921    }
5922
5923    #[test]
5924    fn shared_arc_send_recv_observes_other_share() {
5925        // Two `Arc<ChannelData>` shares of the same channel observe
5926        // each other's mutations — the producer/consumer-endpoints
5927        // shape. Distinct from HashSet/HashMap (which are Arc::make_mut
5928        // clone-on-write) — Channel uses interior mutability via
5929        // Mutex.
5930        let producer = Arc::new(ChannelData::new());
5931        let consumer = Arc::clone(&producer);
5932        producer.send(KindedSlot::from_int(42)).unwrap();
5933        assert_eq!(consumer.len(), 1);
5934        let got = consumer.try_recv().unwrap();
5935        assert_eq!(got.as_i64(), Some(42));
5936        // After consumer drained, producer-side observes empty.
5937        assert!(producer.is_empty());
5938    }
5939
5940    #[test]
5941    fn dropping_channel_with_heap_payloads_retires_shares() {
5942        // Refcount discipline: KindedSlot payloads queued in the
5943        // channel own one strong-count share; dropping the channel
5944        // (last `Arc<ChannelData>` share retired) must drop each
5945        // queued slot and retire its inner share. Any Arc-leak would
5946        // surface as a non-zero strong-count after the channel
5947        // dropped.
5948        let s = Arc::new("payload".to_string());
5949        let weak = Arc::downgrade(&s);
5950        let c = ChannelData::new();
5951        c.send(KindedSlot::from_string_arc(s)).unwrap();
5952        // The queued slot owns the only strong share.
5953        assert_eq!(weak.strong_count(), 1);
5954        drop(c);
5955        assert_eq!(
5956            weak.strong_count(),
5957            0,
5958            "dropped Channel must retire queued KindedSlot shares"
5959        );
5960    }
5961
5962    #[test]
5963    fn closed_send_drops_rejected_payload_share() {
5964        // After close, a rejected send must NOT leak the payload
5965        // share — KindedSlot::Drop runs on the rejected slot.
5966        let c = ChannelData::new();
5967        c.close();
5968        let s = Arc::new("rejected".to_string());
5969        let weak = Arc::downgrade(&s);
5970        let slot = KindedSlot::from_string_arc(s);
5971        assert_eq!(weak.strong_count(), 1);
5972        // The rejected send consumes the slot and drops it internally.
5973        assert!(c.send(slot).is_err());
5974        assert_eq!(
5975            weak.strong_count(),
5976            0,
5977            "rejected-send slot must drop, not leak"
5978        );
5979    }
5980}
5981
5982// ── W14-variant-codegen unit tests (ADR-006 §2.7.17 / Q18) ──────────────────
5983
5984#[cfg(test)]
5985mod result_option_storage {
5986    //! W14-variant-codegen (ADR-006 §2.7.17 / Q18, 2026-05-10): pin the
5987    //! `ResultData::ok` / `err` and `OptionData::some` / `none` API
5988    //! contracts.
5989    use super::*;
5990    use crate::kinded_slot::KindedSlot;
5991    use std::sync::Arc;
5992
5993    #[test]
5994    fn ok_carrier_is_ok_true() {
5995        let payload = KindedSlot::from_int(42);
5996        let r = ResultData::ok(payload);
5997        assert!(r.is_ok);
5998        // Payload kind preserved.
5999        assert_eq!(r.payload.as_i64(), Some(42));
6000    }
6001
6002    #[test]
6003    fn err_carrier_is_ok_false() {
6004        let payload = KindedSlot::from_string_arc(Arc::new("oops".to_string()));
6005        let r = ResultData::err(payload);
6006        assert!(!r.is_ok);
6007        // Payload string preserved.
6008        assert_eq!(r.payload.as_str(), Some("oops"));
6009    }
6010
6011    #[test]
6012    fn result_clone_bumps_payload_share() {
6013        // The storage carrier's Clone path goes through KindedSlot's
6014        // explicit Clone impl, which dispatches retain on the payload's
6015        // kind. Verify that cloning the wrapper preserves the payload's
6016        // Arc identity (the inner Arc<String> share is bumped, not
6017        // duplicated).
6018        let payload_arc = Arc::new("hello".to_string());
6019        let payload = KindedSlot::from_string_arc(Arc::clone(&payload_arc));
6020        let r1 = ResultData::ok(payload);
6021        let r2 = r1.clone();
6022        // Pointer equality on the inner String: both wrappers
6023        // reference the same Arc<String> (the kinded clone bumped
6024        // the share, did not deep-copy the string body).
6025        assert_eq!(r1.payload.as_str(), Some("hello"));
6026        assert_eq!(r2.payload.as_str(), Some("hello"));
6027        // Original Arc retains an extra share from each KindedSlot
6028        // (1 own + 2 wrappers = 3 strong refs; the local `payload_arc`
6029        // is the third).
6030        assert!(Arc::strong_count(&payload_arc) >= 2);
6031    }
6032
6033    #[test]
6034    fn some_carrier_is_some_true() {
6035        let payload = KindedSlot::from_bool(true);
6036        let o = OptionData::some(payload);
6037        assert!(o.is_some);
6038        assert_eq!(o.payload.as_bool(), Some(true));
6039    }
6040
6041    #[test]
6042    fn none_carrier_is_some_false() {
6043        let o = OptionData::none();
6044        assert!(!o.is_some);
6045        // None payload is a placeholder Bool-kind zero-bits slot —
6046        // KindedSlot::Drop is a no-op on it (verified by the slot
6047        // raw bits and kind).
6048        assert_eq!(o.payload.slot().raw(), 0);
6049    }
6050
6051    #[test]
6052    fn smoke_target_ok_int_then_unwrap() {
6053        // Storage-layer counterpart of the W14-variant-codegen smoke
6054        // target: `let r = Ok(42); if r.is_ok() { print(r.unwrap_ok()) }`
6055        // outputs 42. The is_ok / unwrap_ok pair surfaces at the
6056        // storage tier as `r.is_ok` + `r.payload.as_i64()`.
6057        let r = ResultData::ok(KindedSlot::from_int(42));
6058        assert!(r.is_ok);
6059        assert_eq!(r.payload.as_i64(), Some(42));
6060    }
6061
6062    #[test]
6063    fn arc_wrap_typed_pointer_round_trip() {
6064        // Pin the typed-Arc raw-pointer dispatch contract: wrap an
6065        // Arc<ResultData> via Arc::into_raw, recover via
6066        // Arc::from_raw, verify pointer identity. This is the slot-
6067        // bits transit path that the §2.7.17 dispatch tables retire
6068        // in `clone_with_kind` / `drop_with_kind`.
6069        let arc = Arc::new(ResultData::ok(KindedSlot::from_int(7)));
6070        let bits = Arc::into_raw(arc) as u64;
6071        // Recover and verify is_ok.
6072        let arc2: Arc<ResultData> =
6073            unsafe { Arc::from_raw(bits as *const ResultData) };
6074        assert!(arc2.is_ok);
6075        assert_eq!(arc2.payload.as_i64(), Some(7));
6076        drop(arc2);
6077    }
6078}
6079
6080#[cfg(test)]
6081mod concurrency_storage {
6082    //! W17-concurrency (ADR-006 §2.7.25, 2026-05-11): pin the `lock`
6083    //! / `try_lock` / `set` / `get` API contracts on `MutexData`, the
6084    //! `load` / `store` / `fetch_add` / `fetch_sub` /
6085    //! `compare_exchange` contracts on `AtomicData`, and the
6086    //! `is_initialized` / `cached` / `take_initializer` /
6087    //! `store_result` contracts on `LazyData`. Storage-tier only —
6088    //! closure-call integration for `Lazy.get` lives at the handler
6089    //! tier (`executor/objects/concurrency_methods.rs`).
6090    use super::*;
6091    use crate::kinded_slot::KindedSlot;
6092    use std::sync::Arc;
6093
6094    // ── MutexData ──────────────────────────────────────────────────
6095
6096    #[test]
6097    fn mutex_new_holds_initial_value() {
6098        let m = MutexData::new(KindedSlot::from_int(42));
6099        assert_eq!(m.get().as_i64(), Some(42));
6100    }
6101
6102    #[test]
6103    fn mutex_lock_is_noop_at_landing() {
6104        let m = MutexData::new(KindedSlot::from_int(0));
6105        m.lock();
6106        // lock returns; observable state unchanged.
6107        assert_eq!(m.get().as_i64(), Some(0));
6108    }
6109
6110    #[test]
6111    fn mutex_try_lock_returns_true_uncontended() {
6112        let m = MutexData::new(KindedSlot::from_int(0));
6113        assert!(m.try_lock());
6114    }
6115
6116    #[test]
6117    fn mutex_set_replaces_value_and_drops_prior() {
6118        // Storage-layer counterpart of the smoke target's
6119        // `m.set(5); print(m.value)`.
6120        let m = MutexData::new(KindedSlot::from_int(0));
6121        m.set(KindedSlot::from_int(5));
6122        assert_eq!(m.get().as_i64(), Some(5));
6123    }
6124
6125    #[test]
6126    fn mutex_set_with_heap_payload_retires_shares() {
6127        // The prior slot drops cleanly when `set` replaces it; no
6128        // Arc-leak. A heap-bearing payload's strong-count returns to
6129        // zero after `set` and `drop(mutex)`.
6130        let s = Arc::new("initial".to_string());
6131        let weak = Arc::downgrade(&s);
6132        let m = MutexData::new(KindedSlot::from_string_arc(s));
6133        assert_eq!(weak.strong_count(), 1);
6134        m.set(KindedSlot::from_int(7));
6135        assert_eq!(
6136            weak.strong_count(),
6137            0,
6138            "Mutex.set must drop prior heap payload share"
6139        );
6140        drop(m);
6141    }
6142
6143    #[test]
6144    fn mutex_shared_arc_observes_set_mutations() {
6145        // Two `Arc<MutexData>` shares of the same mutex observe each
6146        // other's mutations — the producer/consumer-endpoints shape
6147        // (mirror of Channel).
6148        let m1 = Arc::new(MutexData::new(KindedSlot::from_int(0)));
6149        let m2 = Arc::clone(&m1);
6150        m1.set(KindedSlot::from_int(99));
6151        assert_eq!(m2.get().as_i64(), Some(99));
6152    }
6153
6154    // ── AtomicData ─────────────────────────────────────────────────
6155
6156    #[test]
6157    fn atomic_new_holds_initial_value() {
6158        let a = AtomicData::new(7);
6159        assert_eq!(a.load(), 7);
6160    }
6161
6162    #[test]
6163    fn atomic_store_replaces_value() {
6164        let a = AtomicData::new(0);
6165        a.store(42);
6166        assert_eq!(a.load(), 42);
6167    }
6168
6169    #[test]
6170    fn atomic_fetch_add_returns_prior_and_increments() {
6171        // Smoke-target storage layer: a starts at 0, fetch_add(1)
6172        // returns 0 (prior), load() returns 1.
6173        let a = AtomicData::new(0);
6174        let prior = a.fetch_add(1);
6175        assert_eq!(prior, 0);
6176        assert_eq!(a.load(), 1);
6177    }
6178
6179    #[test]
6180    fn atomic_fetch_sub_returns_prior_and_decrements() {
6181        let a = AtomicData::new(10);
6182        let prior = a.fetch_sub(3);
6183        assert_eq!(prior, 10);
6184        assert_eq!(a.load(), 7);
6185    }
6186
6187    #[test]
6188    fn atomic_compare_exchange_swaps_on_match() {
6189        let a = AtomicData::new(5);
6190        let prior = a.compare_exchange(5, 99);
6191        assert_eq!(prior, 5);
6192        assert_eq!(a.load(), 99);
6193    }
6194
6195    #[test]
6196    fn atomic_compare_exchange_keeps_on_mismatch() {
6197        let a = AtomicData::new(5);
6198        let prior = a.compare_exchange(7, 99);
6199        assert_eq!(prior, 5);
6200        assert_eq!(a.load(), 5);
6201    }
6202
6203    #[test]
6204    fn atomic_shared_arc_observes_other_share() {
6205        let a1 = Arc::new(AtomicData::new(0));
6206        let a2 = Arc::clone(&a1);
6207        a1.store(42);
6208        assert_eq!(a2.load(), 42);
6209        a2.fetch_add(8);
6210        assert_eq!(a1.load(), 50);
6211    }
6212
6213    // ── LazyData ───────────────────────────────────────────────────
6214
6215    #[test]
6216    fn lazy_new_is_not_initialized() {
6217        let dummy_closure = KindedSlot::from_int(0);
6218        // Note: at the storage tier we don't actually call the
6219        // closure — that lives at the handler tier. The closure
6220        // payload here is just any `KindedSlot`; `is_initialized`
6221        // looks at the cached value, not the initializer.
6222        let l = LazyData::new(dummy_closure);
6223        assert!(!l.is_initialized());
6224    }
6225
6226    #[test]
6227    fn lazy_take_initializer_then_store_result_marks_initialized() {
6228        // Simulates the handler-tier `lazy.get()` flow: take the
6229        // initializer, "run it" (the test substitutes a result), then
6230        // cache the result. After store_result, is_initialized=true
6231        // and cached() returns the stored value.
6232        let l = LazyData::new(KindedSlot::from_int(0));
6233        let init = l
6234            .take_initializer()
6235            .expect("initializer present before first get");
6236        assert!(!l.is_initialized());
6237        // "Run the initializer" — at storage-tier test we just drop
6238        // the initializer slot and synthesize a result.
6239        drop(init);
6240        l.store_result(KindedSlot::from_int(42));
6241        assert!(l.is_initialized());
6242        let got = l.cached().expect("cached after store_result");
6243        assert_eq!(got.as_i64(), Some(42));
6244    }
6245
6246    #[test]
6247    fn lazy_take_initializer_returns_none_after_caching() {
6248        // After cache is populated, `take_initializer` returns None
6249        // — the handler tier's get() uses this to detect "already
6250        // initialized, use cached() instead".
6251        let l = LazyData::new(KindedSlot::from_int(0));
6252        let _init = l.take_initializer().unwrap();
6253        l.store_result(KindedSlot::from_int(7));
6254        assert!(l.take_initializer().is_none());
6255    }
6256
6257    #[test]
6258    fn lazy_cached_returns_none_before_init() {
6259        let l = LazyData::new(KindedSlot::from_int(0));
6260        assert!(l.cached().is_none());
6261    }
6262
6263    #[test]
6264    fn lazy_dropping_lazy_with_heap_payload_retires_shares() {
6265        // Refcount discipline: the cached `KindedSlot` owns one
6266        // strong-count share; dropping the LazyData retires it.
6267        let s = Arc::new("cached_value".to_string());
6268        let weak = Arc::downgrade(&s);
6269        let l = LazyData::new(KindedSlot::from_int(0));
6270        l.store_result(KindedSlot::from_string_arc(s));
6271        assert_eq!(weak.strong_count(), 1);
6272        drop(l);
6273        assert_eq!(
6274            weak.strong_count(),
6275            0,
6276            "Dropped LazyData must retire cached KindedSlot's share"
6277        );
6278    }
6279}
6280
6281// Wave 2 Round 4 D4 ckpt-3 (2026-05-14): the `trait_object_storage` test mod
6282// was authored against `TraitObjectStorage.value: Arc<TypedObjectStorage>`
6283// + `make_object()` returning `Arc<TypedObjectStorage>`. Post inner-field
6284// shift to `*const TypedObjectStorage`, every test that does `Arc::clone(&obj)`
6285// or `Arc::downgrade(&obj)` no longer compiles, and every test that observes
6286// the inner refcount via the weak count needs to migrate to HeapHeader
6287// `get_refcount()` inspection. Ckpt-final adapts the tests in lockstep with
6288// the full HeapValue::TypedObject variant signature flip; intermediate
6289// close gate (broken cargo check OK per
6290// `docs/cluster-audits/bulldozer-multi-session-chain-pattern.md` §Discipline
6291// relaxed) preserves the test mod source verbatim under a never-match cfg
6292// so the ckpt-final adapter has the original assertions as the migration
6293// target.
6294#[cfg(any())]
6295mod trait_object_storage {
6296    //! W17-trait-object-storage (ADR-006 §2.7.24 / Q25.C, 2026-05-11):
6297    //! pin the `TraitObjectStorage` API + refcount-discipline contracts.
6298    //! Storage-tier only — `OpCode::BoxTraitObject` /
6299    //! `OpCode::DynMethodCall` emission and end-to-end dyn-coerce smoke
6300    //! live in W17-trait-object-emission (round 2 of Wave 2.6).
6301    //!
6302    //! Coverage:
6303    //!   - Construction (`TraitObjectStorage::new`)
6304    //!   - vtable / value field access
6305    //!   - `method()` lookup
6306    //!   - `vtable_eq()` identity contract per §Q25.C.2
6307    //!   - Clone bumps both inner Arc strong counts
6308    //!   - Drop retires both inner Arc strong counts
6309    //!   - `KindedSlot::from_trait_object` retain-on-clone parity
6310    //!   - `KindedSlot::from_trait_object` drop-decrement parity
6311    //!   - `Arc<TraitObjectStorage>` clone roundtrip (via clone_with_kind
6312    //!     contract through the kind label, not through HeapValue)
6313    //!   - End-to-end retain/drop balance over multiple clones
6314    use super::*;
6315    use crate::kinded_slot::KindedSlot;
6316    use crate::native_kind::NativeKind;
6317    use crate::value::{VTable, VTableEntry};
6318    use std::collections::HashMap;
6319    use std::sync::Arc;
6320
6321    /// Build a minimal `TypedObjectStorage` for tests — single i64 field,
6322    /// no heap-typed slots. Mirror of the shape used by concurrency
6323    /// tests' `KindedSlot::from_int` payloads.
6324    ///
6325    /// **Wave 2 Round 4 D4 ckpt-3 (2026-05-14): returns `*mut
6326    /// TypedObjectStorage` (v2-raw `_new` shape)** per the
6327    /// `TraitObjectStorage.value: *const TypedObjectStorage` inner-field
6328    /// shift. Tests that previously observed inner refcount via
6329    /// `Arc::downgrade(&obj)` are surfaced as broken pending test-side
6330    /// migration to HeapHeader refcount inspection
6331    /// (`unsafe { (*ptr).header.get_refcount() }`).
6332    fn make_object(value: i64) -> *mut TypedObjectStorage {
6333        let mut slots: Vec<crate::slot::ValueSlot> = Vec::with_capacity(1);
6334        slots.push(crate::slot::ValueSlot::from_int(value));
6335        let field_kinds: Arc<[NativeKind]> = Arc::from(vec![NativeKind::Int64]);
6336        TypedObjectStorage::_new(
6337            42, // schema_id — arbitrary
6338            slots.into_boxed_slice(),
6339            0,  // heap_mask: no heap slots
6340            field_kinds,
6341        )
6342    }
6343
6344    /// Build a minimal `VTable` for tests — one `Direct` method entry.
6345    fn make_vtable(trait_name: &str, concrete_type_id: u32, method: &str) -> Arc<VTable> {
6346        let mut methods: HashMap<String, VTableEntry> = HashMap::new();
6347        methods.insert(
6348            method.to_string(),
6349            VTableEntry::Direct { function_id: 7 },
6350        );
6351        Arc::new(VTable {
6352            trait_names: vec![trait_name.to_string()],
6353            concrete_type_id,
6354            methods,
6355        })
6356    }
6357
6358    #[test]
6359    fn new_holds_value_and_vtable_arcs() {
6360        let obj = make_object(1);
6361        let vt = make_vtable("Animal", 100, "name");
6362        let storage = TraitObjectStorage::new(Arc::clone(&obj), Arc::clone(&vt));
6363        // Both halves remain accessible; vtable's concrete_type_id
6364        // matches what the impl declared.
6365        assert_eq!(storage.value.schema_id, 42);
6366        assert_eq!(storage.vtable.concrete_type_id, 100);
6367        assert_eq!(storage.vtable.trait_names[0], "Animal");
6368    }
6369
6370    #[test]
6371    fn method_lookup_returns_entry_for_known_method() {
6372        let obj = make_object(1);
6373        let vt = make_vtable("Animal", 100, "name");
6374        let storage = TraitObjectStorage::new(obj, vt);
6375        let entry = storage
6376            .method("name")
6377            .expect("known method present in vtable");
6378        match entry {
6379            VTableEntry::Direct { function_id } => assert_eq!(*function_id, 7),
6380            _ => panic!("expected Direct entry"),
6381        }
6382    }
6383
6384    #[test]
6385    fn method_lookup_returns_none_for_unknown_method() {
6386        let obj = make_object(1);
6387        let vt = make_vtable("Animal", 100, "name");
6388        let storage = TraitObjectStorage::new(obj, vt);
6389        assert!(storage.method("speak").is_none());
6390    }
6391
6392    #[test]
6393    fn vtable_eq_identifies_same_vtable_share() {
6394        // Per §Q25.C.2, vtable-identity check uses `Arc::ptr_eq`. Two
6395        // carriers built from the same vtable Arc compare equal.
6396        let obj1 = make_object(1);
6397        let obj2 = make_object(2);
6398        let vt = make_vtable("Animal", 100, "name");
6399        let s1 = TraitObjectStorage::new(obj1, Arc::clone(&vt));
6400        let s2 = TraitObjectStorage::new(obj2, Arc::clone(&vt));
6401        assert!(s1.vtable_eq(&s2));
6402    }
6403
6404    #[test]
6405    fn vtable_eq_rejects_distinct_vtables() {
6406        // Two carriers built from distinct vtables — even if the
6407        // trait name matches — fail the identity check.
6408        let obj1 = make_object(1);
6409        let obj2 = make_object(2);
6410        let vt1 = make_vtable("Animal", 100, "name");
6411        let vt2 = make_vtable("Animal", 100, "name");
6412        let s1 = TraitObjectStorage::new(obj1, vt1);
6413        let s2 = TraitObjectStorage::new(obj2, vt2);
6414        assert!(!s1.vtable_eq(&s2));
6415    }
6416
6417    #[test]
6418    fn clone_bumps_both_inner_arcs() {
6419        // TraitObjectStorage::clone is a pair of Arc bumps. Verify
6420        // each inner Arc's strong-count increases by one.
6421        let obj = make_object(1);
6422        let vt = make_vtable("Animal", 100, "name");
6423        let obj_weak = Arc::downgrade(&obj);
6424        let vt_weak = Arc::downgrade(&vt);
6425        let storage = TraitObjectStorage::new(Arc::clone(&obj), Arc::clone(&vt));
6426        // Now: external obj/vt + storage's clones = 2 strong each.
6427        // Drop the externals so we can observe the storage's owned shares.
6428        drop(obj);
6429        drop(vt);
6430        assert_eq!(obj_weak.strong_count(), 1);
6431        assert_eq!(vt_weak.strong_count(), 1);
6432
6433        // Clone storage — both inner Arcs should bump.
6434        let storage2 = storage.clone();
6435        assert_eq!(obj_weak.strong_count(), 2);
6436        assert_eq!(vt_weak.strong_count(), 2);
6437
6438        drop(storage);
6439        assert_eq!(obj_weak.strong_count(), 1);
6440        assert_eq!(vt_weak.strong_count(), 1);
6441
6442        drop(storage2);
6443        assert_eq!(obj_weak.strong_count(), 0);
6444        assert_eq!(vt_weak.strong_count(), 0);
6445    }
6446
6447    #[test]
6448    fn kinded_slot_from_trait_object_drop_decrement() {
6449        // The §2.7.7 retain-on-read protocol via `KindedSlot::Drop`
6450        // must retire one `Arc<TraitObjectStorage>` share when the
6451        // slot drops. Verify the strong-count returns to zero after
6452        // both the original Arc and the KindedSlot drop.
6453        let obj = make_object(99);
6454        let vt = make_vtable("Animal", 100, "name");
6455        let storage = Arc::new(TraitObjectStorage::new(obj, vt));
6456        let weak = Arc::downgrade(&storage);
6457        let slot = KindedSlot::from_trait_object(Arc::clone(&storage));
6458        // External: 1 share. Slot: 1 share. Total: 2.
6459        assert_eq!(weak.strong_count(), 2);
6460
6461        // Drop the external Arc — slot still holds one share.
6462        drop(storage);
6463        assert_eq!(weak.strong_count(), 1);
6464
6465        // Drop the slot — retires the last share.
6466        drop(slot);
6467        assert_eq!(weak.strong_count(), 0);
6468    }
6469
6470    #[test]
6471    fn kinded_slot_clone_bumps_share() {
6472        // KindedSlot::Clone retains via clone_with_kind — verify the
6473        // share count tracks correctly across clones.
6474        let obj = make_object(1);
6475        let vt = make_vtable("Animal", 100, "name");
6476        let storage = Arc::new(TraitObjectStorage::new(obj, vt));
6477        let weak = Arc::downgrade(&storage);
6478        let slot1 = KindedSlot::from_trait_object(Arc::clone(&storage));
6479        let slot2 = slot1.clone();
6480        let slot3 = slot2.clone();
6481        // External + 3 slots = 4 shares.
6482        assert_eq!(weak.strong_count(), 4);
6483
6484        drop(storage);
6485        drop(slot1);
6486        drop(slot2);
6487        // 1 slot remaining.
6488        assert_eq!(weak.strong_count(), 1);
6489        drop(slot3);
6490        assert_eq!(weak.strong_count(), 0);
6491    }
6492
6493    #[test]
6494    fn kinded_slot_kind_label_is_ptr_trait_object() {
6495        // The kind label must be `NativeKind::Ptr(HeapKind::TraitObject)`
6496        // for the §2.7.7 dispatch tables (clone_with_kind /
6497        // drop_with_kind) to find the correct arm. This pins the
6498        // construction-side contract.
6499        let obj = make_object(1);
6500        let vt = make_vtable("Animal", 100, "name");
6501        let storage = Arc::new(TraitObjectStorage::new(obj, vt));
6502        let slot = KindedSlot::from_trait_object(storage);
6503        assert_eq!(slot.kind(), NativeKind::Ptr(HeapKind::TraitObject));
6504    }
6505
6506    #[test]
6507    fn slot_bits_recover_to_typed_arc_via_canonical_pattern() {
6508        // The canonical recovery pattern (per `3ac2f11` precedent):
6509        // bits = slot.raw(), Arc::from_raw, clone, into_raw. Verify
6510        // that round-tripping the raw bits through Arc::from_raw
6511        // recovers an Arc with the expected vtable identity.
6512        let obj = make_object(7);
6513        let vt = make_vtable("Animal", 100, "name");
6514        let storage = Arc::new(TraitObjectStorage::new(obj, vt));
6515        let original_vt_ptr = Arc::as_ptr(&storage.vtable);
6516        let slot = KindedSlot::from_trait_object(Arc::clone(&storage));
6517        let bits = slot.slot().raw();
6518
6519        // SAFETY: bits came from KindedSlot::from_trait_object which
6520        // stores `Arc::into_raw(Arc<TraitObjectStorage>)`. The slot
6521        // owns the share; we leak the recovered Arc back to keep the
6522        // slot's share intact for normal drop discipline.
6523        let recovered: Arc<TraitObjectStorage> =
6524            unsafe { Arc::from_raw(bits as *const TraitObjectStorage) };
6525        let cloned = Arc::clone(&recovered);
6526        let _ = Arc::into_raw(recovered); // restore slot's share
6527
6528        // Recovered Arc points to the same storage — same vtable Arc.
6529        assert!(Arc::ptr_eq(&cloned.vtable, &storage.vtable));
6530        // Pointer-equality on the inner vtable's raw pointer matches.
6531        assert_eq!(Arc::as_ptr(&cloned.vtable), original_vt_ptr);
6532
6533        drop(cloned);
6534        drop(slot);
6535        drop(storage);
6536    }
6537
6538    #[test]
6539    fn dropping_trait_object_with_typed_object_payload_retires_payload_share() {
6540        // Refcount discipline at the carrier level: drop the
6541        // TraitObjectStorage Arc to zero strong count, and verify
6542        // the inner TypedObject Arc's strong count returns to zero
6543        // too. This is the end-to-end retain/drop balance for the
6544        // fat-pointer carrier.
6545        let obj = make_object(99);
6546        let vt = make_vtable("Animal", 100, "name");
6547        let obj_weak = Arc::downgrade(&obj);
6548        let vt_weak = Arc::downgrade(&vt);
6549        let storage = Arc::new(TraitObjectStorage::new(obj, vt));
6550        // After move: storage holds the only shares of obj + vt.
6551        assert_eq!(obj_weak.strong_count(), 1);
6552        assert_eq!(vt_weak.strong_count(), 1);
6553
6554        drop(storage);
6555        assert_eq!(obj_weak.strong_count(), 0, "TypedObject share must retire");
6556        assert_eq!(vt_weak.strong_count(), 0, "VTable share must retire");
6557    }
6558
6559    // ── Wave 2 Agent E (2026-05-14): v2-raw HeapHeader migration tests ──────
6560
6561    #[test]
6562    fn new_initializes_heap_header() {
6563        // Wave 2 Agent E: confirm `TraitObjectStorage::new` initializes the
6564        // HeapHeader at offset 0 with HEAP_KIND_V2_TRAIT_OBJECT and
6565        // refcount=1. The header sits unused for `Arc<TraitObjectStorage>`
6566        // instances (Arc owns the lifecycle).
6567        let obj = make_object(1);
6568        let vt = make_vtable("Animal", 100, "name");
6569        let storage = TraitObjectStorage::new(obj, vt);
6570        assert_eq!(
6571            storage.header.kind(),
6572            crate::v2::heap_header::HEAP_KIND_V2_TRAIT_OBJECT,
6573        );
6574        assert_eq!(storage.header.get_refcount(), 1);
6575    }
6576
6577    #[test]
6578    fn v2_raw_new_drop_round_trip_balances_inner_arcs() {
6579        // Wave 2 Agent E: verify the v2-raw lifecycle (`_new` + `_drop`)
6580        // round-trips cleanly without leaking the inner Arc shares.
6581        // Mirror of D1's `TypedObjectStorage` round-trip test (commit
6582        // 0e4510d4).
6583        let obj = make_object(7);
6584        let vt = make_vtable("Animal", 100, "name");
6585        let obj_weak = Arc::downgrade(&obj);
6586        let vt_weak = Arc::downgrade(&vt);
6587        unsafe {
6588            let ptr = TraitObjectStorage::_new(obj, vt);
6589            assert!(!ptr.is_null());
6590            // Header refcount=1 + inner Arcs hold one share each.
6591            assert_eq!((*ptr).header.get_refcount(), 1);
6592            assert_eq!(obj_weak.strong_count(), 1);
6593            assert_eq!(vt_weak.strong_count(), 1);
6594            assert_eq!(
6595                (*ptr).header.kind(),
6596                crate::v2::heap_header::HEAP_KIND_V2_TRAIT_OBJECT,
6597            );
6598            // `_drop` retires the inner shares + deallocates.
6599            TraitObjectStorage::_drop(ptr);
6600        }
6601        assert_eq!(
6602            obj_weak.strong_count(),
6603            0,
6604            "TypedObject share must retire on _drop",
6605        );
6606        assert_eq!(
6607            vt_weak.strong_count(),
6608            0,
6609            "VTable share must retire on _drop",
6610        );
6611    }
6612
6613    #[test]
6614    fn heap_element_release_elem_to_zero_drops_payload() {
6615        // Wave 2 Agent E: verify the HeapElement trait dispatch
6616        // (`release_elem`) deallocates the carrier at refcount=0 and
6617        // retires the inner Arc shares. Mirror of D1's
6618        // `TypedObjectStorage::test_heap_element_release_elem_to_zero`.
6619        use crate::v2::heap_element::HeapElement;
6620        let obj = make_object(13);
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            assert_eq!((*ptr).header.get_refcount(), 1);
6627            // `release_elem` decrements to 0 and runs `_drop`.
6628            TraitObjectStorage::release_elem(ptr);
6629        }
6630        assert_eq!(
6631            obj_weak.strong_count(),
6632            0,
6633            "TypedObject share must retire after release_elem to zero",
6634        );
6635        assert_eq!(
6636            vt_weak.strong_count(),
6637            0,
6638            "VTable share must retire after release_elem to zero",
6639        );
6640    }
6641
6642    #[test]
6643    fn heap_element_release_elem_held_share_preserves_payload() {
6644        // Wave 2 Agent E: verify `release_elem` is a no-op at refcount > 1
6645        // (it decrements but does not deallocate). The inner Arc shares
6646        // remain held until the final `_drop`. Mirror of D1's
6647        // `TypedObjectStorage::test_heap_element_release_elem_held_share`.
6648        use crate::v2::heap_element::HeapElement;
6649        let obj = make_object(17);
6650        let vt = make_vtable("Animal", 100, "name");
6651        let obj_weak = Arc::downgrade(&obj);
6652        let vt_weak = Arc::downgrade(&vt);
6653        unsafe {
6654            let ptr = TraitObjectStorage::_new(obj, vt);
6655            // Bump refcount to 2 (simulate a second slot holding this
6656            // carrier).
6657            (*ptr).header.retain();
6658            assert_eq!((*ptr).header.get_refcount(), 2);
6659            // First release_elem: refcount=2 → 1, no dealloc.
6660            TraitObjectStorage::release_elem(ptr);
6661            assert_eq!((*ptr).header.get_refcount(), 1);
6662            // Inner shares still held.
6663            assert_eq!(obj_weak.strong_count(), 1);
6664            assert_eq!(vt_weak.strong_count(), 1);
6665            // Final _drop retires.
6666            TraitObjectStorage::_drop(ptr);
6667        }
6668        assert_eq!(obj_weak.strong_count(), 0);
6669        assert_eq!(vt_weak.strong_count(), 0);
6670    }
6671
6672    #[test]
6673    fn kinded_slot_from_trait_object_raw_constructor_kind_and_bits() {
6674        // Wave 2 Agent E: `KindedSlot::from_trait_object_raw` stores a
6675        // raw `*const TraitObjectStorage` directly (NOT `Arc::into_raw`)
6676        // with kind `NativeKind::Ptr(HeapKind::TraitObject)`. Mirror of
6677        // D1's `from_typed_object_raw_constructor_kind_and_bits` test.
6678        let obj = make_object(2);
6679        let vt = make_vtable("Animal", 100, "name");
6680        unsafe {
6681            let ptr = TraitObjectStorage::_new(obj, vt);
6682            let slot = KindedSlot::from_trait_object_raw(ptr);
6683            assert_eq!(slot.kind(), NativeKind::Ptr(HeapKind::TraitObject));
6684            // Slot bits are the raw pointer (NOT Arc::into_raw).
6685            assert_eq!(slot.slot().raw(), ptr as u64);
6686            // Forget the slot — Wave 2 transitional Arc-style dispatch
6687            // arms would call `Arc::decrement_strong_count` on raw
6688            // pointer bits (heap corruption) — see the D1 follow-up
6689            // lockstep requirement. Deallocate manually instead.
6690            std::mem::forget(slot);
6691            TraitObjectStorage::_drop(ptr);
6692        }
6693    }
6694
6695    #[test]
6696    fn v2_raw_carrier_size_matches_expected_layout() {
6697        // Wave 2 Agent E: pin the v2-raw struct layout — HeapHeader (8)
6698        // + Arc<TypedObjectStorage> (8) + Arc<VTable> (8) = 24 bytes
6699        // (matching the audit §E.3 24-byte size contract for the E-a
6700        // path). The inner Arcs stay 8-byte each in E's Round 2 scope;
6701        // D2's lockstep flip migrates the inner pointers but the
6702        // outer size contract is set here.
6703        assert_eq!(
6704            std::mem::size_of::<TraitObjectStorage>(),
6705            24,
6706            "TraitObjectStorage v2-raw layout must be 24 bytes \
6707             (HeapHeader 8 + Arc<TypedObjectStorage> 8 + Arc<VTable> 8)",
6708        );
6709    }
6710}