Skip to main content

logicaffeine_compile/concurrency/
marshal.rs

1//! Marshalling between the interpreter's `RuntimeValue` and the `Send`-able
2//! [`RtPayload`] that crosses task/thread boundaries through channels.
3//!
4//! [`materialize`] moves a value OUT of a task's `Rc`-based heap into an owned,
5//! self-contained `RtPayload`; [`rebuild`] reconstructs a fresh `Rc`-based value
6//! in the receiving task's heap. The pair mirrors [`RuntimeValue::deep_clone`]
7//! but crosses the `Send` boundary. Values that cannot cross (closures) yield a
8//! [`MarshalError`]; the Send/escape analysis (Phase 4) rejects them statically,
9//! so this is a defensive backstop, not the primary gate.
10
11use std::cell::RefCell;
12use std::rc::Rc;
13
14use logicaffeine_runtime::RtPayload;
15
16// The integer-sequence description-length codec (the Auto column menu) lives in the leaf crate so
17// the wire layer and the proof layer share one implementation of the format. `describe::` is used
18// for the encode/decode entry points; the generator IR and shared helpers are re-imported so their
19// existing call sites (and downstream `marshal::GenExpr` references) resolve unchanged.
20use logicaffeine_base::describe::{
21    self, consider, deserialize_gen, detect_affine, emit_best_int_column, serialize_gen,
22    MAX_GEN_DEPTH, MAX_GEN_NODES,
23};
24pub use logicaffeine_base::describe::{gen_eval, GenCmp, GenExpr};
25
26use crate::interpreter::{
27    ClosureValue, InductiveValue, ListRepr, MapStorage, RuntimeValue, StructValue,
28};
29use logicaffeine_data::wire::{canonical_f64, canonical_f64_bits, put_f64, put_f64s};
30
31/// Why a value could not be marshalled across a task boundary.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum MarshalError {
34    /// A value type that cannot cross a task boundary (e.g. a closure, whose
35    /// captured environment would alias another task's heap).
36    NotSendable(&'static str),
37}
38
39/// Move a value out of a task's heap into a `Send`-able payload.
40pub fn materialize(value: &RuntimeValue) -> Result<RtPayload, MarshalError> {
41    Ok(match value {
42        RuntimeValue::Int(n) => RtPayload::Int(*n),
43        RuntimeValue::BigInt(b) => {
44            let (negative, magnitude) = b.to_le_bytes();
45            RtPayload::BigInt {
46                negative,
47                magnitude,
48            }
49        }
50        RuntimeValue::Rational(r) => {
51            let (num_negative, num_magnitude) = r.numerator().to_le_bytes();
52            let (_den_sign, den_magnitude) = r.denominator().to_le_bytes();
53            RtPayload::Rational {
54                num_negative,
55                num_magnitude,
56                den_magnitude,
57            }
58        }
59        RuntimeValue::Decimal(d) => {
60            let (negative, magnitude, scale) = d.to_le_bytes();
61            RtPayload::Decimal {
62                negative,
63                magnitude,
64                scale,
65            }
66        }
67        RuntimeValue::Money(m) => {
68            let (negative, magnitude, scale) = m.amount.to_le_bytes();
69            RtPayload::Money {
70                negative,
71                magnitude,
72                scale,
73                currency: m.currency.code.to_string(),
74            }
75        }
76        RuntimeValue::Uuid(u) => RtPayload::Uuid(u.to_bytes()),
77        RuntimeValue::Complex(c) => {
78            let (re_negative, re_num) = c.re().numerator().to_le_bytes();
79            let (_, re_den) = c.re().denominator().to_le_bytes();
80            let (im_negative, im_num) = c.im().numerator().to_le_bytes();
81            let (_, im_den) = c.im().denominator().to_le_bytes();
82            RtPayload::Complex {
83                re_negative,
84                re_num,
85                re_den,
86                im_negative,
87                im_num,
88                im_den,
89            }
90        }
91        RuntimeValue::Modular(m) => {
92            let (_, value) = m.value().to_le_bytes();
93            let (_, modulus) = m.modulus().to_le_bytes();
94            RtPayload::Modular { value, modulus }
95        }
96        RuntimeValue::Float(f) => RtPayload::Float(*f),
97        RuntimeValue::Bool(b) => RtPayload::Bool(*b),
98        RuntimeValue::Char(c) => RtPayload::Char(*c),
99        RuntimeValue::Text(s) => RtPayload::Text((**s).clone()),
100        RuntimeValue::Nothing => RtPayload::Nothing,
101        RuntimeValue::Duration(n) => RtPayload::Duration(*n),
102        RuntimeValue::Date(n) => RtPayload::Date(*n),
103        RuntimeValue::Moment(n) => RtPayload::Moment(*n),
104        RuntimeValue::Span { months, days } => RtPayload::Span {
105            months: *months,
106            days: *days,
107        },
108        RuntimeValue::Time(n) => RtPayload::Time(*n),
109        RuntimeValue::Word(w) => RtPayload::Word {
110            width: w.width(),
111            bits: w.to_u64(),
112        },
113        // A SIMD lane vector is a transient compute value (a register), not a wire type — like a
114        // closure, it does not cross a task boundary. (A future increment can serialize its lanes.)
115        RuntimeValue::Lanes(_) => return Err(MarshalError::NotSendable("Lanes8Word32")),
116        RuntimeValue::ComplexF(c) => RtPayload::ComplexF {
117            re: c.re(),
118            im: c.im(),
119        },
120        RuntimeValue::List(items) => {
121            let vals = items.borrow().to_values();
122            RtPayload::List(vals.iter().map(materialize).collect::<Result<_, _>>()?)
123        }
124        RuntimeValue::Set(items) => RtPayload::Set(
125            items
126                .borrow()
127                .iter()
128                .map(materialize)
129                .collect::<Result<_, _>>()?,
130        ),
131        RuntimeValue::Tuple(items) => {
132            RtPayload::Tuple(items.iter().map(materialize).collect::<Result<_, _>>()?)
133        }
134        RuntimeValue::Map(m) => {
135            let mut pairs = Vec::new();
136            for (k, v) in m.borrow().iter() {
137                pairs.push((materialize(k)?, materialize(v)?));
138            }
139            RtPayload::Map(pairs)
140        }
141        RuntimeValue::Struct(s) => {
142            let mut fields = Vec::new();
143            for (name, v) in s.fields.iter() {
144                fields.push((name.clone(), materialize(v)?));
145            }
146            RtPayload::Struct {
147                type_name: s.type_name.clone(),
148                fields,
149            }
150        }
151        RuntimeValue::Inductive(ind) => RtPayload::Inductive {
152            type_name: ind.inductive_type.clone(),
153            constructor: ind.constructor.clone(),
154            args: ind.args.iter().map(materialize).collect::<Result<_, _>>()?,
155        },
156        // A channel/task handle is an opaque `Send` scheduler id, so it CAN cross
157        // a task (and worker-thread) boundary — e.g. passed as a spawn argument.
158        // It resolves against the one shared scheduler on the other side.
159        RuntimeValue::Chan(id) => RtPayload::Chan(*id),
160        RuntimeValue::TaskHandle(id) => RtPayload::TaskHandle(*id),
161        // A peer handle is just its topic string — trivially `Send`.
162        RuntimeValue::Peer(topic) => RtPayload::Peer((**topic).clone()),
163        RuntimeValue::Function(_) => return Err(MarshalError::NotSendable("Function")),
164        RuntimeValue::Algebraic(_) | RuntimeValue::ExactReal(_) | RuntimeValue::Ordinal(_) => {
165            let Some((kind, bytes)) = crate::semantics::exact::exact_bytes(value) else {
166                return Err(MarshalError::NotSendable(logicaffeine_base::exact_real::STAYS_WITH_ITS_PROGRAM));
167            };
168            RtPayload::Exact { kind, bytes }
169        }
170        RuntimeValue::Hyperreal(_) | RuntimeValue::Hypernatural(_) => {
171            let Some((kind, name, residues, bytes)) = crate::semantics::exact::scaled_bytes(value) else {
172                return Err(MarshalError::NotSendable(logicaffeine_base::exact_real::STAYS_WITH_ITS_PROGRAM));
173            };
174            RtPayload::Scaled {
175                kind,
176                name,
177                residues,
178                bytes,
179            }
180        }
181        RuntimeValue::Depends(d) => {
182            let (name, residues) = crate::semantics::exact::scale_parts(&d.scale);
183            let branches = d
184                .value
185                .raw_branches()
186                .into_iter()
187                .map(|b| b.map(|b| materialize(&b.0)).transpose())
188                .collect::<Result<Vec<_>, _>>()?;
189            RtPayload::Depends {
190                name,
191                residues,
192                modulus: d.value.modulus(),
193                branches,
194            }
195        }
196        RuntimeValue::Converging(c) => RtPayload::Converging {
197            value: Box::new(materialize(c.value())?),
198            steps: c.steps(),
199            revisions: c.revisions(),
200            certificate: c.certificate().map(str::to_string),
201        },
202        // A view is a handle into ONE task's DOM: sending it would alias another task's page.
203        RuntimeValue::View(_) => return Err(MarshalError::NotSendable("View")),
204        RuntimeValue::Action(_) => return Err(MarshalError::NotSendable("Action")),
205        // A live CRDT crosses the boundary as its KIND-TAGGED full convergent STATE — NOT its `Rc`
206        // handle (that would alias the same replica cell). The far side rebuilds a fresh replica and
207        // merges, so a redelivered payload is idempotent (per-replica MAX, never double-counts). This
208        // is what makes `Sync <crdt>` marshal correctly across the resumable-VM boundary.
209        RuntimeValue::Crdt(c) => RtPayload::Crdt(c.borrow().to_full_state_bytes()),
210        // A Quantity travels as its exact SI magnitude (a rational), its dimension (the exponent
211        // vector), and the display unit's symbol — reconstructed losslessly on the far side.
212        RuntimeValue::Quantity(qv) => {
213            let (num_negative, num_magnitude) = qv.q.magnitude_si().numerator().to_le_bytes();
214            let (_den_sign, den_magnitude) = qv.q.magnitude_si().denominator().to_le_bytes();
215            let dim = qv.q.dimension();
216            let mut dim_num = Vec::with_capacity(logicaffeine_base::BaseDim::COUNT);
217            let mut dim_den = Vec::with_capacity(logicaffeine_base::BaseDim::COUNT);
218            for d in logicaffeine_base::BaseDim::ALL {
219                let e = dim.exponent(d);
220                dim_num.push(e.numerator());
221                dim_den.push(e.denominator());
222            }
223            RtPayload::Quantity {
224                num_negative,
225                num_magnitude,
226                den_magnitude,
227                dim_num,
228                dim_den,
229                unit_symbol: qv.unit.symbol.to_string(),
230            }
231        }
232    })
233}
234
235/// Reconstruct a fresh `Rc`-based value in the receiving task's heap.
236pub fn rebuild(payload: RtPayload) -> RuntimeValue {
237    match payload {
238        RtPayload::Nothing => RuntimeValue::Nothing,
239        RtPayload::Int(n) => RuntimeValue::Int(n),
240        RtPayload::BigInt {
241            negative,
242            magnitude,
243        } => RuntimeValue::from_bigint(logicaffeine_base::BigInt::from_le_bytes(
244            negative, &magnitude,
245        )),
246        RtPayload::Rational {
247            num_negative,
248            num_magnitude,
249            den_magnitude,
250        } => {
251            let num = logicaffeine_base::BigInt::from_le_bytes(num_negative, &num_magnitude);
252            let den = logicaffeine_base::BigInt::from_le_bytes(false, &den_magnitude);
253            // A well-formed payload always has a nonzero denominator; fall back to the
254            // numerator (treated as a whole number) if a corrupt one slips through.
255            match logicaffeine_base::Rational::new(num.clone(), den) {
256                Some(r) => RuntimeValue::from_rational(r),
257                None => RuntimeValue::from_bigint(num),
258            }
259        }
260        RtPayload::Decimal {
261            negative,
262            magnitude,
263            scale,
264        } => RuntimeValue::Decimal(Rc::new(logicaffeine_base::Decimal::from_le_bytes(
265            negative, &magnitude, scale,
266        ))),
267        RtPayload::Money {
268            negative,
269            magnitude,
270            scale,
271            currency,
272        } => {
273            let amount = logicaffeine_base::Decimal::from_le_bytes(negative, &magnitude, scale);
274            let currency = logicaffeine_base::money::currency::by_code(&currency).unwrap_or(
275                logicaffeine_base::Currency {
276                    code: "XXX",
277                    scale: 0,
278                },
279            );
280            RuntimeValue::Money(Rc::new(logicaffeine_base::Money { amount, currency }))
281        }
282        RtPayload::Uuid(bytes) => {
283            RuntimeValue::Uuid(Rc::new(logicaffeine_base::Uuid::from_bytes(bytes)))
284        }
285        RtPayload::Complex {
286            re_negative,
287            re_num,
288            re_den,
289            im_negative,
290            im_num,
291            im_den,
292        } => {
293            let mk = |neg: bool, num: &[u8], den: &[u8]| {
294                logicaffeine_base::Rational::new(
295                    logicaffeine_base::BigInt::from_le_bytes(neg, num),
296                    logicaffeine_base::BigInt::from_le_bytes(false, den),
297                )
298                .unwrap_or_else(logicaffeine_base::Rational::zero)
299            };
300            let re = mk(re_negative, &re_num, &re_den);
301            let im = mk(im_negative, &im_num, &im_den);
302            RuntimeValue::Complex(Rc::new(logicaffeine_base::Complex::new(re, im)))
303        }
304        RtPayload::ComplexF { re, im } => {
305            RuntimeValue::ComplexF(Rc::new(logicaffeine_base::ComplexF::new(re, im)))
306        }
307        RtPayload::Modular { value, modulus } => {
308            let v = logicaffeine_base::BigInt::from_le_bytes(false, &value);
309            let n = logicaffeine_base::BigInt::from_le_bytes(false, &modulus);
310            match logicaffeine_base::Modular::new(v, n) {
311                Some(m) => RuntimeValue::Modular(Rc::new(m)),
312                None => RuntimeValue::Nothing, // a corrupt (non-positive) modulus degrades gracefully
313            }
314        }
315        RtPayload::Quantity {
316            num_negative,
317            num_magnitude,
318            den_magnitude,
319            dim_num,
320            dim_den,
321            unit_symbol,
322        } => {
323            let num = logicaffeine_base::BigInt::from_le_bytes(num_negative, &num_magnitude);
324            let den = logicaffeine_base::BigInt::from_le_bytes(false, &den_magnitude);
325            let magnitude = logicaffeine_base::Rational::new(num, den)
326                .unwrap_or_else(logicaffeine_base::Rational::zero);
327            // Rebuild the dimension from its exponent vector (BaseDim::ALL order).
328            let mut exps = [logicaffeine_base::Exp::ZERO; logicaffeine_base::BaseDim::COUNT];
329            for (i, slot) in exps.iter_mut().enumerate() {
330                let n = dim_num.get(i).copied().unwrap_or(0);
331                let d = dim_den.get(i).copied().unwrap_or(1);
332                *slot = logicaffeine_base::Exp::new(n, if d == 0 { 1 } else { d });
333            }
334            let dim = logicaffeine_base::Dimension::from_exps(exps);
335            // Resolve the display unit by its symbol; a compound or unknown symbol falls back to the
336            // SI/dimension display (a synthetic empty-symbol unit at the SI base).
337            let unit = logicaffeine_base::quantity::units::by_name(&unit_symbol)
338                .filter(|u| u.dimension == dim)
339                .unwrap_or_else(|| {
340                    logicaffeine_base::Unit::linear("", dim, logicaffeine_base::Rational::one())
341                });
342            let q = logicaffeine_base::Quantity::si(magnitude, dim);
343            RuntimeValue::Quantity(Rc::new(crate::interpreter::QuantityValue { q, unit }))
344        }
345        RtPayload::Float(f) => RuntimeValue::Float(f),
346        RtPayload::Bool(b) => RuntimeValue::Bool(b),
347        RtPayload::Char(c) => RuntimeValue::Char(c),
348        RtPayload::Text(s) => RuntimeValue::Text(Rc::new(s)),
349        RtPayload::Duration(n) => RuntimeValue::Duration(n),
350        RtPayload::Date(n) => RuntimeValue::Date(n),
351        RtPayload::Moment(n) => RuntimeValue::Moment(n),
352        RtPayload::Span { months, days } => RuntimeValue::Span { months, days },
353        RtPayload::Time(n) => RuntimeValue::Time(n),
354        RtPayload::Word { width, bits } => {
355            match logicaffeine_base::WordVal::from_u64(width, bits) {
356                Some(w) => RuntimeValue::Word(w),
357                // A well-formed payload always carries width 32/64; degrade a corrupt one to its value.
358                None => RuntimeValue::Int(bits as i64),
359            }
360        }
361        RtPayload::List(items) => {
362            let vals: Vec<RuntimeValue> = items.into_iter().map(rebuild).collect();
363            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::from_values(vals))))
364        }
365        RtPayload::Set(items) => RuntimeValue::Set(Rc::new(RefCell::new(
366            items.into_iter().map(rebuild).collect(),
367        ))),
368        RtPayload::Tuple(items) => {
369            RuntimeValue::Tuple(Rc::new(items.into_iter().map(rebuild).collect()))
370        }
371        RtPayload::Map(pairs) => {
372            let m: MapStorage = pairs
373                .into_iter()
374                .map(|(k, v)| (rebuild(k), rebuild(v)))
375                .collect();
376            RuntimeValue::Map(Rc::new(RefCell::new(m)))
377        }
378        RtPayload::Struct { type_name, fields } => {
379            let f = fields.into_iter().map(|(k, v)| (k, rebuild(v))).collect();
380            RuntimeValue::Struct(Box::new(StructValue {
381                type_name,
382                fields: f,
383            }))
384        }
385        RtPayload::Inductive {
386            type_name,
387            constructor,
388            args,
389        } => RuntimeValue::Inductive(std::rc::Rc::new(InductiveValue {
390            inductive_type: type_name,
391            constructor,
392            args: args.into_iter().map(rebuild).collect(),
393        })),
394        RtPayload::Chan(id) => RuntimeValue::Chan(id),
395        RtPayload::TaskHandle(id) => RuntimeValue::TaskHandle(id),
396        RtPayload::Peer(topic) => RuntimeValue::Peer(Rc::new(topic)),
397        // Rebuild a fresh CRDT replica from the shipped full state (see the `materialize` arm): the
398        // far side owns an independent `Rc` cell whose convergent value equals the sender's.
399        RtPayload::Exact { kind, bytes } => crate::semantics::exact::from_exact_bytes(kind, &bytes)
400            .expect("a materialized exact value rebuilds"),
401        RtPayload::Scaled {
402            kind,
403            name,
404            residues,
405            bytes,
406        } => crate::semantics::exact::from_scaled_bytes(kind, &name, &residues, &bytes)
407            .expect("a materialized scaled value rebuilds"),
408        RtPayload::Depends {
409            name,
410            residues,
411            modulus,
412            branches,
413        } => crate::semantics::exact::depends_from_parts(
414            &name,
415            &residues,
416            modulus,
417            branches.into_iter().map(|b| b.map(rebuild)).collect(),
418        )
419        .expect("a materialized dependence rebuilds"),
420        RtPayload::Converging {
421            value,
422            steps,
423            revisions,
424            certificate,
425        } => RuntimeValue::Converging(Rc::new(match certificate {
426            Some(cert) => logicaffeine_base::limit_value::Converging::settled(rebuild(*value), &cert),
427            None => logicaffeine_base::limit_value::Converging::provisional(
428                rebuild(*value),
429                steps,
430                revisions,
431            ),
432        })),
433        RtPayload::Crdt(bytes) => RuntimeValue::Crdt(Rc::new(RefCell::new(
434            crate::semantics::crdt::CrdtValue::from_full_state_bytes(&bytes),
435        ))),
436    }
437}
438
439// =============================================================================
440// Wire codec — a message is any language value, sent over the relay
441// =============================================================================
442//
443// A peer message is just a value, and both ends speak the same language, so the
444// wire form IS the value — carrying its type. [`WireValue`] is the network-
445// portable shape of a value, mirroring the `Send`-able [`RtPayload`] minus the
446// pieces that have no meaning off-machine (a `Function` cannot be marshalled at
447// all; a `Chan`/`TaskHandle` is an index into THIS process's scheduler). Its
448// `struct`/`inductive` variants carry the type name (and constructor), so a sent
449// `Point` is reconstructed as a `Point`, not a bare map — the type rides with the
450// value. Encoding is `bincode`: compact binary, no text parsing, and `RtPayload`
451// is already the materialized form, so on the far side `rebuild` is the only cost
452// left. (Runtime is serde-free by charter, so this serde mirror lives here.)
453// Both peers are the same Logos binary, so the non-self-describing encoding is
454// sound; swapping `bincode` for a self-describing codec (CBOR) is a one-liner if
455// cross-version peers ever matter.
456
457/// A value as it travels the wire — the network-portable projection of
458/// [`RtPayload`], type tags and all.
459#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
460enum WireValue {
461    Nothing,
462    Int(i64),
463    BigInt {
464        negative: bool,
465        magnitude: Vec<u8>,
466    },
467    Rational {
468        num_negative: bool,
469        num_magnitude: Vec<u8>,
470        den_magnitude: Vec<u8>,
471    },
472    Decimal {
473        negative: bool,
474        magnitude: Vec<u8>,
475        scale: u32,
476    },
477    Money {
478        negative: bool,
479        magnitude: Vec<u8>,
480        scale: u32,
481        currency: std::string::String,
482    },
483    Uuid([u8; 16]),
484    Complex {
485        re_negative: bool,
486        re_num: Vec<u8>,
487        re_den: Vec<u8>,
488        im_negative: bool,
489        im_num: Vec<u8>,
490        im_den: Vec<u8>,
491    },
492    ComplexF {
493        re: f64,
494        im: f64,
495    },
496    Modular {
497        value: Vec<u8>,
498        modulus: Vec<u8>,
499    },
500    Quantity {
501        num_negative: bool,
502        num_magnitude: Vec<u8>,
503        den_magnitude: Vec<u8>,
504        dim_num: Vec<i32>,
505        dim_den: Vec<i32>,
506        unit_symbol: String,
507    },
508    Float(f64),
509    Bool(bool),
510    Char(char),
511    Text(String),
512    Duration(i64),
513    Date(i32),
514    Moment(i64),
515    Span {
516        months: i32,
517        days: i32,
518    },
519    Time(i64),
520    Word {
521        width: u32,
522        bits: u64,
523    },
524    Peer(String),
525    List(Vec<WireValue>),
526    Tuple(Vec<WireValue>),
527    Set(Vec<WireValue>),
528    Map(Vec<(WireValue, WireValue)>),
529    Struct {
530        type_name: String,
531        fields: Vec<(String, WireValue)>,
532    },
533    Inductive {
534        type_name: String,
535        constructor: String,
536        args: Vec<WireValue>,
537    },
538    Exact {
539        kind: u8,
540        bytes: Vec<u8>,
541    },
542    Scaled {
543        kind: u8,
544        name: String,
545        residues: Vec<u8>,
546        bytes: Vec<u8>,
547    },
548    Depends {
549        name: String,
550        residues: Vec<u8>,
551        modulus: u64,
552        branches: Vec<Option<WireValue>>,
553    },
554    Converging {
555        value: Box<WireValue>,
556        steps: u64,
557        revisions: u64,
558        certificate: Option<String>,
559    },
560}
561
562/// The wire envelope: the sender's inbox topic plus the typed value.
563#[derive(serde::Serialize, serde::Deserialize)]
564struct WireMessage {
565    from: String,
566    msg: WireValue,
567}
568
569/// One `RtPayload` → its portable [`WireValue`]. `None` if it carries a value
570/// with no meaning on another machine — a local channel or task handle.
571fn rt_to_wire(p: &RtPayload) -> Option<WireValue> {
572    Some(match p {
573        RtPayload::Nothing => WireValue::Nothing,
574        RtPayload::Int(n) => WireValue::Int(*n),
575        RtPayload::BigInt {
576            negative,
577            magnitude,
578        } => WireValue::BigInt {
579            negative: *negative,
580            magnitude: magnitude.clone(),
581        },
582        RtPayload::Rational {
583            num_negative,
584            num_magnitude,
585            den_magnitude,
586        } => WireValue::Rational {
587            num_negative: *num_negative,
588            num_magnitude: num_magnitude.clone(),
589            den_magnitude: den_magnitude.clone(),
590        },
591        RtPayload::Decimal {
592            negative,
593            magnitude,
594            scale,
595        } => WireValue::Decimal {
596            negative: *negative,
597            magnitude: magnitude.clone(),
598            scale: *scale,
599        },
600        RtPayload::Money {
601            negative,
602            magnitude,
603            scale,
604            currency,
605        } => WireValue::Money {
606            negative: *negative,
607            magnitude: magnitude.clone(),
608            scale: *scale,
609            currency: currency.clone(),
610        },
611        RtPayload::Uuid(bytes) => WireValue::Uuid(*bytes),
612        RtPayload::ComplexF { re, im } => WireValue::ComplexF {
613            re: canonical_f64(*re),
614            im: canonical_f64(*im),
615        },
616        RtPayload::Complex {
617            re_negative,
618            re_num,
619            re_den,
620            im_negative,
621            im_num,
622            im_den,
623        } => WireValue::Complex {
624            re_negative: *re_negative,
625            re_num: re_num.clone(),
626            re_den: re_den.clone(),
627            im_negative: *im_negative,
628            im_num: im_num.clone(),
629            im_den: im_den.clone(),
630        },
631        RtPayload::Modular { value, modulus } => WireValue::Modular {
632            value: value.clone(),
633            modulus: modulus.clone(),
634        },
635        RtPayload::Quantity {
636            num_negative,
637            num_magnitude,
638            den_magnitude,
639            dim_num,
640            dim_den,
641            unit_symbol,
642        } => WireValue::Quantity {
643            num_negative: *num_negative,
644            num_magnitude: num_magnitude.clone(),
645            den_magnitude: den_magnitude.clone(),
646            dim_num: dim_num.clone(),
647            dim_den: dim_den.clone(),
648            unit_symbol: unit_symbol.clone(),
649        },
650        RtPayload::Float(f) => WireValue::Float(canonical_f64(*f)),
651        RtPayload::Bool(b) => WireValue::Bool(*b),
652        RtPayload::Char(c) => WireValue::Char(*c),
653        RtPayload::Text(s) => WireValue::Text(s.clone()),
654        RtPayload::Duration(n) => WireValue::Duration(*n),
655        RtPayload::Date(n) => WireValue::Date(*n),
656        RtPayload::Moment(n) => WireValue::Moment(*n),
657        RtPayload::Span { months, days } => WireValue::Span {
658            months: *months,
659            days: *days,
660        },
661        RtPayload::Time(n) => WireValue::Time(*n),
662        RtPayload::Word { width, bits } => WireValue::Word {
663            width: *width,
664            bits: *bits,
665        },
666        RtPayload::Peer(topic) => WireValue::Peer(topic.clone()),
667        RtPayload::List(items) => WireValue::List(rt_seq_to_wire(items)?),
668        RtPayload::Tuple(items) => WireValue::Tuple(rt_seq_to_wire(items)?),
669        RtPayload::Set(items) => WireValue::Set(rt_seq_to_wire(items)?),
670        RtPayload::Map(pairs) => {
671            let mut wire_pairs = pairs
672                .iter()
673                .map(|(k, v)| Some((rt_to_wire(k)?, rt_to_wire(v)?)))
674                .collect::<Option<Vec<_>>>()?;
675            // Canonical order: sort by the encoded key, so the wire is the same
676            // bytes regardless of the source map's (hash) iteration order.
677            wire_pairs.sort_by(|a, b| canon_bytes(&a.0).cmp(&canon_bytes(&b.0)));
678            WireValue::Map(wire_pairs)
679        }
680        RtPayload::Struct { type_name, fields } => {
681            let mut wire_fields = fields
682                .iter()
683                .map(|(n, v)| Some((n.clone(), rt_to_wire(v)?)))
684                .collect::<Option<Vec<_>>>()?;
685            // A struct is a record (unordered fields), so canonicalize by name.
686            wire_fields.sort_by(|a, b| a.0.cmp(&b.0));
687            WireValue::Struct {
688                type_name: type_name.clone(),
689                fields: wire_fields,
690            }
691        }
692        RtPayload::Inductive {
693            type_name,
694            constructor,
695            args,
696        } => WireValue::Inductive {
697            type_name: type_name.clone(),
698            constructor: constructor.clone(),
699            args: rt_seq_to_wire(args)?,
700        },
701        // A scheduler token indexes THIS process's scheduler — not portable.
702        RtPayload::Chan(_) | RtPayload::TaskHandle(_) => return None,
703        // A CRDT does not travel as an INLINE channel/wire message — it converges over the dedicated
704        // relay `Sync`/`Merge` protocol (the state-based `crdt_to_wire`/`crdt_merge_wire` delta path).
705        // As a channel payload it is a same-process move (rebuilt via `RtPayload::Crdt`), so it has no
706        // portable inline `WireValue` form here.
707        RtPayload::Crdt(_) => return None,
708        RtPayload::Exact { kind, bytes } => WireValue::Exact {
709            kind: *kind,
710            bytes: bytes.clone(),
711        },
712        RtPayload::Scaled {
713            kind,
714            name,
715            residues,
716            bytes,
717        } => WireValue::Scaled {
718            kind: *kind,
719            name: name.clone(),
720            residues: residues.clone(),
721            bytes: bytes.clone(),
722        },
723        RtPayload::Depends {
724            name,
725            residues,
726            modulus,
727            branches,
728        } => WireValue::Depends {
729            name: name.clone(),
730            residues: residues.clone(),
731            modulus: *modulus,
732            branches: branches
733                .iter()
734                .map(|b| match b {
735                    Some(p) => rt_to_wire(p).map(Some),
736                    None => Some(None),
737                })
738                .collect::<Option<Vec<_>>>()?,
739        },
740        RtPayload::Converging {
741            value,
742            steps,
743            revisions,
744            certificate,
745        } => WireValue::Converging {
746            value: Box::new(rt_to_wire(value)?),
747            steps: *steps,
748            revisions: *revisions,
749            certificate: certificate.clone(),
750        },
751    })
752}
753
754fn rt_seq_to_wire(items: &[RtPayload]) -> Option<Vec<WireValue>> {
755    items.iter().map(rt_to_wire).collect()
756}
757
758/// One [`WireValue`] → its `RtPayload`. The inverse of [`rt_to_wire`]; total.
759fn wire_to_rt(w: WireValue) -> RtPayload {
760    match w {
761        WireValue::Nothing => RtPayload::Nothing,
762        WireValue::Int(n) => RtPayload::Int(n),
763        WireValue::BigInt {
764            negative,
765            magnitude,
766        } => RtPayload::BigInt {
767            negative,
768            magnitude,
769        },
770        WireValue::Rational {
771            num_negative,
772            num_magnitude,
773            den_magnitude,
774        } => RtPayload::Rational {
775            num_negative,
776            num_magnitude,
777            den_magnitude,
778        },
779        WireValue::Decimal {
780            negative,
781            magnitude,
782            scale,
783        } => RtPayload::Decimal {
784            negative,
785            magnitude,
786            scale,
787        },
788        WireValue::Money {
789            negative,
790            magnitude,
791            scale,
792            currency,
793        } => RtPayload::Money {
794            negative,
795            magnitude,
796            scale,
797            currency,
798        },
799        WireValue::Uuid(bytes) => RtPayload::Uuid(bytes),
800        WireValue::ComplexF { re, im } => RtPayload::ComplexF { re, im },
801        WireValue::Complex {
802            re_negative,
803            re_num,
804            re_den,
805            im_negative,
806            im_num,
807            im_den,
808        } => RtPayload::Complex {
809            re_negative,
810            re_num,
811            re_den,
812            im_negative,
813            im_num,
814            im_den,
815        },
816        WireValue::Modular { value, modulus } => RtPayload::Modular { value, modulus },
817        WireValue::Quantity {
818            num_negative,
819            num_magnitude,
820            den_magnitude,
821            dim_num,
822            dim_den,
823            unit_symbol,
824        } => RtPayload::Quantity {
825            num_negative,
826            num_magnitude,
827            den_magnitude,
828            dim_num,
829            dim_den,
830            unit_symbol,
831        },
832        WireValue::Float(f) => RtPayload::Float(f),
833        WireValue::Bool(b) => RtPayload::Bool(b),
834        WireValue::Char(c) => RtPayload::Char(c),
835        WireValue::Text(s) => RtPayload::Text(s),
836        WireValue::Duration(n) => RtPayload::Duration(n),
837        WireValue::Date(n) => RtPayload::Date(n),
838        WireValue::Moment(n) => RtPayload::Moment(n),
839        WireValue::Span { months, days } => RtPayload::Span { months, days },
840        WireValue::Time(n) => RtPayload::Time(n),
841        WireValue::Word { width, bits } => RtPayload::Word { width, bits },
842        WireValue::Peer(topic) => RtPayload::Peer(topic),
843        WireValue::List(items) => RtPayload::List(items.into_iter().map(wire_to_rt).collect()),
844        WireValue::Tuple(items) => RtPayload::Tuple(items.into_iter().map(wire_to_rt).collect()),
845        WireValue::Set(items) => RtPayload::Set(items.into_iter().map(wire_to_rt).collect()),
846        WireValue::Map(pairs) => RtPayload::Map(
847            pairs
848                .into_iter()
849                .map(|(k, v)| (wire_to_rt(k), wire_to_rt(v)))
850                .collect(),
851        ),
852        WireValue::Struct { type_name, fields } => RtPayload::Struct {
853            type_name,
854            fields: fields
855                .into_iter()
856                .map(|(n, v)| (n, wire_to_rt(v)))
857                .collect(),
858        },
859        WireValue::Inductive {
860            type_name,
861            constructor,
862            args,
863        } => RtPayload::Inductive {
864            type_name,
865            constructor,
866            args: args.into_iter().map(wire_to_rt).collect(),
867        },
868        WireValue::Exact { kind, bytes } => RtPayload::Exact { kind, bytes },
869        WireValue::Scaled {
870            kind,
871            name,
872            residues,
873            bytes,
874        } => RtPayload::Scaled {
875            kind,
876            name,
877            residues,
878            bytes,
879        },
880        WireValue::Depends {
881            name,
882            residues,
883            modulus,
884            branches,
885        } => RtPayload::Depends {
886            name,
887            residues,
888            modulus,
889            branches: branches.into_iter().map(|b| b.map(wire_to_rt)).collect(),
890        },
891        WireValue::Converging {
892            value,
893            steps,
894            revisions,
895            certificate,
896        } => RtPayload::Converging {
897            value: Box::new(wire_to_rt(*value)),
898            steps,
899            revisions,
900            certificate,
901        },
902    }
903}
904
905/// Whether every exact value inside a payload from a peer decodes canonically, so that
906/// [`rebuild`] (which trusts payloads materialized in this process) never meets a forged one.
907fn exact_payloads_valid(p: &RtPayload) -> bool {
908    use crate::semantics::exact;
909    match p {
910        RtPayload::Exact { kind, bytes } => exact::from_exact_bytes(*kind, bytes).is_some(),
911        RtPayload::Scaled {
912            kind,
913            name,
914            residues,
915            bytes,
916        } => exact::from_scaled_bytes(*kind, name, residues, bytes).is_some(),
917        RtPayload::Depends {
918            name,
919            residues,
920            modulus,
921            branches,
922        } => {
923            branches.iter().flatten().all(exact_payloads_valid)
924                && exact::depends_from_parts(
925                    name,
926                    residues,
927                    *modulus,
928                    branches
929                        .iter()
930                        .map(|b| b.as_ref().map(|_| RuntimeValue::Nothing))
931                        .collect(),
932                )
933                .is_some()
934        }
935        RtPayload::Converging { value, .. } => exact_payloads_valid(value),
936        RtPayload::List(items) | RtPayload::Tuple(items) | RtPayload::Set(items) => {
937            items.iter().all(exact_payloads_valid)
938        }
939        RtPayload::Map(pairs) => pairs
940            .iter()
941            .all(|(k, v)| exact_payloads_valid(k) && exact_payloads_valid(v)),
942        RtPayload::Struct { fields, .. } => fields.iter().all(|(_, v)| exact_payloads_valid(v)),
943        RtPayload::Inductive { args, .. } => args.iter().all(exact_payloads_valid),
944        _ => true,
945    }
946}
947
948/// The wire encoding for a message body.
949///
950/// `Native` is the default and the fast path — *our* compact tagged-varint binary
951/// format, encoded and decoded in a SINGLE PASS straight to/from `RuntimeValue`
952/// with no intermediate trees: the hot loop for a list of scalars allocates only
953/// the output buffer. `Json` is offered for interop with non-Logos peers (or human
954/// debugging) through a real parser (`serde_json`), never a hand-rolled one. Both
955/// ride the same relay: every message self-describes its codec in a leading header
956/// byte, so any receiver decodes either.
957#[derive(Debug, Clone, Copy, PartialEq, Eq)]
958pub enum WireCodec {
959    /// Our single-pass tagged-varint binary (default) — the high-throughput path.
960    Native,
961    /// `serde_json` text — interop / debuggable, larger and slower.
962    Json,
963}
964
965/// Whether a message carries an integrity checksum.
966///
967/// Independent of the codec. Pay a few bytes + a hash to have the receiver reject
968/// a corrupted or mis-encoded message, or go bare for raw speed. This is
969/// *integrity*, not secrecy — for confidentiality run the relay over `wss://`/TLS
970/// at the transport layer; a message-level signing/encryption layer is separate.
971#[derive(Debug, Clone, Copy, PartialEq, Eq)]
972pub enum WireIntegrity {
973    /// No checksum — smallest and fastest; the receiver trusts the bytes.
974    Raw,
975    /// An FNV-1a checksum over the payload — the receiver rejects a corrupted or
976    /// mis-encoded message (`message_from_wire` returns `None`).
977    Checked,
978}
979
980// The framing header byte: bit 0 = integrity, bit 1 = compressed, bits 2-3 = the
981// compression codec id (when compressed), bit 4 = payload codec; any other bit set
982// is an unknown format and is rejected.
983const H_CHECKED: u8 = 0x01;
984const H_COMPRESSED: u8 = 0x02;
985const H_CODEC: u8 = 0x0C; // bits 2-3: 0=deflate, 1=lz4, 2=zstd (only meaningful when H_COMPRESSED)
986const H_JSON: u8 = 0x10;
987const H_KNOWN: u8 = H_CHECKED | H_COMPRESSED | H_CODEC | H_JSON;
988
989/// Encode a directed peer message for the relay wire: the sender's inbox topic
990/// plus the FULL language value — scalars, collections, structs, inductives,
991/// nested, type tags and all — as compact `bincode` under the process default
992/// integrity (`default_integrity`). Closures, and channel/task handles (local to
993/// this process), cannot travel between machines and are reported with a clear
994/// error rather than silently dropped.
995pub fn message_to_wire(from: &str, value: &RuntimeValue) -> Result<Vec<u8>, String> {
996    message_to_wire_with(from, value, WireCodec::Native, current_integrity())
997}
998
999/// Encode ONE value as the plain, self-describing recursive wire form — no envelope, no
1000/// framing, no columnar/compression/dedup transforms. This is the exact format the shared
1001/// [`logicaffeine_data::wire`] core decodes, so a `RuntimeValue` encoded here round-trips
1002/// through an AOT-generated type's `wire_decode` (and vice versa). The speed-first form used
1003/// to hand a program AST to a compile-once native partial evaluator.
1004pub fn encode_value_raw(v: &RuntimeValue) -> Result<Vec<u8>, String> {
1005    with_flat_lists(true, || {
1006        with_structure(WireStructure::Off, || {
1007            with_dedup(false, || {
1008                let mut out = Vec::new();
1009                native_encode(v, &mut out)?;
1010                Ok(out)
1011            })
1012        })
1013    })
1014}
1015
1016/// Decode a value produced by [`encode_value_raw`] (or by an AOT-generated `wire_encode`)
1017/// back into a `RuntimeValue`. `None` on any malformed or trailing-byte input.
1018pub fn decode_value_raw(buf: &[u8]) -> Option<RuntimeValue> {
1019    let mut pos = 0usize;
1020    let v = native_decode(buf, &mut pos)?;
1021    if pos == buf.len() {
1022        Some(v)
1023    } else {
1024        None
1025    }
1026}
1027
1028/// What the [`message_to_wire_best`] auto-tuner optimizes for.
1029#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1030pub enum WireGoal {
1031    /// Fewest bytes on the wire — measure every applicable encoding and ship the smallest.
1032    /// The bandwidth-bound choice (a network link).
1033    Smallest,
1034    /// Cheapest decode — fixed-width `memcpy` columns, no compression, no structural
1035    /// transform. The latency / CPU-bound choice (datacenter, shared memory, RDMA).
1036    Fastest,
1037}
1038
1039/// The no-brainer encoder — "just use this." Picks the Pareto-optimal dial combination for
1040/// `goal` and ships it. Because every wire form is self-describing by its leading tag, this
1041/// is purely an ENCODE-side decision: the decoder reconstructs via `message_from_wire` with
1042/// no hint, so `best` interoperates with every existing peer.
1043///
1044/// `Smallest` measures the FULL cross product of the size-affecting dials (numerics ×
1045/// structure × float-coding × compression) and returns the minimum. Because every
1046/// single-dial configuration is literally one of the candidates, the result is provably
1047/// never larger than ANY single knob — on any workload. `Fastest` returns the fixed
1048/// memcpy-decode form directly. (`Smallest` pays N encode passes for the minimum bytes; it
1049/// is the opt-in "I am bandwidth-bound" choice, not the default.)
1050pub fn message_to_wire_best(
1051    from: &str,
1052    value: &RuntimeValue,
1053    goal: WireGoal,
1054) -> Result<Vec<u8>, String> {
1055    match goal {
1056        WireGoal::Fastest => with_numerics(WireNumerics::Fixed, || {
1057            with_structure(WireStructure::Off, || {
1058                with_floats(WireFloats::Memcpy, || {
1059                    with_compression_codec(WireCompression::None, || message_to_wire(from, value))
1060                })
1061            })
1062        }),
1063        WireGoal::Smallest => smallest_over(
1064            from,
1065            value,
1066            &[
1067                WireCompression::None,
1068                WireCompression::Deflate,
1069                WireCompression::Lz4,
1070                WireCompression::Zstd,
1071            ],
1072        ),
1073    }
1074}
1075
1076/// The size bake-off, parameterized by the compression codecs allowed (so a negotiated send only tries
1077/// what the receiver can decode). Measures the FULL cross product of the size-affecting dials
1078/// (numerics × structure/G5 × float-coding) against each allowed compression and returns the smallest —
1079/// the maximal crush. Runs under whatever type registry is in scope (so name elision composes when the
1080/// caller enabled it). Every form is self-describing, so the decoder needs no hint.
1081fn smallest_over(
1082    from: &str,
1083    value: &RuntimeValue,
1084    compressions: &[WireCompression],
1085) -> Result<Vec<u8>, String> {
1086    let mut best: Option<Vec<u8>> = None;
1087    // Structure-sharing dimension: if the SAME subtree is reached more than once, Rc-dedup ships it
1088    // ONCE + backrefs. It competes as just another bake-off candidate (each is a single dedup-scoped
1089    // encode, so the per-encode id table never leaks across passes), so it wins ONLY when it actually
1090    // beats the backref-tag overhead. The gather runs once HERE; no sharing → no dedup pass, no cost.
1091    let dedup_opts: &[bool] = if value_has_sharing(value) {
1092        &[false, true]
1093    } else {
1094        &[false]
1095    };
1096    for num in [
1097        WireNumerics::Varint,
1098        WireNumerics::Fixed,
1099        WireNumerics::GroupVarint,
1100    ] {
1101        for st in [
1102            WireStructure::Off,
1103            WireStructure::Affine,
1104            WireStructure::Auto,
1105        ] {
1106            for fl in [WireFloats::Memcpy, WireFloats::XorDelta] {
1107                for &comp in compressions {
1108                    for &dedup in dedup_opts {
1109                        let bytes = with_dedup(dedup, || {
1110                            with_numerics(num, || {
1111                                with_structure(st, || {
1112                                    with_floats(fl, || {
1113                                        with_compression_codec(comp, || {
1114                                            message_to_wire(from, value)
1115                                        })
1116                                    })
1117                                })
1118                            })
1119                        })?;
1120                        if best.as_ref().map_or(true, |b| bytes.len() < b.len()) {
1121                            best = Some(bytes);
1122                        }
1123                    }
1124                }
1125            }
1126        }
1127    }
1128    best.ok_or_else(|| "no encoding produced".to_string())
1129}
1130
1131/// Encode a message TO a peer using EVERYTHING both sides support — the negotiated maximal crush.
1132/// Applies all the self-describing dials (any peer decodes them) via the size bake-off, but restricts
1133/// the receiver-capability knobs to the negotiated surface: only compression codecs the receiver can
1134/// decode, and type-id NAME ELISION only when epochs matched (`neg.use_type_id`). Refuses to ship a
1135/// computation the receiver declined. Stays MINIMAL in cost too: a tiny message that name-elision can't
1136/// help ships the plain default without paying for the search. Never larger than the default; always
1137/// self-describing, so it round-trips on the receiver with no hint.
1138pub fn message_to_wire_negotiated(
1139    from: &str,
1140    value: &RuntimeValue,
1141    neg: &Negotiated,
1142    registry: WireTypeRegistry,
1143) -> Result<Vec<u8>, String> {
1144    if matches!(value, RuntimeValue::Function(_)) && !neg.may_send_computed {
1145        return Err("the receiver does not accept computed (shipped-function) sends".to_string());
1146    }
1147    let default = message_to_wire(from, value)?;
1148    // Minimal cost: a small message that type-id can't shrink skips the bake-off entirely.
1149    if default.len() <= AUTO_SEARCH_THRESHOLD && !neg.use_type_id {
1150        return Ok(default);
1151    }
1152    let codecs: Vec<WireCompression> = if neg.compression == WireCompression::None {
1153        vec![WireCompression::None]
1154    } else {
1155        vec![WireCompression::None, neg.compression]
1156    };
1157    let search = || smallest_over(from, value, &codecs);
1158    // Name elision only fires when the receiver shares our type registry (negotiated epoch match).
1159    let best = if neg.use_type_id {
1160        with_type_registry(registry, search)
1161    } else {
1162        search()
1163    }?;
1164    Ok(if best.len() < default.len() {
1165        best
1166    } else {
1167        default
1168    })
1169}
1170
1171/// Below this default-encoding size the `Smallest` search cannot meaningfully shrink a message (the
1172/// envelope + a scalar / short string is already near-minimal), so [`message_to_wire_auto`] skips the
1173/// N-pass bake-off and ships the default. Tuned so a scalar / short message never pays for the search.
1174const AUTO_SEARCH_THRESHOLD: usize = 64;
1175
1176/// The genuine no-brainer — "just send it." Runs the full [`WireGoal::Smallest`] bake-off ONLY when the
1177/// payload is large enough for it to matter, and otherwise ships the plain default (so calling this on
1178/// every message — including scalars and short strings — costs a single encode pass, not the N-pass
1179/// search). The result is NEVER larger than the default, ALWAYS self-describing (so it interoperates
1180/// with every peer, no hint), and round-trips exactly. This is the recommended default sender.
1181pub fn message_to_wire_auto(from: &str, value: &RuntimeValue) -> Result<Vec<u8>, String> {
1182    let default = message_to_wire(from, value)?;
1183    if default.len() <= AUTO_SEARCH_THRESHOLD {
1184        return Ok(default);
1185    }
1186    // The bake-off includes the default dial set as one candidate, so its winner is ≤ `default`.
1187    let best = message_to_wire_best(from, value, WireGoal::Smallest)?;
1188    Ok(if best.len() < default.len() {
1189        best
1190    } else {
1191        default
1192    })
1193}
1194
1195/// A receiver's admission-control budget — the limits it will accept from a sender, so a malicious or
1196/// buggy peer cannot exhaust the receiver's memory, stack, or CPU. Enforced DURING decode, BEFORE the
1197/// offending allocation or recursion happens, so an over-budget message is refused cleanly (the decode
1198/// returns `None`) rather than processed. A receiver advertises these in the capability handshake so a
1199/// cooperative sender stays within them; an uncooperative one is still bounded by the enforcement.
1200#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1201pub struct ReceiveLimits {
1202    /// Largest message body (post-decompress framing aside) the receiver will decode, in bytes.
1203    pub max_bytes: usize,
1204    /// Maximum container-nesting depth. Bounds decode RECURSION, so a deeply-nested but byte-small
1205    /// message cannot overflow the receiver's stack (a remote crash).
1206    pub max_depth: usize,
1207    /// Maximum element count for any single decoded collection (list / map / set / struct-list /
1208    /// numeric column). A claimed count above this is refused before the elements are read.
1209    pub max_elements: usize,
1210    /// Maximum byte length of any single decoded string.
1211    pub max_string_bytes: usize,
1212    /// Whether to accept a SHIPPED computation (`T_FUNC`) at all. When `false`, a computed send is
1213    /// refused at decode — independent of (and prior to) the C2 acceptance contract that gates whether
1214    /// an accepted computation may be EVALUATED.
1215    pub accept_computed: bool,
1216}
1217
1218/// Generous-but-finite defaults: every real message passes (genuine nesting is almost always < 10
1219/// deep); only the pathological / adversarial ones are refused. `max_depth` sits BELOW
1220/// `MAX_ENCODE_DEPTH` on purpose — the recursive DECODER's stack frame is heavier than the encoder's
1221/// (one giant `match`), so the depth that is safe to recurse on a small (wasm ~1 MiB) stack is lower
1222/// than what we allow ourselves to encode. A deployment tightens these per peer through the handshake.
1223pub const DEFAULT_RECEIVE_LIMITS: ReceiveLimits = ReceiveLimits {
1224    max_bytes: 64 << 20,
1225    max_depth: 64,
1226    max_elements: 1 << 24,
1227    max_string_bytes: 1 << 24,
1228    accept_computed: true,
1229};
1230
1231impl Default for ReceiveLimits {
1232    fn default() -> Self {
1233        DEFAULT_RECEIVE_LIMITS
1234    }
1235}
1236
1237thread_local! {
1238    static RECEIVE_LIMITS: std::cell::Cell<ReceiveLimits> =
1239        const { std::cell::Cell::new(DEFAULT_RECEIVE_LIMITS) };
1240    static DECODE_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
1241}
1242
1243/// Decode under `limits` for the duration of `f` — the receiver's admission gate. Restores the prior
1244/// limits afterward (so it nests). Pair with [`message_from_wire`].
1245pub fn with_receive_limits<T>(limits: ReceiveLimits, f: impl FnOnce() -> T) -> T {
1246    let prev = RECEIVE_LIMITS.with(|c| c.replace(limits));
1247    let r = f();
1248    RECEIVE_LIMITS.with(|c| c.set(prev));
1249    r
1250}
1251
1252/// The limits in force on this thread (default [`DEFAULT_RECEIVE_LIMITS`] outside a
1253/// [`with_receive_limits`] scope).
1254fn receive_limits() -> ReceiveLimits {
1255    RECEIVE_LIMITS.with(std::cell::Cell::get)
1256}
1257
1258// ── Optional-capability feature bits a peer advertises in its [`PeerProfile`]. Unknown bits are
1259//    ignored on receipt, so the set grows forward/backward-compatibly. ──
1260/// The peer understands DEFLATE-compressed frames.
1261pub const FEAT_DEFLATE: u32 = 1 << 0;
1262/// The peer understands LZ4-compressed frames.
1263pub const FEAT_LZ4: u32 = 1 << 1;
1264/// The peer understands Zstd-compressed frames.
1265pub const FEAT_ZSTD: u32 = 1 << 2;
1266/// The peer can resolve type-id name elision against a shared type registry.
1267pub const FEAT_TYPE_ID: u32 = 1 << 3;
1268/// The peer is willing to receive a shipped computation (subject to its acceptance contracts).
1269pub const FEAT_COMPUTED: u32 = 1 << 4;
1270/// The peer can reconstruct FEC / erasure-coded redundant frames.
1271pub const FEAT_FEC: u32 = 1 << 5;
1272
1273/// A peer's PUBLISHED acceptance surface — the single declarative object it EXPOSES to the other side
1274/// AND that its own decoder ENFORCES. Declare it once: it is both advertised (in the handshake) and the
1275/// budget the decode path checks, so the two can never drift. Carries the resource budget
1276/// ([`ReceiveLimits`]), the peer's type-registry epoch (a content hash of its type table — when both
1277/// peers' epochs match, struct/enum NAMES need not travel, since both derive the same ids), and a
1278/// feature bitset (`FEAT_*`).
1279#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1280pub struct PeerProfile {
1281    /// What this peer will accept — enforced on decode, advertised so a cooperative sender stays within it.
1282    pub limits: ReceiveLimits,
1283    /// Content hash of this peer's type table (0 = none). Equal non-zero epochs on both sides unlock
1284    /// name elision.
1285    pub registry_epoch: u64,
1286    /// `FEAT_*` capability bits this peer understands.
1287    pub features: u32,
1288}
1289
1290impl Default for PeerProfile {
1291    fn default() -> Self {
1292        Self {
1293            limits: ReceiveLimits::default(),
1294            registry_epoch: 0,
1295            features: FEAT_DEFLATE | FEAT_LZ4 | FEAT_ZSTD | FEAT_TYPE_ID | FEAT_COMPUTED | FEAT_FEC,
1296        }
1297    }
1298}
1299
1300impl PeerProfile {
1301    /// The profile to ASSUME for a peer we have NOT yet heard from — NO optional capabilities and NO
1302    /// shared type registry. Negotiating against it yields a plain, uncompressed, self-describing send
1303    /// that ANY receiver (even a non-Logos relay consumer) can decode. A capability turns on only once
1304    /// the peer ADVERTISES it in its handshake. (`Default` is the opposite — the FULL profile a Logos
1305    /// node advertises for ITSELF.)
1306    pub const fn conservative() -> Self {
1307        Self {
1308            limits: DEFAULT_RECEIVE_LIMITS,
1309            registry_epoch: 0,
1310            features: 0,
1311        }
1312    }
1313}
1314
1315const PEER_PROFILE_VERSION: u8 = 1;
1316
1317/// Serialize a [`PeerProfile`] for the handshake — a version byte (so a future layout is recognized,
1318/// not mis-parsed) followed by the budget, epoch, and feature bits.
1319pub fn encode_peer_profile(p: &PeerProfile) -> Vec<u8> {
1320    let mut out = Vec::with_capacity(32);
1321    out.push(PEER_PROFILE_VERSION);
1322    write_uvarint(p.limits.max_bytes as u64, &mut out);
1323    write_uvarint(p.limits.max_depth as u64, &mut out);
1324    write_uvarint(p.limits.max_elements as u64, &mut out);
1325    write_uvarint(p.limits.max_string_bytes as u64, &mut out);
1326    out.push(p.limits.accept_computed as u8);
1327    write_uvarint(p.registry_epoch, &mut out);
1328    write_uvarint(p.features as u64, &mut out);
1329    out
1330}
1331
1332/// Parse a peer's advertised [`PeerProfile`]. A version this build does not understand, or a truncated
1333/// blob, yields `None` — the caller then falls back to the conservative defaults (never mis-decodes).
1334pub fn decode_peer_profile(buf: &[u8]) -> Option<PeerProfile> {
1335    let mut pos = 0;
1336    let version = *buf.get(pos)?;
1337    pos += 1;
1338    if version != PEER_PROFILE_VERSION {
1339        return None;
1340    }
1341    let max_bytes = read_uvarint(buf, &mut pos)? as usize;
1342    let max_depth = read_uvarint(buf, &mut pos)? as usize;
1343    let max_elements = read_uvarint(buf, &mut pos)? as usize;
1344    let max_string_bytes = read_uvarint(buf, &mut pos)? as usize;
1345    let accept_computed = *buf.get(pos)? != 0;
1346    pos += 1;
1347    let registry_epoch = read_uvarint(buf, &mut pos)?;
1348    let features = read_uvarint(buf, &mut pos)? as u32;
1349    Some(PeerProfile {
1350        limits: ReceiveLimits {
1351            max_bytes,
1352            max_depth,
1353            max_elements,
1354            max_string_bytes,
1355            accept_computed,
1356        },
1357        registry_epoch,
1358        features,
1359    })
1360}
1361
1362/// The encoding choices negotiated for sending TO a peer, from my profile and the peer's advertised one.
1363#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1364pub struct Negotiated {
1365    /// Elide type NAMES from the wire — only when both sides speak type-id AND share the same non-zero
1366    /// registry epoch (so the receiver can resolve the ids).
1367    pub use_type_id: bool,
1368    /// Ship a computed function — only when the receiver both accepts computed sends and advertises the
1369    /// capability. (The receiver still gates INVOCATION through its acceptance contracts.)
1370    pub may_send_computed: bool,
1371    /// The strongest compression BOTH peers understand (`None` if they share none).
1372    pub compression: WireCompression,
1373    /// The receiver's byte budget — keep each message under this so it is not refused.
1374    pub peer_max_bytes: usize,
1375}
1376
1377/// Negotiate how to send TO a peer from my profile + the peer's advertised one. CONSERVATIVE: a
1378/// capability is used only when BOTH sides expose it. This is where "expose properly" pays off — the
1379/// sender automatically restricts itself to exactly the surface the receiver published, so it never
1380/// ships a form the receiver can't decode, won't run code the receiver declined, and stays under the
1381/// receiver's size budget.
1382pub fn negotiate(mine: &PeerProfile, theirs: &PeerProfile) -> Negotiated {
1383    let both = |bit: u32| mine.features & bit != 0 && theirs.features & bit != 0;
1384    let compression = if both(FEAT_ZSTD) {
1385        WireCompression::Zstd
1386    } else if both(FEAT_LZ4) {
1387        WireCompression::Lz4
1388    } else if both(FEAT_DEFLATE) {
1389        WireCompression::Deflate
1390    } else {
1391        WireCompression::None
1392    };
1393    Negotiated {
1394        use_type_id: both(FEAT_TYPE_ID)
1395            && mine.registry_epoch != 0
1396            && mine.registry_epoch == theirs.registry_epoch,
1397        may_send_computed: theirs.limits.accept_computed && both(FEAT_COMPUTED),
1398        compression,
1399        peer_max_bytes: theirs.limits.max_bytes,
1400    }
1401}
1402
1403/// Magic prefix that marks a frame as a capability HANDSHAKE rather than a data message. A data frame
1404/// begins with a 1-byte header in the small `H_KNOWN` range, so this ASCII prefix can never collide
1405/// with one — the receiver tells them apart unambiguously.
1406const HANDSHAKE_MAGIC: &[u8; 4] = b"LCHS";
1407
1408/// Build a handshake frame advertising `from`'s [`PeerProfile`]: the magic prefix, the sender identity,
1409/// then the serialized profile. Published like any message but recognized + absorbed (not delivered as
1410/// data) by the receiver.
1411pub fn make_handshake_frame(from: &str, profile: &PeerProfile) -> Vec<u8> {
1412    let mut out = Vec::with_capacity(8 + from.len() + 16);
1413    out.extend_from_slice(HANDSHAKE_MAGIC);
1414    write_str(from, &mut out);
1415    out.extend_from_slice(&encode_peer_profile(profile));
1416    out
1417}
1418
1419/// Parse a handshake frame → `(sender, advertised profile)`. `None` when `data` is not a handshake (no
1420/// magic) or is malformed — so a data message is never mistaken for one, and an unknown/garbage profile
1421/// is ignored rather than mis-applied.
1422pub fn parse_handshake_frame(data: &[u8]) -> Option<(String, PeerProfile)> {
1423    let rest = data.strip_prefix(HANDSHAKE_MAGIC.as_slice())?;
1424    let mut pos = 0;
1425    let from = read_str(rest, &mut pos)?;
1426    let profile = decode_peer_profile(rest.get(pos..)?)?;
1427    Some((from, profile))
1428}
1429
1430/// As [`message_to_wire`], with an explicit codec and integrity mode.
1431pub fn message_to_wire_with(
1432    from: &str,
1433    value: &RuntimeValue,
1434    codec: WireCodec,
1435    integrity: WireIntegrity,
1436) -> Result<Vec<u8>, String> {
1437    let mut body = match codec {
1438        // Single pass straight from the live value — no intermediate trees.
1439        WireCodec::Native => {
1440            // A small base capacity covers the envelope + a scalar/short message
1441            // without a realloc; the packed-array arms reserve their own bulk.
1442            let mut out = Vec::with_capacity(from.len() + 32);
1443            write_str(from, &mut out);
1444            native_encode(value, &mut out)?;
1445            out
1446        }
1447        // JSON goes through the serde mirror (interop, not the speed path).
1448        WireCodec::Json => {
1449            let payload = materialize(value).map_err(|MarshalError::NotSendable(t)| {
1450                format!("a {t} cannot be sent over the network")
1451            })?;
1452            let msg = rt_to_wire(&payload).ok_or_else(|| {
1453                "a channel or task handle cannot be sent over the network".to_string()
1454            })?;
1455            serde_json::to_vec(&WireMessage {
1456                from: from.to_string(),
1457                msg,
1458            })
1459            .map_err(|e| format!("message encode failed: {e}"))?
1460        }
1461    };
1462    // Optional compression — but only KEEP it if it actually shrank the body, so a
1463    // small/incompressible message is never made bigger or slower.
1464    let mut compression = WireCompression::None;
1465    if let Some((used, z)) = compress_body(compression_codec(), &body) {
1466        if z.len() < body.len() {
1467            body = z;
1468            compression = used;
1469        }
1470    }
1471    Ok(frame(codec, integrity, compression, body))
1472}
1473
1474/// Decode a wire message (from [`message_to_wire`]) into `(sender, value)`,
1475/// rebuilding the typed value in the local heap. Auto-detects the codec,
1476/// integrity, and compression; a checksum mismatch, an unknown header, a bad
1477/// inflate, trailing bytes, or any malformed input all return `None` — never a
1478/// panic, never a half-rebuilt value.
1479pub fn message_from_wire(bytes: &[u8]) -> Option<(String, RuntimeValue)> {
1480    // Admission gate: refuse a message larger than the receiver's byte budget BEFORE decompressing or
1481    // decoding it — a hostile peer cannot make the receiver spend memory/CPU on an over-budget frame.
1482    if bytes.len() > receive_limits().max_bytes {
1483        return None;
1484    }
1485    // Fresh dedup memo per top-level message (ids are message-local; never leak across messages).
1486    DECODE_MEMO.with(|c| c.borrow_mut().clear());
1487    let (codec, compression, framed) = unframe(bytes)?;
1488    // Inflate first if needed (the checksum, already verified, covered the
1489    // compressed bytes — so we never spend CPU inflating a corrupt message). The
1490    // codec is read off the header, so any peer decodes any sender's choice.
1491    let inflated;
1492    let body: &[u8] = if compression == WireCompression::None {
1493        framed
1494    } else {
1495        inflated = decompress_body(compression, framed)?;
1496        &inflated
1497    };
1498    match codec {
1499        WireCodec::Native => {
1500            let mut pos = 0;
1501            let from = read_str(body, &mut pos)?;
1502            let value = native_decode(body, &mut pos)?;
1503            (pos == body.len()).then_some((from, value)) // reject trailing bytes
1504        }
1505        WireCodec::Json => {
1506            let WireMessage { from, msg } = serde_json::from_slice(body).ok()?;
1507            let payload = wire_to_rt(msg);
1508            exact_payloads_valid(&payload).then(|| (from, rebuild(payload)))
1509        }
1510    }
1511}
1512
1513/// The plain-words name of the structural encoding a column tag selects — the codec's own
1514/// dial vocabulary, for surfacing *which* encoding actually won (benchmarks, docs, debug).
1515/// A non-column or unknown tag reads as the generic `"value"`.
1516fn column_tag_name(tag: u8) -> &'static str {
1517    match tag {
1518        T_INTS => "varint",
1519        T_INTS_FIXED => "fixed (memcpy)",
1520        T_INTS_GV => "group-varint",
1521        T_INTS_ALIGNED => "fixed-aligned",
1522        T_INTS_AFFINE => "affine (base,stride,n)",
1523        T_INTS_DELTA => "delta",
1524        T_INTS_DOD => "delta-of-delta",
1525        T_INTS_FOR => "FOR bit-pack",
1526        T_INTS_RLE => "run-length",
1527        T_INTS_DICT => "dictionary",
1528        T_INTS_POLY => "polynomial",
1529        T_INTS_GEOMETRIC => "geometric",
1530        T_INTS_PERIODIC => "periodic",
1531        T_INTS_SPARSE => "sparse",
1532        T_GEN => "generator",
1533        T_BYTES => "byte column",
1534        describe::T_INTS_LRECUR => "linear-recurrence",
1535        describe::T_INTS_LFSR => "LFSR",
1536        describe::T_INTS_FCSR => "FCSR",
1537        T_FLOATS => "memcpy floats",
1538        T_FLOATS_XOR => "xor-delta floats",
1539        T_FLOATS_CONST => "constant floats",
1540        T_FLOATS_AFFINE => "affine floats",
1541        T_FLOATS_SPARSE => "sparse floats",
1542        T_FLOATS_PERIODIC => "periodic floats",
1543        T_FLOATS_GEOMETRIC => "geometric floats",
1544        T_FLOATS_ALIGNED => "aligned floats",
1545        T_BOOLS => "bit-packed bools",
1546        T_BOOLS_PERIODIC => "periodic bools",
1547        T_BOOLS_RLE => "run-length bools",
1548        T_STRINGS => "flat strings",
1549        T_STRINGS_TEMPLATE => "templated strings",
1550        T_STRINGS_FRONT => "front-coded strings",
1551        T_STRINGS_AFFIX => "affix strings",
1552        T_STRINGS_DICT => "dictionary strings",
1553        T_SET_INTS => "int set (column menu)",
1554        T_SET_STRINGS => "string set (front-coded)",
1555        T_MAP_INTKEY => "int-keyed map (columnar)",
1556        _ => "value",
1557    }
1558}
1559
1560/// Name the structural encoding of each column in a native wire message — the codec
1561/// explaining its own output, so "which dial won" is legible to a human. A single-column
1562/// message (int / float / string / bool list, a set, an int-keyed map) yields one name; a
1563/// record list (`T_STRUCTS`) yields one `"field: encoding"` per field. Empty for a shape it
1564/// does not model (compressed, JSON, a bare scalar, or a malformed frame) — the caller then
1565/// shows a generic label. It reuses the real decode dispatch to skip each column body, so it
1566/// can never drift from the format it reports on.
1567pub fn describe_columns(bytes: &[u8]) -> Vec<String> {
1568    describe_columns_inner(bytes).unwrap_or_default()
1569}
1570
1571fn describe_columns_inner(bytes: &[u8]) -> Option<Vec<String>> {
1572    let (codec, compression, body) = unframe(bytes)?;
1573    if !matches!(codec, WireCodec::Native) || compression != WireCompression::None {
1574        return None; // callers inspect the uncompressed native bytes
1575    }
1576    DECODE_MEMO.with(|c| c.borrow_mut().clear());
1577    let mut pos = 0;
1578    let _from = read_str(body, &mut pos)?;
1579    let tag = *body.get(pos)?;
1580    if tag == T_STRUCTS {
1581        let mut p = pos + 1;
1582        let (_type_name, field_names) = read_struct_schema(body, &mut p)?;
1583        let _rows = read_uvarint(body, &mut p)?;
1584        let mut out = Vec::with_capacity(field_names.len());
1585        for name in &field_names {
1586            let col_tag = *body.get(p)?;
1587            native_decode(body, &mut p)?; // advance past the column via the real decoder
1588            out.push(format!("{name}: {}", column_tag_name(col_tag)));
1589        }
1590        return Some(out);
1591    }
1592    Some(vec![column_tag_name(tag).to_string()])
1593}
1594
1595/// How a connection-scoped schema dictionary identifies a struct schema on the wire.
1596/// All modes are corruption-free; they trade size against robustness.
1597#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
1598pub enum WireSchemaMode {
1599    /// No dictionary — the schema is always inline (`T_STRUCTS`). Always safe.
1600    #[default]
1601    Off,
1602    /// Position ids: a 1-byte counter. The smallest, but the id only means anything
1603    /// relative to ONE sender's ordered stream — use only for a reliable
1604    /// point-to-point connection (one sender per receiver cache).
1605    Sequential,
1606    /// Content-addressed: the id is a 64-bit fingerprint of the schema itself, so it
1607    /// is sender-independent and order-independent. Multiple senders, reordering, and
1608    /// loss are all safe (a reference to an unknown fingerprint resolves to `None`,
1609    /// never to the wrong schema; a definition whose fingerprint conflicts with a
1610    /// different cached schema is rejected). The footgun-free default for a mesh.
1611    ContentAddressed,
1612}
1613
1614/// A connection-scoped schema dictionary (one per direction per peer). A struct
1615/// schema (type name + field names) is transmitted ONCE and referenced thereafter —
1616/// the RPC-grade win for streams of same-shaped struct messages. The mode picks the
1617/// id scheme (see [`WireSchemaMode`]); every mode is corruption-free — a decoder that
1618/// cannot resolve a reference returns `None` rather than mis-decoding. An optional
1619/// keyframe interval re-emits a definition every *k* references so a late or lossy
1620/// receiver self-heals.
1621#[derive(Debug)]
1622pub struct WireSchemaCache {
1623    mode: WireSchemaMode,
1624    // Sequential (position) state.
1625    send_seq: std::collections::HashMap<(String, Vec<String>), u32>,
1626    recv_seq: Vec<(String, Vec<String>)>,
1627    next: u32,
1628    // Content-addressed (fingerprint) state.
1629    send_ca: std::collections::HashSet<u64>,
1630    recv_ca: std::collections::HashMap<u64, (String, Vec<String>)>,
1631    // Self-healing: re-emit a definition every `keyframe` references (content mode).
1632    keyframe: Option<u32>,
1633    refs_since_def: std::collections::HashMap<u64, u32>,
1634}
1635
1636impl Default for WireSchemaCache {
1637    /// The footgun-free default: content-addressed.
1638    fn default() -> Self {
1639        Self::with_mode(WireSchemaMode::ContentAddressed)
1640    }
1641}
1642
1643impl WireSchemaCache {
1644    fn with_mode(mode: WireSchemaMode) -> Self {
1645        Self {
1646            mode,
1647            send_seq: std::collections::HashMap::new(),
1648            recv_seq: Vec::new(),
1649            next: 0,
1650            send_ca: std::collections::HashSet::new(),
1651            recv_ca: std::collections::HashMap::new(),
1652            keyframe: None,
1653            refs_since_def: std::collections::HashMap::new(),
1654        }
1655    }
1656    /// Content-addressed (footgun-free): safe for multiple senders, reordering, loss.
1657    pub fn content_addressed() -> Self {
1658        Self::with_mode(WireSchemaMode::ContentAddressed)
1659    }
1660    /// Position ids (smallest): for a single reliable ordered point-to-point stream.
1661    pub fn sequential() -> Self {
1662        Self::with_mode(WireSchemaMode::Sequential)
1663    }
1664    /// Re-emit a schema definition every `k` references, so a late/lossy receiver
1665    /// self-heals (content-addressed mode).
1666    pub fn with_keyframe(mut self, k: u32) -> Self {
1667        self.keyframe = Some(k);
1668        self
1669    }
1670}
1671
1672/// The 64-bit content fingerprint of a struct schema — a sender-independent identity.
1673/// Collisions are negligible for realistic schema counts, and a fingerprint clash
1674/// with a *different* cached schema is rejected on definition, so it cannot corrupt.
1675fn schema_fingerprint(type_name: &str, field_names: &[String]) -> u64 {
1676    let mut bytes = Vec::with_capacity(type_name.len() + 8);
1677    bytes.extend_from_slice(type_name.as_bytes());
1678    for f in field_names {
1679        bytes.push(0);
1680        bytes.extend_from_slice(f.as_bytes());
1681    }
1682    fnv1a_64(&bytes)
1683}
1684
1685/// What the encoder should emit for a struct list's schema, per the active cache.
1686enum SchemaEmit {
1687    Inline,
1688    SeqDef(u32),
1689    SeqRef(u32),
1690    CaDef,
1691    CaRef(u64),
1692}
1693
1694thread_local! {
1695    // The cache in force for the current cached encode/decode. Swapped in by the
1696    // `*_cached` entry points for the duration of the call, then swapped back out.
1697    static SCHEMA_CACHE: RefCell<Option<WireSchemaCache>> = const { RefCell::new(None) };
1698}
1699
1700/// RAII: install `cache` into the thread-local for the duration of a cached
1701/// encode/decode and ALWAYS swap it back out — on normal return AND on a panic
1702/// unwind — so a panic mid-codec can never strand or poison the schema state.
1703struct CacheScope<'a> {
1704    cache: &'a mut WireSchemaCache,
1705}
1706impl<'a> CacheScope<'a> {
1707    fn enter(cache: &'a mut WireSchemaCache) -> Self {
1708        SCHEMA_CACHE.with(|c| *c.borrow_mut() = Some(std::mem::take(cache)));
1709        Self { cache }
1710    }
1711}
1712impl Drop for CacheScope<'_> {
1713    fn drop(&mut self) {
1714        SCHEMA_CACHE.with(|c| *self.cache = c.borrow_mut().take().unwrap_or_default());
1715    }
1716}
1717
1718/// As [`message_to_wire_with`], but a known struct schema is sent by reference
1719/// instead of inline (per the cache's [`WireSchemaMode`]).
1720pub fn message_to_wire_cached(
1721    from: &str,
1722    value: &RuntimeValue,
1723    codec: WireCodec,
1724    integrity: WireIntegrity,
1725    cache: &mut WireSchemaCache,
1726) -> Result<Vec<u8>, String> {
1727    let _scope = CacheScope::enter(cache);
1728    message_to_wire_with(from, value, codec, integrity)
1729}
1730
1731/// As [`message_from_wire`], but resolves schema references against `cache` and
1732/// records schema definitions into it.
1733pub fn message_from_wire_cached(
1734    bytes: &[u8],
1735    cache: &mut WireSchemaCache,
1736) -> Option<(String, RuntimeValue)> {
1737    let _scope = CacheScope::enter(cache);
1738    message_from_wire(bytes)
1739}
1740
1741/// Encode side: decide how to transmit a struct schema, recording state as needed.
1742fn schema_send(type_name: &str, field_names: &[String]) -> SchemaEmit {
1743    SCHEMA_CACHE.with(|c| {
1744        let mut g = c.borrow_mut();
1745        let Some(cache) = g.as_mut() else {
1746            return SchemaEmit::Inline;
1747        };
1748        match cache.mode {
1749            WireSchemaMode::Off => SchemaEmit::Inline,
1750            WireSchemaMode::Sequential => {
1751                let key = (type_name.to_string(), field_names.to_vec());
1752                if let Some(&id) = cache.send_seq.get(&key) {
1753                    SchemaEmit::SeqRef(id)
1754                } else {
1755                    let id = cache.next;
1756                    cache.next += 1;
1757                    cache.send_seq.insert(key, id);
1758                    SchemaEmit::SeqDef(id)
1759                }
1760            }
1761            WireSchemaMode::ContentAddressed => {
1762                let fp = schema_fingerprint(type_name, field_names);
1763                let known = cache.send_ca.contains(&fp);
1764                let count = cache.refs_since_def.entry(fp).or_insert(0);
1765                let keyframe_due = matches!(cache.keyframe, Some(k) if known && *count >= k);
1766                if known && !keyframe_due {
1767                    *count += 1;
1768                    SchemaEmit::CaRef(fp)
1769                } else {
1770                    *count = 0;
1771                    cache.send_ca.insert(fp);
1772                    SchemaEmit::CaDef
1773                }
1774            }
1775        }
1776    })
1777}
1778
1779/// Decode side (sequential): record a definition at `id`. Ids must arrive in order;
1780/// a matching re-definition is tolerated, a gap or a conflict is rejected. `true`
1781/// when there is no cache (a definition is self-decodable inline regardless).
1782fn schema_recv_register_seq(id: u32, type_name: &str, field_names: &[String]) -> bool {
1783    SCHEMA_CACHE.with(|c| {
1784        let mut g = c.borrow_mut();
1785        let Some(cache) = g.as_mut() else { return true };
1786        let entry = (type_name.to_string(), field_names.to_vec());
1787        let idx = id as usize;
1788        if idx == cache.recv_seq.len() {
1789            cache.recv_seq.push(entry);
1790            true
1791        } else if idx < cache.recv_seq.len() {
1792            cache.recv_seq[idx] == entry
1793        } else {
1794            false
1795        }
1796    })
1797}
1798
1799/// Decode side (sequential): resolve a reference; `None` if unknown.
1800fn schema_recv_lookup_seq(id: u32) -> Option<(String, Vec<String>)> {
1801    SCHEMA_CACHE.with(|c| {
1802        c.borrow()
1803            .as_ref()
1804            .and_then(|cache| cache.recv_seq.get(id as usize).cloned())
1805    })
1806}
1807
1808/// Decode side (content-addressed): record a definition under its fingerprint. A
1809/// fingerprint that already maps to a DIFFERENT schema (a collision) is rejected —
1810/// so a clash can never corrupt, only fail. `true` when there is no cache.
1811fn schema_recv_register_ca(type_name: &str, field_names: &[String]) -> bool {
1812    SCHEMA_CACHE.with(|c| {
1813        let mut g = c.borrow_mut();
1814        let Some(cache) = g.as_mut() else { return true };
1815        let fp = schema_fingerprint(type_name, field_names);
1816        let entry = (type_name.to_string(), field_names.to_vec());
1817        match cache.recv_ca.get(&fp) {
1818            Some(existing) => *existing == entry, // collision with a different schema → reject
1819            None => {
1820                cache.recv_ca.insert(fp, entry);
1821                true
1822            }
1823        }
1824    })
1825}
1826
1827/// Decode side (content-addressed): resolve a reference by fingerprint; `None` if the
1828/// definition was never seen (reordering / loss / stale decode).
1829fn schema_recv_lookup_ca(fp: u64) -> Option<(String, Vec<String>)> {
1830    SCHEMA_CACHE.with(|c| {
1831        c.borrow()
1832            .as_ref()
1833            .and_then(|cache| cache.recv_ca.get(&fp).cloned())
1834    })
1835}
1836
1837/// A program-derived registry of every struct/enum schema, shared by both ends of a
1838/// Logos↔Logos link (each side builds it from the SAME program type definitions). Every
1839/// type gets a stable small id — canonical by fingerprint, so declaration order is
1840/// irrelevant and sender + receiver always agree. The codec ships the id instead of the
1841/// type/field NAMES, and the receiver — running the same program — resolves it. This is
1842/// the "duh, you use that" default that drops names off the wire entirely.
1843#[derive(Debug, Default, Clone)]
1844pub struct WireTypeRegistry {
1845    by_id: Vec<(String, Vec<String>)>,
1846    by_fp: std::collections::HashMap<u64, u32>,
1847    // Enums: id → (type_name, ordered constructor list). The constructor ORDER is part of
1848    // the type def (we ship a constructor *index*), so it is preserved, not sorted.
1849    enums_by_id: Vec<(String, Vec<String>)>,
1850    enums_by_name: std::collections::HashMap<String, u32>,
1851}
1852
1853impl WireTypeRegistry {
1854    /// Build from `(type_name, field_names)` struct schemas. Field names are sorted (the
1855    /// codec's canonical order), duplicates collapsed, and the set ordered by fingerprint
1856    /// so two peers that declared the same types in any order assign identical ids.
1857    pub fn new(schemas: Vec<(String, Vec<String>)>) -> Self {
1858        let mut canon: Vec<(String, Vec<String>)> = schemas
1859            .into_iter()
1860            .map(|(n, mut f)| {
1861                f.sort();
1862                (n, f)
1863            })
1864            .collect();
1865        canon.sort_by_key(|(n, f)| schema_fingerprint(n, f));
1866        canon.dedup_by_key(|(n, f)| schema_fingerprint(n, f));
1867        let by_fp = canon
1868            .iter()
1869            .enumerate()
1870            .map(|(i, (n, f))| (schema_fingerprint(n, f), i as u32))
1871            .collect();
1872        Self {
1873            by_id: canon,
1874            by_fp,
1875            ..Self::default()
1876        }
1877    }
1878
1879    /// Add enum types `(type_name, ordered_constructors)`. Constructor order is preserved
1880    /// (the wire ships a constructor index); the enum set is ordered by fingerprint so
1881    /// both peers assign identical enum ids regardless of declaration order.
1882    pub fn with_enums(mut self, enums: Vec<(String, Vec<String>)>) -> Self {
1883        let mut canon = enums;
1884        canon.sort_by_key(|(n, c)| schema_fingerprint(n, c));
1885        canon.dedup_by_key(|(n, c)| schema_fingerprint(n, c));
1886        self.enums_by_name = canon
1887            .iter()
1888            .enumerate()
1889            .map(|(i, (n, _))| (n.clone(), i as u32))
1890            .collect();
1891        self.enums_by_id = canon;
1892        self
1893    }
1894
1895    /// A content hash of this registry's WHOLE type set — the registry EPOCH advertised in the
1896    /// handshake. Two peers that declared the SAME struct + enum types (in any order) compute the same
1897    /// epoch, so when their epochs MATCH they may elide type NAMES from the wire (type-id). `0` for an
1898    /// empty registry — no shared types, so never elide. Deterministic: folds the per-type
1899    /// fingerprints, which are already in canonical fingerprint order.
1900    pub fn epoch(&self) -> u64 {
1901        if self.by_id.is_empty() && self.enums_by_id.is_empty() {
1902            return 0;
1903        }
1904        let mut acc: u64 = 0xcbf2_9ce4_8422_2325;
1905        for (n, f) in &self.by_id {
1906            acc = acc.rotate_left(5) ^ schema_fingerprint(n, f);
1907        }
1908        for (n, c) in &self.enums_by_id {
1909            acc = acc.rotate_left(7) ^ schema_fingerprint(n, c).wrapping_mul(0x0000_0100_0000_01b3);
1910        }
1911        acc.max(1) // a non-empty registry is never epoch 0 (0 means "no registry")
1912    }
1913
1914    fn id_of(&self, type_name: &str, field_names: &[String]) -> Option<u32> {
1915        self.by_fp
1916            .get(&schema_fingerprint(type_name, field_names))
1917            .copied()
1918    }
1919    fn schema_of(&self, id: u32) -> Option<(String, Vec<String>)> {
1920        self.by_id.get(id as usize).cloned()
1921    }
1922    /// The enum id + the constructor's index, when this enum type is registered.
1923    fn enum_id_of(&self, type_name: &str, constructor: &str) -> Option<(u32, u32)> {
1924        let id = *self.enums_by_name.get(type_name)?;
1925        let (_, ctors) = self.enums_by_id.get(id as usize)?;
1926        let idx = ctors.iter().position(|c| c == constructor)? as u32;
1927        Some((id, idx))
1928    }
1929    /// The `(type_name, ordered constructors)` for an enum id.
1930    fn enum_schema_of(&self, id: u32) -> Option<(String, Vec<String>)> {
1931        self.enums_by_id.get(id as usize).cloned()
1932    }
1933}
1934
1935thread_local! {
1936    static TYPE_REGISTRY: RefCell<Option<WireTypeRegistry>> = const { RefCell::new(None) };
1937}
1938
1939/// Install a shared type registry for the duration of `f` (consulted by BOTH encode and
1940/// decode). Restores the previous registry on return or panic.
1941pub fn with_type_registry<T>(reg: WireTypeRegistry, f: impl FnOnce() -> T) -> T {
1942    struct Restore(Option<WireTypeRegistry>);
1943    impl Drop for Restore {
1944        fn drop(&mut self) {
1945            TYPE_REGISTRY.with(|c| *c.borrow_mut() = self.0.take());
1946        }
1947    }
1948    let _restore = Restore(TYPE_REGISTRY.with(|c| c.borrow_mut().replace(reg)));
1949    f()
1950}
1951
1952/// The id the active registry assigns this struct/enum schema, if it knows it.
1953fn type_registry_id(type_name: &str, field_names: &[String]) -> Option<u32> {
1954    TYPE_REGISTRY.with(|c| {
1955        c.borrow()
1956            .as_ref()
1957            .and_then(|r| r.id_of(type_name, field_names))
1958    })
1959}
1960
1961/// Resolve a registry id back to its `(type_name, field_names)` schema.
1962fn type_registry_schema(id: u32) -> Option<(String, Vec<String>)> {
1963    TYPE_REGISTRY.with(|c| c.borrow().as_ref().and_then(|r| r.schema_of(id)))
1964}
1965
1966/// The active registry's `(enum_id, constructor_index)` for an enum value, if known.
1967fn type_registry_enum_id(type_name: &str, constructor: &str) -> Option<(u32, u32)> {
1968    TYPE_REGISTRY.with(|c| {
1969        c.borrow()
1970            .as_ref()
1971            .and_then(|r| r.enum_id_of(type_name, constructor))
1972    })
1973}
1974
1975/// Resolve an enum id back to its `(type_name, ordered constructors)`.
1976fn type_registry_enum_schema(id: u32) -> Option<(String, Vec<String>)> {
1977    TYPE_REGISTRY.with(|c| c.borrow().as_ref().and_then(|r| r.enum_schema_of(id)))
1978}
1979
1980thread_local! {
1981    // Rc-DEDUP (G8): when enabled, a subtree that the SAME `Rc` reaches more than once on the value
1982    // graph ships ONCE (`T_SHARED_DEF id + value`) and every later occurrence ships a tiny backref
1983    // (`T_SHARED_REF id`) — and the decoder rebuilds the SHARING (one `Rc`, aliased), not N copies.
1984    // Off by default, so every existing byte-stream is untouched; a value with no actual sharing is
1985    // byte-identical even with the knob on (nothing is in `ENCODE_SHARED`, so no tag is emitted).
1986    static DEDUP_ENABLED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1987    // The set of `Rc` pointers that occur ≥2× — computed once at the root encode. `None` until the
1988    // root gathers it (the lazy-init signal).
1989    static ENCODE_SHARED: RefCell<Option<std::collections::HashSet<usize>>> = const { RefCell::new(None) };
1990    // Shared pointer → the id assigned at its first occurrence (so later occurrences reference it).
1991    static ENCODE_WRITTEN: RefCell<std::collections::HashMap<usize, u64>> =
1992        RefCell::new(std::collections::HashMap::new());
1993    // Decode side: id → the value first decoded under it, so a `T_SHARED_REF` resolves to the SAME
1994    // `Rc` (sharing preserved). Reset at the top of every top-level `message_from_wire`.
1995    static DECODE_MEMO: RefCell<std::collections::HashMap<u64, RuntimeValue>> =
1996        RefCell::new(std::collections::HashMap::new());
1997}
1998
1999/// Encode `f`'s value with Rc-dedup ON: shared subtrees ship once + backrefs. Self-describing by tag,
2000/// so the receiver rebuilds the sharing with no knob of its own. The default (OFF) is byte-unchanged.
2001pub fn with_dedup<T>(enabled: bool, f: impl FnOnce() -> T) -> T {
2002    let prev = DEDUP_ENABLED.with(|c| c.replace(enabled));
2003    ENCODE_SHARED.with(|c| *c.borrow_mut() = None);
2004    ENCODE_WRITTEN.with(|c| c.borrow_mut().clear());
2005    let r = f();
2006    DEDUP_ENABLED.with(|c| c.set(prev));
2007    ENCODE_SHARED.with(|c| *c.borrow_mut() = None);
2008    ENCODE_WRITTEN.with(|c| c.borrow_mut().clear());
2009    r
2010}
2011
2012/// The `Rc`-backed value types whose pointer identity can be shared — the dedup candidates. A
2013/// `usize` address uniquely keys an allocation (two `Rc`s of one allocation share it; distinct
2014/// allocations never collide, even across types).
2015fn shareable_ptr(v: &RuntimeValue) -> Option<usize> {
2016    match v {
2017        RuntimeValue::List(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2018        RuntimeValue::Tuple(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2019        RuntimeValue::Set(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2020        RuntimeValue::Map(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2021        RuntimeValue::Text(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2022        RuntimeValue::BigInt(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2023        RuntimeValue::Rational(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2024        RuntimeValue::Decimal(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2025        RuntimeValue::Money(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2026        RuntimeValue::Complex(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2027        RuntimeValue::Modular(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2028        RuntimeValue::Quantity(rc) => Some(Rc::as_ptr(rc) as *const () as usize),
2029        _ => None,
2030    }
2031}
2032
2033/// Walk the value graph counting `Rc` occurrences; any pointer seen ≥2× goes in `shared`. Cycle-safe
2034/// (a pointer is descended into only on its FIRST sighting; the 2nd marks it shared and stops) and
2035/// depth-bounded (deep non-cyclic nesting just stops gathering — missing a share only costs a little
2036/// size, never correctness).
2037fn gather_shared(
2038    v: &RuntimeValue,
2039    seen: &mut std::collections::HashMap<usize, u32>,
2040    shared: &mut std::collections::HashSet<usize>,
2041    depth: u32,
2042) {
2043    if depth >= MAX_ENCODE_DEPTH {
2044        return;
2045    }
2046    if let Some(p) = shareable_ptr(v) {
2047        let c = seen.entry(p).or_insert(0);
2048        *c += 1;
2049        if *c >= 2 {
2050            shared.insert(p);
2051            return; // already descended on the first sighting — stop (this is the cycle guard too)
2052        }
2053    }
2054    match v {
2055        RuntimeValue::List(rc) => {
2056            if let ListRepr::Boxed(items) = &*rc.borrow() {
2057                for x in items {
2058                    gather_shared(x, seen, shared, depth + 1);
2059                }
2060            }
2061        }
2062        RuntimeValue::Tuple(rc) => {
2063            for x in rc.iter() {
2064                gather_shared(x, seen, shared, depth + 1);
2065            }
2066        }
2067        RuntimeValue::Set(rc) => {
2068            for x in rc.borrow().iter() {
2069                gather_shared(x, seen, shared, depth + 1);
2070            }
2071        }
2072        RuntimeValue::Map(rc) => {
2073            for (k, val) in rc.borrow().iter() {
2074                gather_shared(k, seen, shared, depth + 1);
2075                gather_shared(val, seen, shared, depth + 1);
2076            }
2077        }
2078        RuntimeValue::Struct(b) => {
2079            for val in b.fields.values() {
2080                gather_shared(val, seen, shared, depth + 1);
2081            }
2082        }
2083        RuntimeValue::Inductive(b) => {
2084            for x in &b.args {
2085                gather_shared(x, seen, shared, depth + 1);
2086            }
2087        }
2088        _ => {}
2089    }
2090}
2091
2092/// Does `v` actually alias a subtree — the same `Rc` reached more than once? The auto-tuner asks this
2093/// (one cheap graph walk) to decide whether the dedup candidate is even worth trying; a tree-shaped
2094/// value answers `false` and pays nothing more.
2095fn value_has_sharing(v: &RuntimeValue) -> bool {
2096    let mut seen = std::collections::HashMap::new();
2097    let mut shared = std::collections::HashSet::new();
2098    gather_shared(v, &mut seen, &mut shared, 0);
2099    !shared.is_empty()
2100}
2101
2102/// At the encoder's entry for `v`: if dedup is on, lazily gather the shared set at the root, then —
2103/// for a shared value — emit a backref (and signal `caller returns`) or stamp a fresh def id (and
2104/// let the caller fall through to encode the value normally). Returns `Some(true)` = "I wrote a
2105/// backref, return now", `Some(false)`/`None` = "keep encoding".
2106fn dedup_encode_prefix(v: &RuntimeValue, out: &mut Vec<u8>) -> bool {
2107    if !DEDUP_ENABLED.with(|c| c.get()) {
2108        return false;
2109    }
2110    if ENCODE_SHARED.with(|c| c.borrow().is_none()) {
2111        let mut seen = std::collections::HashMap::new();
2112        let mut shared = std::collections::HashSet::new();
2113        gather_shared(v, &mut seen, &mut shared, 0);
2114        ENCODE_SHARED.with(|c| *c.borrow_mut() = Some(shared));
2115        ENCODE_WRITTEN.with(|c| c.borrow_mut().clear());
2116    }
2117    let Some(p) = shareable_ptr(v) else {
2118        return false;
2119    };
2120    let is_shared = ENCODE_SHARED.with(|c| c.borrow().as_ref().is_some_and(|s| s.contains(&p)));
2121    if !is_shared {
2122        return false;
2123    }
2124    if let Some(id) = ENCODE_WRITTEN.with(|c| c.borrow().get(&p).copied()) {
2125        out.push(T_SHARED_REF);
2126        write_uvarint(id, out);
2127        return true; // a backref — caller returns
2128    }
2129    let id = ENCODE_WRITTEN.with(|c| {
2130        let mut m = c.borrow_mut();
2131        let id = m.len() as u64;
2132        m.insert(p, id);
2133        id
2134    });
2135    out.push(T_SHARED_DEF);
2136    write_uvarint(id, out);
2137    false // first occurrence — caller encodes the value normally after this def header
2138}
2139
2140thread_local! {
2141    static STRUCT_VIEW: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
2142    // Current value-recursion depth of the encoder — bounds nesting so a cyclic value
2143    // (only constructible via the `Rc<RefCell<…>>` a List wraps) returns a clean Err
2144    // instead of overflowing the stack. Reset to 0 by the guard as the recursion unwinds.
2145    static ENCODE_DEPTH: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
2146}
2147
2148// Bounds value-recursion depth (NESTING, not breadth — a million-element list is depth 2).
2149// 128 levels is far beyond any real payload yet safe on the small stacks this runs on: an
2150// unoptimized `native_encode`/`encode_list_repr` frame is several KiB in debug, so the cap
2151// must stay well under a 2 MiB worker/test or ~1 MiB wasm stack — 128 leaves a wide margin.
2152const MAX_ENCODE_DEPTH: u32 = 128;
2153
2154/// Decompression-bomb budget for DECODE paths where a length prefix is NOT gated by
2155/// per-element input bytes (e.g. an RLE/dict run that repeats one element `n` times in zero
2156/// bytes). A crafted frame could otherwise request billions of copies from a tiny input and
2157/// OOM the peer. 256 MiB is far beyond any real peer message yet bounds the blast radius
2158/// (Invariant X). Length-gated paths are already bounded by the remaining buffer.
2159const MAX_DECODE_BYTES: usize = 1 << 28;
2160
2161/// RAII depth counter for the recursive encoder. `enter()` fails (rather than recursing
2162/// into a stack overflow) once nesting passes [`MAX_ENCODE_DEPTH`]; `Drop` unwinds it.
2163struct DepthGuard;
2164impl DepthGuard {
2165    fn enter() -> Result<DepthGuard, String> {
2166        ENCODE_DEPTH.with(|d| {
2167            let n = d.get();
2168            if n >= MAX_ENCODE_DEPTH {
2169                return Err(format!(
2170                    "value nested deeper than {MAX_ENCODE_DEPTH} (cyclic or pathological) — not encodable"
2171                ));
2172            }
2173            d.set(n + 1);
2174            Ok(DepthGuard)
2175        })
2176    }
2177}
2178impl Drop for DepthGuard {
2179    fn drop(&mut self) {
2180        ENCODE_DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
2181    }
2182}
2183
2184/// RAII depth counter for the recursive DECODER — the admission gate against a stack-smashing message.
2185/// `enter()` returns `None` (so `native_decode` rejects cleanly via `?`) once nesting reaches the
2186/// receiver's [`ReceiveLimits::max_depth`], instead of recursing into a stack overflow on a crafted
2187/// deeply-nested payload. `Drop` unwinds the count on every path — normal return, `?`-`None`, and panic
2188/// unwind — so a fresh top-level decode always starts at zero.
2189struct DecodeDepthGuard;
2190impl DecodeDepthGuard {
2191    fn enter() -> Option<DecodeDepthGuard> {
2192        DECODE_DEPTH.with(|d| {
2193            let n = d.get();
2194            if n >= receive_limits().max_depth {
2195                return None;
2196            }
2197            d.set(n + 1);
2198            Some(DecodeDepthGuard)
2199        })
2200    }
2201}
2202impl Drop for DecodeDepthGuard {
2203    fn drop(&mut self) {
2204        DECODE_DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
2205    }
2206}
2207
2208/// Encode structs in the offset-table `T_STRUCT_VIEW` layout for the duration of `f`, so a
2209/// `WireView` reads any single field in O(1) (the Cap'n Proto-beating random-access mode).
2210/// Larger than the packed forms — it is the speed end of the size↔speed dial.
2211pub fn with_struct_view<T>(on: bool, f: impl FnOnce() -> T) -> T {
2212    let prev = STRUCT_VIEW.with(|c| c.replace(on));
2213    let out = f();
2214    STRUCT_VIEW.with(|c| c.set(prev));
2215    out
2216}
2217
2218fn struct_view_on() -> bool {
2219    STRUCT_VIEW.with(std::cell::Cell::get)
2220}
2221
2222// ---- The native single-pass codec: RuntimeValue <-> tagged-varint bytes -----
2223//
2224// One byte of type tag, then a varint/utf8 payload. Signed integers are
2225// zig-zag + LEB128 (small magnitudes cost a byte); lengths are LEB128. Structs
2226// and maps are written in canonical order (fields by name, entries by key bytes)
2227// so the encoding is deterministic. Non-portable values (closures, scheduler
2228// handles) are rejected here, on the spot.
2229
2230// =====================================================================================
2231// Zero-copy WireView — read ONE field/element without decoding the whole message.
2232// Borrows the wire bytes (`&'a [u8]`); a fixed-width array element is read in O(1) at its
2233// byte offset with ZERO allocation. Matches Cap'n Proto / Arrow random-access (O(1), no
2234// parse) while staying varint-small — `Send fast` (the fixed layout) is the zero-copy one.
2235// =====================================================================================
2236
2237/// Advance `pos` past a length-prefixed string without allocating it.
2238fn skip_str(buf: &[u8], pos: &mut usize) -> Option<()> {
2239    let n = read_uvarint(buf, pos)? as usize;
2240    let end = pos.checked_add(n)?;
2241    if end > buf.len() {
2242        return None;
2243    }
2244    *pos = end;
2245    Some(())
2246}
2247
2248/// Decode a received message LAZILY when its top-level value is a self-describing record-list view
2249/// (`T_STRUCTS_VIEW`): returns `(sender, List(WireStructs))` holding the raw frame, so NO row is
2250/// decoded until a field is touched — the production zero-copy receive ("no decode in production",
2251/// Cap'n Proto's home). Any other shape (scalars, maps, single structs, cached/compressed bodies)
2252/// falls back to a full [`message_from_wire`] decode, so every message still round-trips. The
2253/// receiver opts in with the `view` knob; without it, the eager path is used exactly as before.
2254/// Peek a frame: if its top-level value is a self-describing DEFERRABLE view — a record list
2255/// (`T_STRUCTS_VIEW`) or an aligned numeric column (`T_INTS_ALIGNED`/`T_FLOATS_ALIGNED`), all of
2256/// which have no schema-cache dependency — return the sender so its decode can be deferred to
2257/// `Await` (lazy under `view`, eager otherwise). `None` for anything else (scalars, single structs,
2258/// maps, cached, or compressed bodies), which must decode eagerly in arrival order. The drain loop
2259/// uses this to split deferrable views from order-sensitive messages.
2260pub fn peek_deferrable_sender(bytes: &[u8]) -> Option<String> {
2261    let view = view_message(bytes)?;
2262    let deferrable = view.structs_schema().is_some()
2263        || matches!(view.tag(), Some(T_INTS_ALIGNED) | Some(T_FLOATS_ALIGNED));
2264    if !deferrable {
2265        return None;
2266    }
2267    let (_, _, body) = unframe_with(bytes, false)?;
2268    let mut p = 0;
2269    read_str(body, &mut p)
2270}
2271
2272pub fn message_from_wire_view(bytes: &[u8]) -> Option<(String, RuntimeValue)> {
2273    // Only a self-describing native record-list view is lazily wrappable.
2274    if view_message(bytes)
2275        .and_then(|v| v.structs_schema())
2276        .is_some()
2277    {
2278        let (_, _, body) = unframe_with(bytes, false)?;
2279        let mut p = 0;
2280        let sender = read_str(body, &mut p)?; // the sender prefix at the body's head
2281        let lazy = crate::interpreter::ListRepr::from_record_list_view(Rc::new(bytes.to_vec()))?;
2282        return Some((sender, RuntimeValue::List(Rc::new(RefCell::new(lazy)))));
2283    }
2284    message_from_wire(bytes)
2285}
2286
2287/// The leading marker of a batch STREAM message — distinct from any normal frame header (those use
2288/// only the low bits 0x01/0x02/0x10 + a 2-bit compression id) and from a FEC shard (0xFE), so the
2289/// drain loop tells the three apart by the first byte alone.
2290const STREAM_MAGIC: u8 = 0xFD;
2291
2292/// Frame a sequence of values as one batch STREAM message: `[magic][sender][framed value-message]*`,
2293/// each value length-delimited (via [`crate::concurrency::stream::frame_for_stream`]) so the
2294/// receiver deframes them incrementally and reads each in place. ONE relay publish ships the whole
2295/// batch — Kafka-style streaming that amortizes per-message overhead — and `Await stream` reassembles
2296/// the list. Each value is encoded self-describingly so it round-trips without the type registry.
2297pub fn frame_stream_message(from: &str, values: &[RuntimeValue]) -> Result<Vec<u8>, String> {
2298    let mut out = vec![STREAM_MAGIC];
2299    write_str(from, &mut out);
2300    for v in values {
2301        let elem = message_to_wire("", v)?;
2302        crate::concurrency::stream::frame_for_stream(&elem, &mut out);
2303    }
2304    Ok(out)
2305}
2306
2307/// Is `bytes` a batch stream message? If so, return its sender (so `Await stream … from <peer>`
2308/// matches it). `None` for a normal message / FEC shard / anything else.
2309pub fn peek_stream_sender(bytes: &[u8]) -> Option<String> {
2310    if bytes.first() != Some(&STREAM_MAGIC) {
2311        return None;
2312    }
2313    let mut p = 1;
2314    read_str(bytes, &mut p)
2315}
2316
2317/// Deframe a batch stream message into its values, in order. `None` if `bytes` is not a stream
2318/// message; a frame that fails to decode is skipped (never a panic).
2319pub fn deframe_stream_message(bytes: &[u8]) -> Option<Vec<RuntimeValue>> {
2320    if bytes.first() != Some(&STREAM_MAGIC) {
2321        return None;
2322    }
2323    let mut p = 1;
2324    read_str(bytes, &mut p)?;
2325    let mut deframer = crate::concurrency::stream::StreamDeframer::new();
2326    deframer.push(bytes.get(p..)?);
2327    let mut values = Vec::new();
2328    deframer.drain_frames(|frame| {
2329        if let Some((_, v)) = message_from_wire(frame) {
2330            values.push(v);
2331        }
2332    });
2333    Some(values)
2334}
2335
2336/// A borrowed view over one wire message's top-level value. Holds no owned data and never
2337/// decodes the rest of the message; reads are in place. Open it with [`view_message`].
2338#[derive(Clone, Copy)]
2339pub struct WireView<'a> {
2340    /// Slice starting at the top-level value's tag byte.
2341    val: &'a [u8],
2342}
2343
2344/// Open a borrowed, zero-alloc view over `bytes`. `None` for a compressed or JSON message
2345/// (those must be inflated/decoded first — the view is over raw native bytes) or a
2346/// malformed frame. Reads any single field in place afterward.
2347pub fn view_message(bytes: &[u8]) -> Option<WireView<'_>> {
2348    // `verify = false`: opening a view is O(1) even on a checksummed message — validating
2349    // the FNV sum would re-hash the whole body, defeating zero-copy random access. The view
2350    // trusts the bytes (Cap'n Proto / Arrow have no checksum at all); callers wanting
2351    // integrity use a full decode, which validates.
2352    let (codec, compression, body) = unframe_with(bytes, false)?;
2353    if !matches!(codec, WireCodec::Native) || compression != WireCompression::None {
2354        return None;
2355    }
2356    let mut pos = 0;
2357    skip_str(body, &mut pos)?; // skip the sender prefix
2358    Some(WireView {
2359        val: body.get(pos..)?,
2360    })
2361}
2362
2363impl<'a> WireView<'a> {
2364    fn tag(&self) -> Option<u8> {
2365        self.val.first().copied()
2366    }
2367
2368    /// The top-level value as an integer (`T_INT`).
2369    pub fn as_int(&self) -> Option<i64> {
2370        if self.tag()? != T_INT {
2371            return None;
2372        }
2373        let mut p = 1;
2374        Some(unzigzag(read_uvarint(self.val, &mut p)?))
2375    }
2376
2377    /// The top-level value as a float (`T_FLOAT`).
2378    pub fn as_float(&self) -> Option<f64> {
2379        if self.tag()? != T_FLOAT {
2380            return None;
2381        }
2382        let b = self.val.get(1..9)?;
2383        Some(f64::from_le_bytes(b.try_into().ok()?))
2384    }
2385
2386    /// Read ONE field of an offset-table struct view (`T_STRUCT_VIEW`): scan the small
2387    /// name table, then jump to the field's value via the offset table — WITHOUT parsing
2388    /// any other field, however large. The Cap'n Proto-class random-access read; returns
2389    /// a sub-view you read with `as_int`/`as_float`/etc. `None` if not a view or no field.
2390    pub fn struct_field(&self, name: &str) -> Option<WireView<'a>> {
2391        if self.tag()? != T_STRUCT_VIEW {
2392            return None;
2393        }
2394        let mut p = 1;
2395        skip_str(self.val, &mut p)?; // type_name
2396        let count = read_uvarint(self.val, &mut p)? as usize;
2397        let mut idx = None;
2398        for i in 0..count {
2399            let nlen = read_uvarint(self.val, &mut p)? as usize;
2400            let nbytes = self.val.get(p..p.checked_add(nlen)?)?;
2401            if nbytes == name.as_bytes() {
2402                idx = Some(i);
2403            }
2404            p += nlen;
2405        }
2406        let idx = idx?;
2407        let table_pos = p;
2408        let off_at = table_pos.checked_add(idx.checked_mul(4)?)?;
2409        let off_bytes = self.val.get(off_at..off_at.checked_add(4)?)?;
2410        let offset = u32::from_le_bytes(off_bytes.try_into().ok()?) as usize;
2411        let values_start = table_pos.checked_add(count.checked_mul(4)?)?;
2412        Some(WireView {
2413            val: self.val.get(values_start.checked_add(offset)?..)?,
2414        })
2415    }
2416
2417    /// Read an 8-byte-aligned i64 column (`T_INTS_ALIGNED`) as `&[i64]` with ZERO copy —
2418    /// the in-place column read (the kernel-bypass / RDMA path: no per-element decode, no
2419    /// `memcpy`). `None` if it is not an aligned column or the bytes are not 8-aligned in
2420    /// this buffer (then the caller decodes/copies instead, still one `memcpy`).
2421    pub fn as_i64_slice(&self) -> Option<&'a [i64]> {
2422        if self.tag()? != T_INTS_ALIGNED {
2423            return None;
2424        }
2425        let mut p = 1;
2426        let n = read_uvarint(self.val, &mut p)? as usize;
2427        let pad = *self.val.get(p)? as usize;
2428        p += 1 + pad;
2429        let nbytes = n.checked_mul(8)?;
2430        let blob = self.val.get(p..p.checked_add(nbytes)?)?;
2431        if blob.as_ptr() as usize % 8 != 0 {
2432            return None; // not 8-aligned in this buffer → caller copies
2433        }
2434        // SAFETY: `blob` is exactly `n*8` bytes, 8-byte aligned, borrowed for `'a`; every
2435        // bit pattern is a valid `i64`.
2436        Some(unsafe { std::slice::from_raw_parts(blob.as_ptr().cast::<i64>(), n) })
2437    }
2438
2439    /// Read an 8-byte-aligned f64 column (`T_FLOATS_ALIGNED`) as `&[f64]` with ZERO copy —
2440    /// the float twin of [`as_i64_slice`](Self::as_i64_slice). `None` if it is not an
2441    /// aligned float column or the bytes are not 8-aligned in this buffer (caller copies).
2442    pub fn as_f64_slice(&self) -> Option<&'a [f64]> {
2443        if self.tag()? != T_FLOATS_ALIGNED {
2444            return None;
2445        }
2446        let mut p = 1;
2447        let n = read_uvarint(self.val, &mut p)? as usize;
2448        let pad = *self.val.get(p)? as usize;
2449        p += 1 + pad;
2450        let nbytes = n.checked_mul(8)?;
2451        let blob = self.val.get(p..p.checked_add(nbytes)?)?;
2452        if blob.as_ptr() as usize % 8 != 0 {
2453            return None; // not 8-aligned in this buffer → caller copies
2454        }
2455        // SAFETY: `blob` is exactly `n*8` bytes, 8-byte aligned, borrowed for `'a`; every
2456        // bit pattern is a valid `f64` (NaN/Inf/subnormal included — all read verbatim).
2457        Some(unsafe { std::slice::from_raw_parts(blob.as_ptr().cast::<f64>(), n) })
2458    }
2459
2460    /// Read field `fi` of a COLUMNAR fixed struct list (`T_STRUCTS` whose every column is a
2461    /// `T_INTS_FIXED` blob) as its contiguous little-endian `i64` bytes — zero-copy, no per-cell
2462    /// navigation and no materialization. Because the layout is columnar, ALL of field `fi` is
2463    /// adjacent, so summing/scanning it is one CACHE-FRIENDLY contiguous pass — the columnar-
2464    /// analytics win over a row-major reader's strided walk (Cap'n Proto interleaves the fields).
2465    /// `None` if the value is not a columnar all-fixed struct list, or `fi` is out of range.
2466    pub fn structs_fixed_i64_col(&self, fi: usize) -> Option<&'a [u8]> {
2467        if self.tag()? != T_STRUCTS {
2468            return None;
2469        }
2470        let mut p = 1;
2471        skip_str(self.val, &mut p)?; // type_name
2472        let k = read_uvarint(self.val, &mut p)? as usize;
2473        if fi >= k {
2474            return None;
2475        }
2476        for _ in 0..k {
2477            skip_str(self.val, &mut p)?; // field names
2478        }
2479        let n = read_uvarint(self.val, &mut p)? as usize;
2480        for c in 0..=fi {
2481            if *self.val.get(p)? != T_INTS_FIXED {
2482                return None; // a non-fixed column — this fast path needs all columns fixed-width
2483            }
2484            p += 1;
2485            let cnt = read_uvarint(self.val, &mut p)? as usize;
2486            if cnt != n {
2487                return None;
2488            }
2489            let nbytes = n.checked_mul(8)?;
2490            let blob = self.val.get(p..p.checked_add(nbytes)?)?;
2491            if c == fi {
2492                return Some(blob);
2493            }
2494            p += nbytes;
2495        }
2496        None
2497    }
2498
2499    /// Read a byte column (`T_BYTES`) as `&[u8]` with ZERO copy — binary data (hashes, file
2500    /// chunks, crypto) read in place: no decode, no allocation, no `i64` expansion, and
2501    /// (unlike the i64/f64 columns) no alignment requirement, since a `u8` slice is always
2502    /// 1-aligned. The first-class `bytes`/`Data` read that bit-packing can never offer.
2503    /// `None` if this is not a byte column.
2504    pub fn as_byte_slice(&self) -> Option<&'a [u8]> {
2505        if self.tag()? != T_BYTES {
2506            return None;
2507        }
2508        let mut p = 1;
2509        let n = read_uvarint(self.val, &mut p)? as usize;
2510        self.val.get(p..p.checked_add(n)?)
2511    }
2512
2513    /// Row count of a record-list view (variable `T_STRUCTS_VIEW` or fixed `T_STRUCTS_FVIEW`),
2514    /// or `None` if not one.
2515    pub fn structs_len(&self) -> Option<usize> {
2516        let tag = self.tag()?;
2517        if tag != T_STRUCTS_VIEW && tag != T_STRUCTS_FVIEW {
2518            return None;
2519        }
2520        let mut p = 1;
2521        skip_str(self.val, &mut p)?; // type_name
2522        let f = read_uvarint(self.val, &mut p)? as usize;
2523        for _ in 0..f {
2524            let nlen = read_uvarint(self.val, &mut p)? as usize;
2525            p = p.checked_add(nlen)?;
2526        }
2527        if tag == T_STRUCTS_FVIEW {
2528            p = p.checked_add(f)?; // skip the F kind bytes
2529        }
2530        Some(read_uvarint(self.val, &mut p)? as usize)
2531    }
2532
2533    /// Read field `name` of row `row` in a record-list view (`T_STRUCTS_VIEW`) in O(1):
2534    /// scan the shared name table once for the field index, jump via the row-offset table
2535    /// to the row block, then via that row's field-offset table to the value — NEVER parsing
2536    /// the other rows or fields, however large the list. The Cap'n Proto-class random access
2537    /// into a record list. Returns a sub-view (`as_int`/`as_float`/…). `None` if not a record
2538    /// view, the row is out of range, or no such field.
2539    pub fn structs_row_field(&self, row: usize, name: &str) -> Option<WireView<'a>> {
2540        if self.tag()? != T_STRUCTS_VIEW {
2541            return None;
2542        }
2543        let mut p = 1;
2544        skip_str(self.val, &mut p)?; // type_name
2545        let f = read_uvarint(self.val, &mut p)? as usize;
2546        let mut field_idx = None;
2547        for i in 0..f {
2548            let nlen = read_uvarint(self.val, &mut p)? as usize;
2549            let nbytes = self.val.get(p..p.checked_add(nlen)?)?;
2550            if nbytes == name.as_bytes() {
2551                field_idx = Some(i);
2552            }
2553            p += nlen;
2554        }
2555        let fi = field_idx?;
2556        let n = read_uvarint(self.val, &mut p)? as usize;
2557        if row >= n {
2558            return None;
2559        }
2560        let row_table_pos = p;
2561        let rows_start = row_table_pos.checked_add(n.checked_mul(4)?)?;
2562        let row_off_at = row_table_pos.checked_add(row.checked_mul(4)?)?;
2563        let row_off = u32::from_le_bytes(
2564            self.val
2565                .get(row_off_at..row_off_at.checked_add(4)?)?
2566                .try_into()
2567                .ok()?,
2568        ) as usize;
2569        let field_table_pos = rows_start.checked_add(row_off)?;
2570        let values_start = field_table_pos.checked_add(f.checked_mul(4)?)?;
2571        let field_off_at = field_table_pos.checked_add(fi.checked_mul(4)?)?;
2572        let field_off = u32::from_le_bytes(
2573            self.val
2574                .get(field_off_at..field_off_at.checked_add(4)?)?
2575                .try_into()
2576                .ok()?,
2577        ) as usize;
2578        Some(WireView {
2579            val: self.val.get(values_start.checked_add(field_off)?..)?,
2580        })
2581    }
2582
2583    /// Read field `name` of row `row` as an owned value for EITHER record-list view: the variable
2584    /// offset-table view (`T_STRUCTS_VIEW`) or the fixed-stride view (`T_STRUCTS_FVIEW`). Both are
2585    /// O(1) random access — the fixed view by pure arithmetic (no offset tables). Numeric/bool
2586    /// reads allocate nothing; a text read materializes the one string. The unified read the lazy
2587    /// `Await view` backing uses, so a peer's `Send indexed` (either layout) reads the same way.
2588    /// `None` if this is not a record view, the row is out of range, or there is no such field.
2589    pub fn structs_row_field_value(&self, row: usize, name: &str) -> Option<RuntimeValue> {
2590        match self.tag()? {
2591            T_STRUCTS_VIEW => self.structs_row_field(row, name)?.decode(),
2592            T_STRUCTS_FVIEW => {
2593                let mut p = 1;
2594                skip_str(self.val, &mut p)?; // type_name
2595                let f = read_uvarint(self.val, &mut p)? as usize;
2596                let mut field_idx = None;
2597                for i in 0..f {
2598                    let nlen = read_uvarint(self.val, &mut p)? as usize;
2599                    let nbytes = self.val.get(p..p.checked_add(nlen)?)?;
2600                    if nbytes == name.as_bytes() {
2601                        field_idx = Some(i);
2602                    }
2603                    p += nlen;
2604                }
2605                let fi = field_idx?;
2606                let kinds = self.val.get(p..p.checked_add(f)?)?;
2607                p += f;
2608                let n = read_uvarint(self.val, &mut p)? as usize;
2609                if row >= n {
2610                    return None;
2611                }
2612                let (offsets, stride) = fview_layout(kinds);
2613                let rows_start = p;
2614                let cell_pos = rows_start
2615                    .checked_add(row.checked_mul(stride)?)?
2616                    .checked_add(offsets[fi])?;
2617                // The string blob follows the fixed rows; its length varint sits right after them.
2618                let mut bp = rows_start.checked_add(n.checked_mul(stride)?)?;
2619                let blob_len = read_uvarint(self.val, &mut bp)? as usize;
2620                let blob = self.val.get(bp..bp.checked_add(blob_len)?)?;
2621                fview_read_cell(kinds[fi], self.val.get(cell_pos..)?, blob)
2622            }
2623            _ => None,
2624        }
2625    }
2626
2627    /// Fully decode the ONE value this view points at (a cell / field / element) into an owned
2628    /// `RuntimeValue` — the materialize-on-touch step a lazy reader runs after locating a field in
2629    /// place. Decodes only this value, never the rest of the message; the bytes outside it stay
2630    /// untouched. (Uses the ambient type registry, so it round-trips name-elided cells too.)
2631    pub fn decode(&self) -> Option<RuntimeValue> {
2632        let mut p = 0;
2633        native_decode(self.val, &mut p)
2634    }
2635
2636    /// The schema of a record-list view (`T_STRUCTS_VIEW`): `(type_name, field_names, row_count)`,
2637    /// read from the shared header WITHOUT decoding a single row — so a lazy backing can carry the
2638    /// schema + length while the row bytes stay un-decoded until a field is touched. `None` if this
2639    /// is not a record-list view.
2640    pub fn structs_schema(&self) -> Option<(String, Vec<String>, usize)> {
2641        let tag = self.tag()?;
2642        if tag != T_STRUCTS_VIEW && tag != T_STRUCTS_FVIEW {
2643            return None;
2644        }
2645        let mut p = 1;
2646        let type_name = read_str(self.val, &mut p)?;
2647        let f = read_uvarint(self.val, &mut p)? as usize;
2648        // Cap the pre-allocation by remaining input: an untrusted field count can't exceed
2649        // the bytes left, so a hostile `f` can't trigger a giant OOM allocation (Invariant X).
2650        let mut field_names = Vec::with_capacity(f.min(self.val.len().saturating_sub(p)));
2651        for _ in 0..f {
2652            field_names.push(read_str(self.val, &mut p)?);
2653        }
2654        if tag == T_STRUCTS_FVIEW {
2655            p = p.checked_add(f)?; // skip the F kind bytes
2656        }
2657        let n = read_uvarint(self.val, &mut p)? as usize;
2658        Some((type_name, field_names, n))
2659    }
2660
2661    /// The top-level value as a bool.
2662    pub fn as_bool(&self) -> Option<bool> {
2663        match self.tag()? {
2664            T_TRUE => Some(true),
2665            T_FALSE => Some(false),
2666            _ => None,
2667        }
2668    }
2669
2670    /// Element count of a homogeneous int list (`T_INTS` varint or `T_INTS_FIXED`).
2671    pub fn int_list_len(&self) -> Option<usize> {
2672        let mut p = 1;
2673        match self.tag()? {
2674            T_INTS_FIXED => Some(read_uvarint(self.val, &mut p)? as usize),
2675            T_INTS => Some((read_uvarint(self.val, &mut p)? >> 1) as usize),
2676            _ => None,
2677        }
2678    }
2679
2680    /// Element `i` of an int list — O(1) + ZERO ALLOC for the fixed layout (seek to the
2681    /// byte offset, read 8 bytes); O(i) scan for the varint layout, still no full decode.
2682    pub fn int_list_get(&self, i: usize) -> Option<i64> {
2683        match self.tag()? {
2684            T_INTS_FIXED => {
2685                let mut p = 1;
2686                let n = read_uvarint(self.val, &mut p)? as usize;
2687                if i >= n {
2688                    return None;
2689                }
2690                let off = p + i * 8; // O(1): direct seek to element i
2691                let b = self.val.get(off..off + 8)?;
2692                Some(i64::from_le_bytes(b.try_into().ok()?))
2693            }
2694            T_INTS => {
2695                let mut p = 1;
2696                let header = read_uvarint(self.val, &mut p)?;
2697                let signed = header & 1 == 1;
2698                let n = (header >> 1) as usize;
2699                if i >= n {
2700                    return None;
2701                }
2702                for _ in 0..i {
2703                    read_uvarint(self.val, &mut p)?;
2704                }
2705                let u = read_uvarint(self.val, &mut p)?;
2706                Some(if signed { unzigzag(u) } else { u as i64 })
2707            }
2708            _ => None,
2709        }
2710    }
2711
2712    /// Element count of a memcpy float list (`T_FLOATS`).
2713    pub fn float_list_len(&self) -> Option<usize> {
2714        if self.tag()? != T_FLOATS {
2715            return None;
2716        }
2717        let mut p = 1;
2718        Some(read_uvarint(self.val, &mut p)? as usize)
2719    }
2720
2721    /// Element `i` of a memcpy float list — O(1), zero alloc.
2722    pub fn float_list_get(&self, i: usize) -> Option<f64> {
2723        if self.tag()? != T_FLOATS {
2724            return None;
2725        }
2726        let mut p = 1;
2727        let n = read_uvarint(self.val, &mut p)? as usize;
2728        if i >= n {
2729            return None;
2730        }
2731        let off = p + i * 8;
2732        let b = self.val.get(off..off + 8)?;
2733        Some(f64::from_le_bytes(b.try_into().ok()?))
2734    }
2735
2736    /// Open a parse-ONCE bulk cursor over a record-list view (either layout). `None` if this is not
2737    /// a record-list view. Use it to read a WHOLE list as fast as Cap'n Proto's lazy reader:
2738    /// `structs_row_field_value` re-parses the header on every call (fine for one read, O(n·f) for a
2739    /// full scan), whereas the cursor parses the schema/tables once and every access is O(1).
2740    pub fn structs_cursor(&self) -> Option<WireStructsCursor<'a>> {
2741        let tag = self.tag()?;
2742        if tag != T_STRUCTS_VIEW && tag != T_STRUCTS_FVIEW {
2743            return None;
2744        }
2745        let val = self.val;
2746        let mut p = 1;
2747        let tn_len = read_uvarint(val, &mut p)? as usize; // type_name (skipped)
2748        p = p.checked_add(tn_len)?;
2749        let f = read_uvarint(val, &mut p)? as usize;
2750        // Cap the pre-allocation by remaining input (untrusted field count ⇒ OOM guard, Invariant X).
2751        let mut field_names = Vec::with_capacity(f.min(val.len().saturating_sub(p)));
2752        for _ in 0..f {
2753            let nlen = read_uvarint(val, &mut p)? as usize;
2754            field_names.push(val.get(p..p.checked_add(nlen)?)?);
2755            p += nlen;
2756        }
2757        if tag == T_STRUCTS_FVIEW {
2758            let field_kinds = val.get(p..p.checked_add(f)?)?;
2759            p += f;
2760            let n = read_uvarint(val, &mut p)? as usize;
2761            let (field_offsets, stride) = fview_layout(field_kinds);
2762            let rows_start = p;
2763            let mut bp = rows_start.checked_add(n.checked_mul(stride)?)?;
2764            let blob_len = read_uvarint(val, &mut bp)? as usize;
2765            let blob = val.get(bp..bp.checked_add(blob_len)?)?;
2766            Some(WireStructsCursor {
2767                val,
2768                field_names,
2769                n,
2770                kind: CursorKind::Fixed {
2771                    field_kinds,
2772                    field_offsets,
2773                    stride,
2774                    rows_start,
2775                    blob,
2776                },
2777            })
2778        } else {
2779            let n = read_uvarint(val, &mut p)? as usize;
2780            let row_table_pos = p;
2781            let rows_start = row_table_pos.checked_add(n.checked_mul(4)?)?;
2782            Some(WireStructsCursor {
2783                val,
2784                field_names,
2785                n,
2786                kind: CursorKind::Variable {
2787                    row_table_pos,
2788                    rows_start,
2789                },
2790            })
2791        }
2792    }
2793}
2794
2795/// A parse-once cursor over a record-list view (`T_STRUCTS_VIEW` / `T_STRUCTS_FVIEW`): the schema
2796/// and tables are read ONCE at open, then every `(row, field)` access is O(1) — pure arithmetic for
2797/// the fixed-stride view, a two-`u32` offset jump for the variable view — with no per-call re-scan.
2798pub struct WireStructsCursor<'a> {
2799    val: &'a [u8],
2800    field_names: Vec<&'a [u8]>,
2801    n: usize,
2802    kind: CursorKind<'a>,
2803}
2804
2805enum CursorKind<'a> {
2806    Variable {
2807        row_table_pos: usize,
2808        rows_start: usize,
2809    },
2810    Fixed {
2811        field_kinds: &'a [u8],
2812        field_offsets: Vec<usize>,
2813        stride: usize,
2814        rows_start: usize,
2815        blob: &'a [u8],
2816    },
2817}
2818
2819impl<'a> WireStructsCursor<'a> {
2820    pub fn len(&self) -> usize {
2821        self.n
2822    }
2823    pub fn is_empty(&self) -> bool {
2824        self.n == 0
2825    }
2826    pub fn field_count(&self) -> usize {
2827        self.field_names.len()
2828    }
2829    /// Index of the field named `name`, scanned once by the caller and then reused for every row.
2830    pub fn field_index(&self, name: &str) -> Option<usize> {
2831        self.field_names.iter().position(|&n| n == name.as_bytes())
2832    }
2833
2834    /// Byte slice at the start of cell `(row, fi)` — the one arithmetic/offset step both layouts share.
2835    fn cell_slice(&self, row: usize, fi: usize) -> Option<&'a [u8]> {
2836        if row >= self.n || fi >= self.field_names.len() {
2837            return None;
2838        }
2839        match &self.kind {
2840            CursorKind::Fixed {
2841                field_offsets,
2842                stride,
2843                rows_start,
2844                ..
2845            } => {
2846                let pos = rows_start
2847                    .checked_add(row.checked_mul(*stride)?)?
2848                    .checked_add(field_offsets[fi])?;
2849                self.val.get(pos..)
2850            }
2851            CursorKind::Variable {
2852                row_table_pos,
2853                rows_start,
2854            } => {
2855                let f = self.field_names.len();
2856                let row_off_at = row_table_pos.checked_add(row.checked_mul(4)?)?;
2857                let row_off = u32::from_le_bytes(
2858                    self.val
2859                        .get(row_off_at..row_off_at.checked_add(4)?)?
2860                        .try_into()
2861                        .ok()?,
2862                ) as usize;
2863                let field_table_pos = rows_start.checked_add(row_off)?;
2864                let values_start = field_table_pos.checked_add(f.checked_mul(4)?)?;
2865                let field_off_at = field_table_pos.checked_add(fi.checked_mul(4)?)?;
2866                let field_off = u32::from_le_bytes(
2867                    self.val
2868                        .get(field_off_at..field_off_at.checked_add(4)?)?
2869                        .try_into()
2870                        .ok()?,
2871                ) as usize;
2872                self.val.get(values_start.checked_add(field_off)?..)
2873            }
2874        }
2875    }
2876
2877    /// The `(row, field)` value as an owned `RuntimeValue` — O(1), no header re-scan.
2878    pub fn value(&self, row: usize, fi: usize) -> Option<RuntimeValue> {
2879        match &self.kind {
2880            CursorKind::Fixed {
2881                field_kinds, blob, ..
2882            } => fview_read_cell(*field_kinds.get(fi)?, self.cell_slice(row, fi)?, blob),
2883            CursorKind::Variable { .. } => {
2884                let cell = self.cell_slice(row, fi)?;
2885                let mut q = 0;
2886                native_decode(cell, &mut q)
2887            }
2888        }
2889    }
2890
2891    /// Read an ENTIRE int field of the fixed-stride view as a `Vec<i64>` in one tight pass — the
2892    /// Cap'n-Proto-class read-all: the cells are at a fixed `offset` every `stride` bytes, so after
2893    /// one bounds check on the last cell, the reads are unchecked raw 8-byte loads (no per-read
2894    /// bounds check, no slice indirection, no `RuntimeValue` box). `None` for the variable view
2895    /// (varint cells aren't strided) or a non-int field.
2896    pub fn i64_column(&self, fi: usize) -> Option<Vec<i64>> {
2897        let CursorKind::Fixed {
2898            field_kinds,
2899            field_offsets,
2900            stride,
2901            rows_start,
2902            ..
2903        } = &self.kind
2904        else {
2905            return None;
2906        };
2907        if *field_kinds.get(fi)? != FK_INT {
2908            return None;
2909        }
2910        let base = rows_start.checked_add(field_offsets[fi])?;
2911        let mut out = Vec::with_capacity(self.n);
2912        if self.n > 0 {
2913            // The extreme cell `base + (n-1)*stride .. +8` ⊆ val; every earlier cell is below it,
2914            // so this single check makes every loop read in-bounds.
2915            let last = base.checked_add((self.n - 1).checked_mul(*stride)?)?;
2916            self.val.get(last..last.checked_add(8)?)?;
2917            let ptr = self.val.as_ptr();
2918            for r in 0..self.n {
2919                let p = base + r * stride;
2920                // SAFETY: `p..p+8` ⊆ `[rows_start, rows_start + n*stride)` ⊆ `val` (checked above).
2921                let b = unsafe { std::slice::from_raw_parts(ptr.add(p), 8) };
2922                out.push(i64::from_le_bytes(b.try_into().unwrap()));
2923            }
2924        }
2925        Some(out)
2926    }
2927
2928    /// Fast integer read of an int cell — the Cap'n-Proto-class random read: pure arithmetic + a
2929    /// raw 8-byte read (fixed view) or a tagged-varint decode (variable view), NO `RuntimeValue` box.
2930    /// `None` if the cell is not an integer.
2931    pub fn i64(&self, row: usize, fi: usize) -> Option<i64> {
2932        let cell = self.cell_slice(row, fi)?;
2933        match &self.kind {
2934            CursorKind::Fixed { field_kinds, .. } => match *field_kinds.get(fi)? {
2935                FK_INT => Some(i64::from_le_bytes(cell.get(0..8)?.try_into().ok()?)),
2936                _ => None,
2937            },
2938            CursorKind::Variable { .. } => {
2939                if *cell.first()? != T_INT {
2940                    return None;
2941                }
2942                let mut q = 1;
2943                Some(unzigzag(read_uvarint(cell, &mut q)?))
2944            }
2945        }
2946    }
2947}
2948
2949const T_NOTHING: u8 = 0;
2950const T_FALSE: u8 = 1;
2951const T_TRUE: u8 = 2;
2952const T_INT: u8 = 3;
2953const T_FLOAT: u8 = 4;
2954const T_CHAR: u8 = 5;
2955const T_TEXT: u8 = 6;
2956const T_DURATION: u8 = 7;
2957const T_DATE: u8 = 8;
2958const T_MOMENT: u8 = 9;
2959const T_SPAN: u8 = 10;
2960const T_TIME: u8 = 11;
2961const T_PEER: u8 = 12;
2962const T_LIST: u8 = 13;
2963const T_TUPLE: u8 = 14;
2964const T_SET: u8 = 15;
2965const T_MAP: u8 = 16;
2966const T_STRUCT: u8 = 17;
2967const T_INDUCTIVE: u8 = 18;
2968// Packed homogeneous lists — one tag + count, NO per-element tag, encoded
2969// straight from the specialized `ListRepr` storage. The throughput path.
2970const T_INTS: u8 = 19; // zig-zag varint per element (covers Ints + IntsI32)
2971const T_FLOATS: u8 = 20; // 8-byte little-endian per element
2972const T_BOOLS: u8 = 21; // bit-packed, 8 booleans per byte
2973const T_STRINGS: u8 = 22; // flat string array: count + per-elem byte-lengths + one bytes blob
2974const T_INTS_FIXED: u8 = 23; // fixed-width i64 array: count + raw 8-byte-LE blob (memcpy)
2975const T_INTS_GV: u8 = 24; // group-varint (Stream VByte layout): control stream + data stream
2976                          // Columnar packing for homogeneous lists of compound values: the schema is written
2977                          // ONCE, then each field becomes its own packed column (reusing the array tags above).
2978const T_STRUCTS: u8 = 25; // homogeneous struct list: type_name + field names + one column per field
2979const T_INDUCTIVES: u8 = 26; // homogeneous enum list: type_name + ctor dictionary + index + arg columns
2980                             // Schema-dictionary forms of a struct list (cross-message, connection-scoped cache):
2981const T_STRUCTS_DEF: u8 = 27; // sequential: defines schema at `id` inline, then columns (self-decodable)
2982const T_STRUCTS_REF: u8 = 28; // sequential: references a previously-defined `id`, then columns
2983const T_STRUCTS_CDEF: u8 = 29; // content-addressed: schema inline (fingerprint derived), then columns
2984const T_STRUCTS_CREF: u8 = 30; // content-addressed: 8-byte schema fingerprint, then columns
2985const T_FLOATS_XOR: u8 = 31; // lossless XOR-delta + varint float array (Gorilla-style)
2986const T_INTS_AFFINE: u8 = 32; // closed-form: base + stride*i for all i (3 numbers, no data)
2987const T_BIGINT: u8 = 33; // exact out-of-i64 integer: sign byte + length + little-endian magnitude
2988const T_RATIONAL: u8 = 34; // exact fraction: signed numerator (sign+len+LE) then positive denominator (len+LE)
2989const T_DECIMAL: u8 = 75; // exact base-10 fixed-point (money): sign + coefficient magnitude (len+LE) + base-10 scale (uvarint)
2990const T_COMPLEX: u8 = 76; // exact complex re+im·i: two rationals back to back, each as sign + numerator (len+LE) + denominator (len+LE)
2991const T_MODULAR: u8 = 77; // ℤ/nℤ element: residue magnitude (len+LE) then modulus magnitude (len+LE), both non-negative
2992const T_QUANTITY: u8 = 79; // dimensioned quantity: SI magnitude (sign + num len+LE + den len+LE), 10 exponent (num,den) zigzag-varint pairs, then the unit symbol (len+UTF-8)
2993const T_MONEY: u8 = 80; // money: amount as Decimal (sign + coefficient len+LE + base-10 scale uvarint) then the ISO-4217 currency code (len + UTF-8)
2994const T_UUID: u8 = 81; // uuid: 16 big-endian bytes verbatim (fixed width, no length prefix)
2995const T_EXACT: u8 = 86; // algebraic (0) / computable real (1) / ordinal (2): kind + len + canonical bytes
2996const T_SCALED: u8 = 87; // hyperreal (0) / infinite integer (1): kind + name + len + residues + len + bytes
2997const T_DEPENDS: u8 = 88; // name + len + residues + modulus + per class (0 | 1 + value)
2998const T_CONVERGING: u8 = 89; // value + cases + revisions + (0 | 1 + certificate)
2999const T_COMPLEXF: u8 = 85; // floating-point complex (LAPACK Z): re then im, each 8-byte little-endian f64. Tag 85 (NOT 82/83/84 — the shared column-tag namespace reserves those for describe::T_INTS_{LRECUR,LFSR,FCSR}, which native_decode dispatches by the same byte)
3000                           // Schema-dictionary forms of a SINGLE struct (cross-message, connection-scoped cache),
3001                           // the lone-struct analog of the T_STRUCTS_* list forms: once both peers know a schema,
3002                           // a struct message ships its values in canonical field order with NO inline field-name
3003                           // strings — closing the postcard gap (a lone struct otherwise pays for "x","y",… every
3004                           // send). The DEF/CDEF forms are self-decodable (schema inline); REF/CREF carry values only.
3005const T_STRUCT_DEF: u8 = 35; // sequential: id + schema inline (registered), then values in field order
3006const T_STRUCT_REF: u8 = 36; // sequential: id resolved against the cache, then values
3007const T_STRUCT_CDEF: u8 = 37; // content-addressed: schema inline (fingerprint derived), then values
3008const T_STRUCT_CREF: u8 = 38; // content-addressed: 8-byte schema fingerprint, then values
3009                              // The per-column compression menu (WireStructure::Auto picks the smallest of these +
3010                              // the varint/affine baselines). Each is a categorical win on one data shape; the
3011                              // selector always includes the plain varint, so the chosen form is never larger.
3012const T_INTS_DELTA: u8 = 39; // count + first(zz) + (n-1) zig-zag deltas — monotone columns
3013const T_INTS_DOD: u8 = 40; // count + first(zz) + d1(zz) + (n-2) zig-zag delta-of-deltas — near-linear (timestamps)
3014const T_INTS_FOR: u8 = 41; // count + min(zz) + bit-width + bit-packed (v-min) residuals — clustered ints
3015const T_INTS_RLE: u8 = 42; // run count + (value(zz), run-length) pairs — runs of repeats
3016const T_INTS_DICT: u8 = 43; // dict size + distinct values(zz) + count + index-width + bit-packed indices — low cardinality
3017const T_INTS_POLY: u8 = 50; // degree + count + (degree+1) finite-difference seeds(zz) — SHIP THE GENERATOR for a polynomial column
3018const T_GEN: u8 = 51; // serialized GenExpr + count — a sandboxed pure generator over the index `i` (the general compute-shipping form)
3019const T_FUNC: u8 = 52; // arity + serialized GenExpr — a SHIPPED CALLABLE pure function (the receiver evaluates it in the sandbox)
3020const T_BYTES: u8 = 53; // count + raw 1-byte-per-element blob — a byte column; memcpy in/out and readable in place as &[u8] (zero-copy, no alignment)
3021const T_STRUCTS_TID: u8 = 54; // shared-registry struct LIST: type-id(varint) + N + columns — type/field NAMES elided (the struct-list analog of T_STRUCT_TID)
3022const T_SET_INTS: u8 = 55; // homogeneous int SET: the SORTED-canonical members shipped through the G5 int-column menu (delta/affine/RLE) — a consecutive set {1..n} collapses to base+stride+count, no data
3023const T_STRUCTS_FVIEW: u8 = 56; // FIXED-stride record-list view: type + F + names + F kind-bytes + N + [n×stride fixed rows] + blob_len + string blob. Random access = pure arithmetic (no offset tables): the `indexed fast` form — composes the struct-view with the fixed numeric dial.
3024const T_SET_STRINGS: u8 = 57; // homogeneous string SET, FRONT-CODED: members sorted to canonical order, each shipped as (shared-prefix-len-with-previous, suffix) — sorted similar strings share long prefixes so only the deltas go on the wire
3025const T_MAP_INTKEY: u8 = 58; // INT-KEYED map, COLUMNAR: entries sorted by numeric key → keys as a best int column (G5 menu: affine/delta/RLE) + values as a best-encoded list (reuses the full column menu). An affine int→int map {i↦2i} collapses BOTH columns to closed forms — ~no data. Canonical (insertion-order-invariant).
3026const T_STRINGS_DICT: u8 = 59; // DICTIONARY string column (low cardinality / categorical labels): dict-len + distinct strings (len+bytes) once + count + index-width + bit-packed per-row indices. The string twin of T_INTS_DICT — a handful of distinct labels repeated N times ships the labels once.
3027
3028// Fixed-view field kinds (1 byte each in the schema): the wire width of a cell.
3029const FK_INT: u8 = 0; // 8 bytes, raw i64 little-endian (NOT zig-zag — memcpy, like T_INTS_FIXED)
3030const FK_FLOAT: u8 = 1; // 8 bytes, f64 little-endian
3031const FK_BOOL: u8 = 2; // 1 byte (0/1)
3032const FK_TEXT: u8 = 3; // 8 bytes: u32 offset + u32 length, into the trailing string blob
3033
3034/// The byte width of one fixed-view cell of `kind`.
3035fn fview_width(kind: u8) -> usize {
3036    match kind {
3037        FK_BOOL => 1,
3038        _ => 8, // FK_INT / FK_FLOAT / FK_TEXT(ref)
3039    }
3040}
3041
3042/// Per-field byte offsets within a fixed-view row, and the row stride (their sum).
3043fn fview_layout(kinds: &[u8]) -> (Vec<usize>, usize) {
3044    let mut offsets = Vec::with_capacity(kinds.len());
3045    let mut cur = 0usize;
3046    for &k in kinds {
3047        offsets.push(cur);
3048        cur += fview_width(k);
3049    }
3050    (offsets, cur)
3051}
3052
3053/// The fixed-view kind for each column, or `None` if any column is not a fixed-width-encodable
3054/// leaf (Int/Float/Bool/Text) — then the caller keeps the variable offset-table view.
3055fn columns_fview_kinds(columns: &[ListRepr]) -> Option<Vec<u8>> {
3056    let mut kinds = Vec::with_capacity(columns.len());
3057    for col in columns {
3058        kinds.push(match col {
3059            ListRepr::Ints(_) | ListRepr::IntsI32(_) => FK_INT,
3060            ListRepr::Floats(_) => FK_FLOAT,
3061            ListRepr::Bools(_) => FK_BOOL,
3062            ListRepr::Strings { .. } => FK_TEXT,
3063            _ => return None,
3064        });
3065    }
3066    Some(kinds)
3067}
3068
3069/// Read one fixed-view cell (`kind` bytes at `cell`) into an owned value; `blob` backs FK_TEXT.
3070fn fview_read_cell(kind: u8, cell: &[u8], blob: &[u8]) -> Option<RuntimeValue> {
3071    match kind {
3072        FK_INT => Some(RuntimeValue::Int(i64::from_le_bytes(
3073            cell.get(0..8)?.try_into().ok()?,
3074        ))),
3075        FK_FLOAT => Some(RuntimeValue::Float(f64::from_le_bytes(
3076            cell.get(0..8)?.try_into().ok()?,
3077        ))),
3078        FK_BOOL => Some(RuntimeValue::Bool(*cell.first()? != 0)),
3079        FK_TEXT => {
3080            let off = u32::from_le_bytes(cell.get(0..4)?.try_into().ok()?) as usize;
3081            let len = u32::from_le_bytes(cell.get(4..8)?.try_into().ok()?) as usize;
3082            let s = blob.get(off..off.checked_add(len)?)?;
3083            Some(RuntimeValue::Text(Rc::new(
3084                String::from_utf8(s.to_vec()).ok()?,
3085            )))
3086        }
3087        _ => None,
3088    }
3089}
3090// Type-id elided struct: when both ends share a program type registry, ship the type's
3091// small registry id + the values only — type/field NAMES never go on the wire (the
3092// Logos↔Logos default that beats raw varint). Falls back to T_STRUCT when unknown.
3093const T_STRUCT_TID: u8 = 44; // registry-id(varint) + values in canonical field order
3094const T_WORD: u8 = 60; // fixed-width wrapping int: width byte (32|64) + uvarint value (zero-extended to u64)
3095const T_INTS_GEOMETRIC: u8 = 61; // closed-form: base * ratio^i for all i (3 numbers, no data) — wrapping-exact
3096const T_INTS_PERIODIC: u8 = 62; // cyclic: period p + count + one block of p values → pattern[i % p]
3097const T_SHARED_DEF: u8 = 63; // dedup: id(uvarint) + the value — registers a shared subtree at `id`
3098const T_SHARED_REF: u8 = 64; // dedup: id(uvarint) — a backref to an already-shipped shared subtree
3099const T_FLOATS_CONST: u8 = 65; // closed-form: one f64 (8 LE bytes) + count — every element identical
3100const T_FLOATS_AFFINE: u8 = 66; // closed-form: base + i·stride (2 f64 + count), BIT-EXACT or not used
3101const T_INTS_SPARSE: u8 = 67; // dominant value + count + (delta-index, value) exceptions — sparse/default columns
3102const T_FLOATS_SPARSE: u8 = 68; // dominant f64 + count + (delta-index, f64) exceptions — sparse float columns
3103const T_FLOATS_PERIODIC: u8 = 69; // cyclic: period p + count + one block of p f64 → pattern[i % p]
3104const T_FLOATS_GEOMETRIC: u8 = 70; // closed-form: base * ratio^i (2 f64 + count), BIT-EXACT or not used
3105const T_STRINGS_TEMPLATE: u8 = 71; // templated: prefix + suffix + affine(base,stride) + count → prefix+(base+i·stride)+suffix
3106const T_STRINGS_FRONT: u8 = 72; // front-coded COLUMN (order-preserving): each string = (shared-prefix-len-with-previous, suffix); sorted/hierarchical columns share long prefixes
3107const T_BOOLS_PERIODIC: u8 = 73; // cyclic bool column: period p + count + one p-bit block → block[i % p] (covers const all-true/all-false at p=1, alternating at p=2)
3108const T_BOOLS_RLE: u8 = 74; // run-length bool column: first value + run lengths (alternating) → big runs ([F×n, T×m] / clustered flags) collapse to a few varints
3109const T_STRINGS_AFFIX: u8 = 78; // common prefix + common suffix + per-row ARBITRARY middle → emails (…@host), extensions (….log), wrapped ids; the non-affine sibling of T_STRINGS_TEMPLATE
3110                                // Type-id elided enum: ship the enum's registry id + the constructor INDEX (into the
3111                                // type's ordered constructor list) + the args — type and constructor names elided.
3112const T_INDUCTIVE_TID: u8 = 45; // enum-id(varint) + ctor-index(varint) + arg-count + args
3113                                // Offset-table struct view (the Cap'n Proto-beating random-access layout): a per-field
3114                                // byte-offset table precedes the values, so a `WireView` jumps to ANY field in O(1)
3115                                // without parsing the others (even a huge preceding field). Decodes normally too.
3116const T_STRUCT_VIEW: u8 = 46; // type_name + count + names + [u32 offset]×count + values
3117                              // 8-byte-aligned i64 column: `count + pad-len + pad + raw i64 LE blob`, padded so the blob
3118                              // lands on an 8-byte boundary in the final framed buffer (header len ≡ 1 mod 8, so the
3119                              // body offset is aligned to ≡ 7 mod 8). A `WireView` reads it as `&[i64]` with ZERO copy
3120                              // (`as_i64_slice`) — the in-place column read, for kernel-bypass / RDMA on a LAN.
3121const T_INTS_ALIGNED: u8 = 47; // count + pad-len(1) + pad + i64 LE blob (8-byte aligned blob)
3122                               // The float twin of `T_INTS_ALIGNED`: an 8-byte-aligned `f64` blob a `WireView` reads as
3123                               // `&[f64]` with ZERO copy (`as_f64_slice`). Same padding discipline → the blob is 8-aligned
3124                               // in the framed buffer, so the cast is sound on every architecture (the float column axis).
3125const T_FLOATS_ALIGNED: u8 = 48; // count + pad-len(1) + pad + f64 LE blob (8-byte aligned blob)
3126                                 // A record-LIST view: the shared schema once, a per-ROW offset table, then each row's own
3127                                 // per-FIELD offset table + values. `WireView::structs_row_field(row, name)` jumps to ANY
3128                                 // (row, field) in O(1) — Cap'n Proto-class random access into a huge struct list, without
3129                                 // parsing the other rows or fields. The list analog of `T_STRUCT_VIEW`.
3130const T_STRUCTS_VIEW: u8 = 49; // type + F + names + N + [u32 row_off]×N + per-row([u32 field_off]×F + values)
3131
3132/// How integer arrays are laid out on the wire — the sender's size↔speed dial.
3133/// The *decoder* always handles every variant (each has its own tag), so this is
3134/// purely a sender preference; mix freely on one relay.
3135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3136pub enum WireNumerics {
3137    /// LEB128 varint — smallest, and the best *scalar* decode. The default; the
3138    /// right choice for a network link (bytes are the bottleneck).
3139    Varint,
3140    /// Raw fixed-width `i64` — a `memcpy` both ways (float speed) at 4× the size.
3141    /// For a CPU-bound / bandwidth-rich link (datacenter, shared memory, RDMA).
3142    Fixed,
3143    /// Group-varint (Stream VByte layout) — varint-class size with the widths
3144    /// hoisted into a control stream, so a SIMD shuffle decodes it several ints at
3145    /// a time. The "small AND fast" middle ground on a SIMD-capable host.
3146    GroupVarint,
3147}
3148
3149/// How float arrays are encoded. `Memcpy` is the raw 8-byte-per-element blob (the
3150/// memory-bandwidth ceiling, the default). `XorDelta` XORs each value's bits with the
3151/// previous and varint-codes the result — lossless and bit-exact (it operates on the wire
3152/// bits, so Inf/±0/subnormals are preserved and a NaN is the canonical NaN, as everywhere on
3153/// the wire), and far smaller for slowly-varying
3154/// (time-series) columns. It is applied per-column only when it actually shrinks the
3155/// column, so it never grows a message.
3156#[derive(Clone, Copy, PartialEq, Eq, Debug)]
3157pub enum WireFloats {
3158    Memcpy,
3159    XorDelta,
3160}
3161
3162/// Whether the encoder may replace a column with a *closed-form generator* when the
3163/// data is mathematically structured — the Futamura move on the wire: if the values
3164/// are described by a formula, ship the formula, not the values. Every form is
3165/// lossless and gated by an exact-match proof, so it can never change the decoded
3166/// value; the decoder always reconstructs (each form has its own tag). `Off` by
3167/// default (detection is an O(n) scan the speed dials skip), opt-in per send.
3168#[derive(Clone, Copy, PartialEq, Eq, Debug)]
3169pub enum WireStructure {
3170    /// No structural analysis — integer columns encode by the numeric dial.
3171    Off,
3172    /// Detect an affine progression `v[i] = base + i·stride`; when *every* element
3173    /// matches exactly (wrapping i64), send `(base, stride, n)` — three numbers for
3174    /// the whole column — instead of the data. Falls back to the numeric dial when
3175    /// the data is not affine, so it never grows a message or loses a value.
3176    Affine,
3177    /// The full per-column compression menu: build every applicable encoding
3178    /// (varint baseline · affine · delta · delta-of-delta · frame-of-reference
3179    /// bit-packing · run-length · dictionary) and ship the SMALLEST. The varint
3180    /// baseline is always a candidate, so the result is never larger than `Off`'s
3181    /// varint — each encoding is a categorical win on its shape (monotone,
3182    /// near-linear timestamps, clustered, runs, low-cardinality) and silently loses
3183    /// the bake-off otherwise. The "smallest" knob.
3184    Auto,
3185}
3186
3187thread_local! {
3188    static NUMERICS: std::cell::Cell<WireNumerics> = const { std::cell::Cell::new(WireNumerics::Varint) };
3189    static FLOATS: std::cell::Cell<WireFloats> = const { std::cell::Cell::new(WireFloats::Memcpy) };
3190    static STRUCTURE: std::cell::Cell<WireStructure> = const { std::cell::Cell::new(WireStructure::Off) };
3191    /// When on, EVERY list encodes as the plain, self-describing `T_LIST` (count + per-element
3192    /// tagged values) — no columnar string/struct/inductive packing. This is the flat, fastest-to-
3193    /// decode form the shared [`logicaffeine_data::wire`] core reads. Off by default so the peer
3194    /// codec's columnar wins are untouched; opt-in via [`with_flat_lists`] (used by [`encode_value_raw`]).
3195    static FLAT_LISTS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
3196}
3197
3198/// Run `f` with flat (columnar-free) list encoding forced on/off, restoring the prior value.
3199pub fn with_flat_lists<T>(on: bool, f: impl FnOnce() -> T) -> T {
3200    let prev = FLAT_LISTS.with(|c| c.replace(on));
3201    let out = f();
3202    FLAT_LISTS.with(|c| c.set(prev));
3203    out
3204}
3205
3206#[inline]
3207fn flat_lists() -> bool {
3208    FLAT_LISTS.with(|c| c.get())
3209}
3210
3211/// Encode integer arrays under `n` for the duration of `f`. Scoped — never leaks.
3212pub fn with_numerics<T>(n: WireNumerics, f: impl FnOnce() -> T) -> T {
3213    let prev = NUMERICS.with(|c| c.replace(n));
3214    let out = f();
3215    NUMERICS.with(|c| c.set(prev));
3216    out
3217}
3218
3219/// Convenience for [`WireNumerics::Fixed`] (back-compat).
3220pub fn with_fixed_numerics<T>(f: impl FnOnce() -> T) -> T {
3221    with_numerics(WireNumerics::Fixed, f)
3222}
3223
3224fn numerics() -> WireNumerics {
3225    NUMERICS.with(std::cell::Cell::get)
3226}
3227
3228/// Enable structural (closed-form) integer encoding under `s` for the duration of
3229/// `f`. Scoped — never leaks. See [`WireStructure`].
3230pub fn with_structure<T>(s: WireStructure, f: impl FnOnce() -> T) -> T {
3231    let prev = STRUCTURE.with(|c| c.replace(s));
3232    let out = f();
3233    STRUCTURE.with(|c| c.set(prev));
3234    out
3235}
3236
3237fn structure() -> WireStructure {
3238    STRUCTURE.with(std::cell::Cell::get)
3239}
3240
3241/// The longest repeating block the periodic detector will consider. A period beyond this is an
3242/// unusual "pattern" whose block barely beats storing the data, and it bounds the search cost.
3243const PERIOD_CAP: usize = 512;
3244
3245/// Lower a pure single-parameter arithmetic expression into a [`GenExpr`] over the index —
3246/// the bridge that lets a user's pure function be SHIPPED as the sandboxed generator (it
3247/// becomes data the receiver evaluates, never code it runs). Returns `None` for anything
3248/// outside the provably-total arithmetic subset (calls, indexing, unknown variables, non-
3249/// integer literals, comparison/logical/bitwise ops), so only a safe function is shippable.
3250pub(crate) fn lower_expr_to_genexpr(
3251    e: &crate::ast::stmt::Expr<'_>,
3252    param: logicaffeine_base::Symbol,
3253) -> Option<GenExpr> {
3254    use crate::ast::stmt::{BinaryOpKind, Expr, Literal};
3255    match e {
3256        Expr::Literal(Literal::Number(n)) => Some(GenExpr::Const(*n)),
3257        Expr::Identifier(s) if *s == param => Some(GenExpr::Index),
3258        Expr::BinaryOp { op, left, right } => {
3259            let l = Box::new(lower_expr_to_genexpr(left, param)?);
3260            let r = Box::new(lower_expr_to_genexpr(right, param)?);
3261            Some(match op {
3262                BinaryOpKind::Add => GenExpr::Add(l, r),
3263                BinaryOpKind::Subtract => GenExpr::Sub(l, r),
3264                BinaryOpKind::Multiply => GenExpr::Mul(l, r),
3265                BinaryOpKind::Divide => GenExpr::Div(l, r),
3266                BinaryOpKind::Modulo => GenExpr::Mod(l, r),
3267                _ => return None,
3268            })
3269        }
3270        _ => None,
3271    }
3272}
3273
3274/// Pack `vals` LSB-first at `width` bits each (1..=64). The inverse of [`bitunpack`].
3275fn bitpack(vals: &[u64], width: u8) -> Vec<u8> {
3276    if width == 0 {
3277        return Vec::new();
3278    }
3279    let total_bits = vals.len().saturating_mul(width as usize);
3280    let mut out = vec![0u8; total_bits.div_ceil(8)];
3281    let mut bitpos = 0usize;
3282    for &val in vals {
3283        let mut bits = val;
3284        let mut remaining = width as usize;
3285        while remaining > 0 {
3286            let byte = bitpos / 8;
3287            let off = bitpos % 8;
3288            let take = remaining.min(8 - off);
3289            let mask = (1u64 << take) - 1;
3290            out[byte] |= ((bits & mask) as u8) << off;
3291            bits >>= take;
3292            bitpos += take;
3293            remaining -= take;
3294        }
3295    }
3296    out
3297}
3298
3299/// Read `count` LSB-first `width`-bit values from `bytes`. `None` if `bytes` is too
3300/// short (clean failure on a corrupt length). The inverse of [`bitpack`].
3301fn bitunpack(bytes: &[u8], count: usize, width: u8) -> Option<Vec<u64>> {
3302    if width == 0 || width > 64 {
3303        return None;
3304    }
3305    let total_bits = count.checked_mul(width as usize)?;
3306    if bytes.len() < total_bits.div_ceil(8) {
3307        return None;
3308    }
3309    let mut out = Vec::with_capacity(count.min(PREALLOC_CAP));
3310    let mut bitpos = 0usize;
3311    for _ in 0..count {
3312        let mut val = 0u64;
3313        let mut got = 0usize;
3314        while got < width as usize {
3315            let byte = bitpos / 8;
3316            let off = bitpos % 8;
3317            let take = (width as usize - got).min(8 - off);
3318            let mask = (1u64 << take) - 1;
3319            val |= (((bytes[byte] >> off) as u64) & mask) << got;
3320            got += take;
3321            bitpos += take;
3322        }
3323        out.push(val);
3324    }
3325    Some(out)
3326}
3327
3328/// Encode float arrays under `mode` for the duration of `f`. Scoped — never leaks.
3329pub fn with_floats<T>(mode: WireFloats, f: impl FnOnce() -> T) -> T {
3330    let prev = FLOATS.with(|c| c.replace(mode));
3331    let out = f();
3332    FLOATS.with(|c| c.set(prev));
3333    out
3334}
3335
3336fn floats_mode() -> WireFloats {
3337    FLOATS.with(std::cell::Cell::get)
3338}
3339
3340/// If every element is BIT-IDENTICAL (compared by wire bits, so `-0.0` and `+0.0` differ and every
3341/// NaN is the one canonical NaN, not `==`), return its bit pattern — the column ships as one f64 + a
3342/// count (constant readings, padding,
3343/// defaults). `None` for an empty or non-constant column.
3344fn detect_float_const(v: &[f64]) -> Option<u64> {
3345    let bits = canonical_f64_bits(*v.first()?);
3346    v.iter().all(|x| canonical_f64_bits(*x) == bits).then_some(bits)
3347}
3348
3349/// If `v` is BIT-EXACTLY the closed form `base + i·stride` evaluated the SAME way the decoder will,
3350/// return `(base, stride)` — so it ships THREE numbers, not `n`. The bit-exact check (never `==`) means
3351/// it fires only when reconstruction is perfect: integer-valued float columns (ids/indices from JSON,
3352/// `0.0,1.0,2.0,…`), power-of-two-stride axes, exact linspace. Real noisy float data simply isn't
3353/// recognized and falls through to XOR-delta / memcpy — so the generator is a pure, lossless win.
3354fn detect_float_affine(v: &[f64]) -> Option<(f64, f64)> {
3355    if v.len() < 3 {
3356        return None;
3357    }
3358    let base = v[0];
3359    let stride = v[1] - v[0];
3360    for (i, &x) in v.iter().enumerate() {
3361        if canonical_f64_bits(base + (i as f64) * stride) != canonical_f64_bits(x) {
3362            return None;
3363        }
3364    }
3365    Some((base, stride))
3366}
3367
3368/// The float twin of [`detect_sparse`]: if ONE f64 (by bit pattern) dominates ≥ ¾ of the column,
3369/// return its bits and the sorted `(index, value)` exceptions — a mostly-default/constant-with-
3370/// outliers float column (sparse telemetry, a mostly-zero signal). Boyer–Moore over the wire bits, so a
3371/// column with no dominant value pays only the O(1)-memory pass.
3372fn detect_float_sparse(v: &[f64]) -> Option<(u64, Vec<(usize, u64)>)> {
3373    if v.len() < 8 {
3374        return None;
3375    }
3376    let mut cand = canonical_f64_bits(v[0]);
3377    let mut count: i64 = 0;
3378    for x in v {
3379        let b = canonical_f64_bits(*x);
3380        if count == 0 {
3381            cand = b;
3382            count = 1;
3383        } else if b == cand {
3384            count += 1;
3385        } else {
3386            count -= 1;
3387        }
3388    }
3389    let occ = v.iter().filter(|x| canonical_f64_bits(**x) == cand).count();
3390    if v.len() - occ > v.len() / 4 {
3391        return None;
3392    }
3393    let exceptions: Vec<(usize, u64)> = v
3394        .iter()
3395        .enumerate()
3396        .filter(|(_, x)| canonical_f64_bits(**x) != cand)
3397        .map(|(i, x)| (i, canonical_f64_bits(*x)))
3398        .collect();
3399    Some((cand, exceptions))
3400}
3401
3402/// The float twin of [`detect_period`]: the minimal period `2 ≤ p ≤ min(len/2, PERIOD_CAP)` such that
3403/// `v[i]` BIT-equals `v[i % p]` (a cyclic waveform / repeated frame). Pure bit-equality, so always
3404/// exact — ship one block of `p` f64 + the count, not all `n`.
3405fn detect_float_period(v: &[f64]) -> Option<usize> {
3406    let n = v.len();
3407    if n < 4 {
3408        return None;
3409    }
3410    let cap = (n / 2).min(PERIOD_CAP);
3411    'p: for p in 2..=cap {
3412        for i in p..n {
3413            if canonical_f64_bits(v[i]) != canonical_f64_bits(v[i - p]) {
3414                continue 'p;
3415            }
3416        }
3417        return Some(p);
3418    }
3419    None
3420}
3421
3422/// The float twin of [`detect_geometric`]: if `v` is BIT-EXACTLY `base · ratio^i` replayed by the same
3423/// `cur *= ratio` accumulation the decoder uses, return `(base, ratio)`. Float multiply rounds, so this
3424/// fires only when reconstruction is perfect — power-of-two ratios (doubling, halving / exponential
3425/// decay), which ARE exact in f64. Everything else falls through, so it is a pure lossless win.
3426fn detect_float_geometric(v: &[f64]) -> Option<(f64, f64)> {
3427    if v.len() < 3 {
3428        return None;
3429    }
3430    let base = v[0];
3431    if base == 0.0 || !base.is_finite() {
3432        return None;
3433    }
3434    let ratio = v[1] / base;
3435    if !ratio.is_finite() || ratio == 1.0 {
3436        return None; // ratio 1 → constant, handled by `detect_float_const`
3437    }
3438    let mut cur = base;
3439    for &x in v {
3440        if canonical_f64_bits(cur) != canonical_f64_bits(x) {
3441            return None;
3442        }
3443        cur *= ratio;
3444    }
3445    Some((base, ratio))
3446}
3447
3448/// XOR-delta encode a float column: count, then the LEB128 varint of each value's
3449/// wire bits XOR the previous value's. Lossless and bit-exact, a NaN canonical.
3450fn floats_xor_encode(out: &mut Vec<u8>, v: &[f64]) {
3451    write_uvarint(v.len() as u64, out);
3452    let mut prev = 0u64;
3453    for &f in v {
3454        let bits = canonical_f64_bits(f);
3455        write_uvarint(bits ^ prev, out);
3456        prev = bits;
3457    }
3458}
3459
3460/// Bytes a `write_uvarint` of `x` occupies (LEB128, 1–10 bytes).
3461fn uvarint_byte_len(x: u64) -> usize {
3462    (((64 - x.leading_zeros()).max(1) + 6) / 7) as usize
3463}
3464
3465/// The body size of the memcpy float encoding (`T_FLOATS`): the count varint + 8
3466/// bytes per element. Used to keep the XOR-delta column ONLY when it actually shrinks.
3467fn floats_memcpy_body_len(n: usize) -> usize {
3468    uvarint_byte_len(n as u64) + n * 8
3469}
3470
3471/// The compression codec for an encoded body — the sender's dial. The wire is
3472/// self-describing (the header carries the codec), so this is purely a sender
3473/// preference; any peer decodes any codec. Each is kept only if it actually shrank
3474/// the body (see [`message_to_wire_with`]), so compression never hurts the fast path.
3475#[derive(Clone, Copy, PartialEq, Eq, Debug)]
3476pub enum WireCompression {
3477    /// No compression. The default — our binary is already compact, so compression
3478    /// is for large/redundant payloads on size-constrained links.
3479    None,
3480    /// DEFLATE (`miniz_oxide`). The balanced middle; what `Send compressed` selects.
3481    Deflate,
3482    /// LZ4 (`lz4_flex`, pure-Rust) — near-memcpy speed, lighter ratio. Ships on every
3483    /// target (native + browser).
3484    Lz4,
3485    /// Zstandard — the best ratio. Native uses the C encoder; the browser cannot
3486    /// encode it (falls back to lz4) but decodes it via the pure-Rust `ruzstd`.
3487    Zstd,
3488}
3489
3490/// The 2-bit on-wire id for a codec (header bits 2-3). `None`/`Deflate` share id 0;
3491/// `None` is distinguished by `H_COMPRESSED` being unset.
3492fn compression_id(c: WireCompression) -> u8 {
3493    match c {
3494        WireCompression::None | WireCompression::Deflate => 0,
3495        WireCompression::Lz4 => 1,
3496        WireCompression::Zstd => 2,
3497    }
3498}
3499
3500/// The compression effort dial — a sender-only preference (the codec output is
3501/// self-describing, so the decoder needs no knowledge of the level). `Fast` favors
3502/// throughput, `Max` favors ratio, `Balanced` is the default middle.
3503#[derive(Clone, Copy, PartialEq, Eq, Debug)]
3504pub enum WireCompressionLevel {
3505    Fast,
3506    Balanced,
3507    Max,
3508}
3509
3510thread_local! {
3511    static COMPRESSION_CODEC: std::cell::Cell<WireCompression> =
3512        const { std::cell::Cell::new(WireCompression::None) };
3513    static COMPRESSION_LEVEL: std::cell::Cell<WireCompressionLevel> =
3514        const { std::cell::Cell::new(WireCompressionLevel::Balanced) };
3515}
3516
3517/// Compress encoded bodies with `codec` (kept only if smaller) for the duration of
3518/// `f`. Scoped so it never leaks.
3519pub fn with_compression_codec<T>(codec: WireCompression, f: impl FnOnce() -> T) -> T {
3520    let prev = COMPRESSION_CODEC.with(|c| c.replace(codec));
3521    let out = f();
3522    COMPRESSION_CODEC.with(|c| c.set(prev));
3523    out
3524}
3525
3526/// Set the compression effort for the duration of `f`. Scoped so it never leaks.
3527pub fn with_compression_level<T>(level: WireCompressionLevel, f: impl FnOnce() -> T) -> T {
3528    let prev = COMPRESSION_LEVEL.with(|c| c.replace(level));
3529    let out = f();
3530    COMPRESSION_LEVEL.with(|c| c.set(prev));
3531    out
3532}
3533
3534fn compression_level() -> WireCompressionLevel {
3535    COMPRESSION_LEVEL.with(std::cell::Cell::get)
3536}
3537
3538/// DEFLATE effort: miniz_oxide levels 1 (fast) / 6 (balanced) / 9 (max).
3539fn deflate_level() -> u8 {
3540    match compression_level() {
3541        WireCompressionLevel::Fast => 1,
3542        WireCompressionLevel::Balanced => 6,
3543        WireCompressionLevel::Max => 9,
3544    }
3545}
3546
3547/// zstd effort: levels 1 (fast) / 9 (balanced) / 19 (max). Decode speed is
3548/// level-independent in zstd, so `Max` costs only encode time.
3549#[cfg(not(target_arch = "wasm32"))]
3550fn zstd_level() -> i32 {
3551    match compression_level() {
3552        WireCompressionLevel::Fast => 1,
3553        WireCompressionLevel::Balanced => 9,
3554        WireCompressionLevel::Max => 19,
3555    }
3556}
3557
3558/// Back-compat convenience: compress with DEFLATE (what the bare `Send compressed`
3559/// keyword selects).
3560pub fn with_compression<T>(f: impl FnOnce() -> T) -> T {
3561    with_compression_codec(WireCompression::Deflate, f)
3562}
3563
3564fn compression_codec() -> WireCompression {
3565    COMPRESSION_CODEC.with(std::cell::Cell::get)
3566}
3567
3568/// Compress `body` with `codec`, returning the codec actually used (it may differ
3569/// from the request: a wasm `Zstd` encode falls back to `Lz4`) and the bytes. The
3570/// caller keeps the result only if it shrank.
3571fn compress_body(codec: WireCompression, body: &[u8]) -> Option<(WireCompression, Vec<u8>)> {
3572    match codec {
3573        WireCompression::None => None,
3574        WireCompression::Deflate => Some((
3575            codec,
3576            miniz_oxide::deflate::compress_to_vec(body, deflate_level()),
3577        )),
3578        WireCompression::Lz4 => Some((codec, lz4_flex::compress_prepend_size(body))),
3579        WireCompression::Zstd => {
3580            #[cfg(not(target_arch = "wasm32"))]
3581            {
3582                zstd::encode_all(body, zstd_level())
3583                    .ok()
3584                    .map(|z| (WireCompression::Zstd, z))
3585            }
3586            #[cfg(target_arch = "wasm32")]
3587            {
3588                // No C encoder in the browser — fall back to lz4 (still universally
3589                // decodable). The header will record lz4, not zstd.
3590                Some((WireCompression::Lz4, lz4_flex::compress_prepend_size(body)))
3591            }
3592        }
3593    }
3594}
3595
3596/// The smallest `body` compresses to across the built-in compressors (deflate / lz4 / zstd), or its
3597/// raw length when none helps — the "fair fight" size for an arbitrary byte string. This is the same
3598/// shop-every-compressor rule [`message_to_wire_best`]'s `Smallest` goal applies to the LOGOS wire,
3599/// exposed so a benchmark can grant a COMPETITOR codec the identical compression opportunity: then a
3600/// size comparison is compressed-vs-compressed (fair), not compressed-LOGOS-vs-raw-competitor.
3601pub fn best_compressed_len(body: &[u8]) -> usize {
3602    [
3603        WireCompression::Deflate,
3604        WireCompression::Lz4,
3605        WireCompression::Zstd,
3606    ]
3607    .into_iter()
3608    .filter_map(|c| compress_body(c, body).map(|(_, z)| z.len()))
3609    .fold(body.len(), usize::min)
3610}
3611
3612/// Compress `body` with the SMALLEST of the built-in codecs (deflate / lz4 / zstd), self-describing:
3613/// byte 0 is the codec tag (0=deflate, 1=lz4, 2=zstd, 3=stored raw), the rest is the body. When
3614/// nothing shrinks the input it is stored raw under tag 3 — so this NEVER grows a payload by more
3615/// than the single tag byte (the discipline for already-encoded / already-encrypted data, which does
3616/// not compress). Compress-BEFORE-encrypt is the intended use: ciphertext is incompressible, so the
3617/// squeeze must happen on the plaintext frame. The inverse is [`decompress_any`].
3618pub fn compress_best(body: &[u8]) -> Vec<u8> {
3619    let mut best_tag = 3u8; // stored (raw)
3620    let mut best_body: Vec<u8> = body.to_vec();
3621    for codec in [
3622        WireCompression::Deflate,
3623        WireCompression::Lz4,
3624        WireCompression::Zstd,
3625    ] {
3626        if let Some((used, z)) = compress_body(codec, body) {
3627            if z.len() < best_body.len() {
3628                best_tag = compression_id(used); // records the codec ACTUALLY used (wasm zstd→lz4)
3629                best_body = z;
3630            }
3631        }
3632    }
3633    let mut out = Vec::with_capacity(best_body.len() + 1);
3634    out.push(best_tag);
3635    out.extend_from_slice(&best_body);
3636    out
3637}
3638
3639/// Inverse of [`compress_best`]: read the codec tag and inflate. `None` on a malformed or oversized
3640/// frame — the decompression-bomb bound (`MAX_DECODE_BYTES`) is inherited from `decompress_body`.
3641pub fn decompress_any(frame: &[u8]) -> Option<Vec<u8>> {
3642    let (&tag, body) = frame.split_first()?;
3643    let codec = match tag {
3644        0 => WireCompression::Deflate,
3645        1 => WireCompression::Lz4,
3646        2 => WireCompression::Zstd,
3647        3 => return Some(body.to_vec()), // stored raw
3648        _ => return None,
3649    };
3650    decompress_body(codec, body)
3651}
3652
3653/// Inflate `body` that was compressed with `codec`. `None` on any malformed input.
3654fn decompress_body(codec: WireCompression, body: &[u8]) -> Option<Vec<u8>> {
3655    match codec {
3656        WireCompression::None => Some(body.to_vec()),
3657        // Decompression-bomb guards (Invariant X): a tiny compressed frame must never inflate to
3658        // gigabytes and OOM the peer. Bound EVERY codec's output at MAX_DECODE_BYTES.
3659        WireCompression::Deflate => {
3660            miniz_oxide::inflate::decompress_to_vec_with_limit(body, MAX_DECODE_BYTES).ok()
3661        }
3662        WireCompression::Lz4 => {
3663            // lz4's 4-byte little-endian size prefix is untrusted — reject a huge claim BEFORE
3664            // `decompress_size_prepended` allocates it.
3665            if body.len() < 4 {
3666                return None;
3667            }
3668            let claimed = u32::from_le_bytes([body[0], body[1], body[2], body[3]]) as usize;
3669            if claimed > MAX_DECODE_BYTES {
3670                return None;
3671            }
3672            lz4_flex::decompress_size_prepended(body).ok()
3673        }
3674        WireCompression::Zstd => {
3675            #[cfg(not(target_arch = "wasm32"))]
3676            {
3677                use std::io::Read;
3678                // zstd carries no trusted output size, so bound the read: take one byte past the
3679                // budget and reject if the stream would exceed it.
3680                let mut out = Vec::new();
3681                let dec = zstd::stream::read::Decoder::new(body).ok()?;
3682                dec.take(MAX_DECODE_BYTES as u64 + 1)
3683                    .read_to_end(&mut out)
3684                    .ok()?;
3685                if out.len() > MAX_DECODE_BYTES {
3686                    return None;
3687                }
3688                Some(out)
3689            }
3690            #[cfg(target_arch = "wasm32")]
3691            {
3692                zstd_decode_ruzstd(body)
3693            }
3694        }
3695    }
3696}
3697
3698/// Pure-Rust zstd decode (the browser's decode path; also the native C-vs-ruzstd
3699/// parity oracle). A standard zstd frame in, the inflated bytes out.
3700fn zstd_decode_ruzstd(body: &[u8]) -> Option<Vec<u8>> {
3701    use std::io::Read;
3702    let mut dec = ruzstd::StreamingDecoder::new(body).ok()?;
3703    let mut out = Vec::new();
3704    dec.read_to_end(&mut out).ok()?;
3705    Some(out)
3706}
3707
3708// ---- Group varint (Stream VByte layout) for int arrays ------------------------
3709//
3710// Each int (zig-zag → u64) is stored at the NARROWEST of {1,2,4,8} bytes; a 2-bit
3711// width code per int packs four codes into one control byte. The control stream is
3712// written BEFORE the data stream, so widths are known up front — DECODE reads one
3713// WIDE unaligned word per int and masks it (no per-byte continuation branch, no
3714// per-element zeroing/copy), and the layout is what a SIMD shuffle consumes.
3715
3716#[inline]
3717fn gv_code(zz: u64) -> u8 {
3718    if zz <= 0xFF {
3719        0
3720    } else if zz <= 0xFFFF {
3721        1
3722    } else if zz <= 0xFFFF_FFFF {
3723        2
3724    } else {
3725        3
3726    }
3727}
3728
3729/// LEB128 varint array (`T_INTS`): a header, then one varint per element. The smallest
3730/// layout and the best *scalar* decode — the default.
3731///
3732/// ADAPTIVE SIGN MODE (zero-overhead): the header is `(count << 1) | signed`, where
3733/// `signed` is set iff the column holds a negative value. A non-negative column ships as
3734/// PLAIN LEB128 — one byte for every value `< 128`, where zig-zag would spend two (it
3735/// doubles the magnitude, halving the one-byte range to `< 64`). Non-negative data (ids,
3736/// counts, sizes, timestamps) is then up to half the bytes, matching protobuf's `int64`;
3737/// a column with any negative keeps zig-zag (protobuf's `sint64`). The mode rides the
3738/// count's low bit, so it costs ZERO extra bytes.
3739fn leb128_encode<I: Iterator<Item = i64> + Clone>(out: &mut Vec<u8>, vals: I, n: usize) {
3740    let signed = vals.clone().any(|x| x < 0);
3741    write_uvarint(((n as u64) << 1) | signed as u64, out);
3742    out.reserve(n * 2);
3743    if signed {
3744        for x in vals {
3745            write_uvarint(zigzag(x), out);
3746        }
3747    } else {
3748        for x in vals {
3749            write_uvarint(x as u64, out);
3750        }
3751    }
3752}
3753
3754/// Fixed-width array (`T_INTS_FIXED`): the `i64` buffer's little-endian bytes ARE
3755/// the wire bytes — one `memcpy` (same trick as floats).
3756fn fixed_encode_i64(out: &mut Vec<u8>, v: &[i64]) {
3757    write_uvarint(v.len() as u64, out);
3758    #[cfg(target_endian = "little")]
3759    {
3760        // SAFETY: reading `&[i64]` as `&[u8]` of the same byte length.
3761        let bytes = unsafe {
3762            std::slice::from_raw_parts(v.as_ptr().cast::<u8>(), std::mem::size_of_val(v))
3763        };
3764        out.extend_from_slice(bytes);
3765    }
3766    #[cfg(target_endian = "big")]
3767    {
3768        out.reserve(v.len() * 8);
3769        for &n in v {
3770            out.extend_from_slice(&n.to_le_bytes());
3771        }
3772    }
3773}
3774
3775fn gv_encode<I: Iterator<Item = i64> + Clone>(out: &mut Vec<u8>, vals: I, n: usize) {
3776    write_uvarint(n as u64, out);
3777    let control_at = out.len();
3778    out.resize(control_at + n.div_ceil(4), 0);
3779    out.reserve(n * 2);
3780    for (i, x) in vals.enumerate() {
3781        let zz = zigzag(x);
3782        let code = gv_code(zz);
3783        out[control_at + (i >> 2)] |= code << ((i & 3) * 2);
3784        out.extend_from_slice(&zz.to_le_bytes()[..1usize << code]);
3785    }
3786}
3787
3788fn gv_decode(buf: &[u8], pos: &mut usize) -> Option<Vec<i64>> {
3789    let n = read_uvarint(buf, pos)? as usize;
3790    let control_len = n.div_ceil(4);
3791    let control = buf.get(*pos..pos.checked_add(control_len)?)?;
3792    let mut dpos = *pos + control_len;
3793    let len = buf.len();
3794    let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
3795    for i in 0..n {
3796        let code = (control[i >> 2] >> ((i & 3) * 2)) & 0x3;
3797        let width = 1usize << code;
3798        let zz = if dpos + 8 <= len {
3799            // Fast path: one wide load, mask off the high `8 - width` bytes. The
3800            // `+ 8 <= len` guard makes the fixed-size read in-bounds.
3801            let word = u64::from_le_bytes(buf[dpos..dpos + 8].try_into().unwrap());
3802            let mask = if width == 8 {
3803                u64::MAX
3804            } else {
3805                (1u64 << (width * 8)) - 1
3806            };
3807            word & mask
3808        } else {
3809            // Safe tail near the buffer end: an exact-width read.
3810            let raw = buf.get(dpos..dpos.checked_add(width)?)?;
3811            let mut b = [0u8; 8];
3812            b[..width].copy_from_slice(raw);
3813            u64::from_le_bytes(b)
3814        };
3815        dpos += width;
3816        v.push(unzigzag(zz));
3817    }
3818    *pos = dpos;
3819    Some(v)
3820}
3821
3822/// Decode a group-varint (`T_INTS_GV`) block, taking the SSSE3 shuffle fast path
3823/// when the CPU has it and falling back to [`gv_decode`] otherwise. Both produce
3824/// bit-identical output — `gv_decode` is the oracle the SIMD path is fuzzed against.
3825fn gv_decode_dispatch(buf: &[u8], pos: &mut usize) -> Option<Vec<i64>> {
3826    #[cfg(target_arch = "x86_64")]
3827    {
3828        if is_x86_feature_detected!("ssse3") {
3829            // SAFETY: guarded by the runtime SSSE3 feature check.
3830            return unsafe { gv_decode_ssse3(buf, pos) };
3831        }
3832    }
3833    gv_decode(buf, pos)
3834}
3835
3836/// The 16 PSHUFB control masks, indexed by `(code_a << 2) | code_b` where each
3837/// code ∈ {0,1,2,3} selects a width of {1,2,4,8} bytes. Lane 0 gathers int A's
3838/// `width_a` low bytes (rest zeroed); lane 1 gathers int B's `width_b` bytes from
3839/// offset `width_a`. A `0x80` index makes PSHUFB write a zero byte.
3840#[cfg(target_arch = "x86_64")]
3841fn gv_shuffle_masks() -> &'static [[u8; 16]; 16] {
3842    use std::sync::OnceLock;
3843    static MASKS: OnceLock<[[u8; 16]; 16]> = OnceLock::new();
3844    MASKS.get_or_init(|| {
3845        let mut m = [[0x80u8; 16]; 16];
3846        for ca in 0..4usize {
3847            for cb in 0..4usize {
3848                let (wa, wb) = (1usize << ca, 1usize << cb);
3849                let entry = &mut m[(ca << 2) | cb];
3850                for j in 0..wa {
3851                    entry[j] = j as u8;
3852                }
3853                for k in 0..wb {
3854                    entry[8 + k] = (wa + k) as u8;
3855                }
3856            }
3857        }
3858        m
3859    })
3860}
3861
3862/// SSSE3 group-varint decode: two ints per PSHUFB. Each 16-byte data load holds
3863/// both ints' little-endian bytes back-to-back; one shuffle splats them into the
3864/// two 8-byte lanes of an XMM register, then we read the lanes as `u64`s. The
3865/// `dpos + 16 <= len` guard keeps the wide load in-bounds; the odd/near-end tail
3866/// is finished with the exact-width scalar reader.
3867#[cfg(target_arch = "x86_64")]
3868#[target_feature(enable = "ssse3")]
3869unsafe fn gv_decode_ssse3(buf: &[u8], pos: &mut usize) -> Option<Vec<i64>> {
3870    use std::arch::x86_64::*;
3871    let n = read_uvarint(buf, pos)? as usize;
3872    let control_len = n.div_ceil(4);
3873    let control = buf.get(*pos..pos.checked_add(control_len)?)?;
3874    let mut dpos = *pos + control_len;
3875    let len = buf.len();
3876    let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
3877    let masks = gv_shuffle_masks();
3878    let mut i = 0;
3879    while i + 2 <= n && dpos + 16 <= len {
3880        let ctrl = control[i >> 2];
3881        let ca = ((ctrl >> ((i & 3) * 2)) & 0x3) as usize;
3882        let cb = ((ctrl >> (((i + 1) & 3) * 2)) & 0x3) as usize;
3883        let data = _mm_loadu_si128(buf.as_ptr().add(dpos).cast());
3884        let mask = _mm_loadu_si128(masks[(ca << 2) | cb].as_ptr().cast());
3885        let out = _mm_shuffle_epi8(data, mask);
3886        let mut tmp = [0u8; 16];
3887        _mm_storeu_si128(tmp.as_mut_ptr().cast(), out);
3888        v.push(unzigzag(u64::from_le_bytes(tmp[0..8].try_into().unwrap())));
3889        v.push(unzigzag(u64::from_le_bytes(tmp[8..16].try_into().unwrap())));
3890        dpos += (1usize << ca) + (1usize << cb);
3891        i += 2;
3892    }
3893    while i < n {
3894        let code = (control[i >> 2] >> ((i & 3) * 2)) & 0x3;
3895        let width = 1usize << code;
3896        let raw = buf.get(dpos..dpos.checked_add(width)?)?;
3897        let mut b = [0u8; 8];
3898        b[..width].copy_from_slice(raw);
3899        v.push(unzigzag(u64::from_le_bytes(b)));
3900        dpos += width;
3901        i += 1;
3902    }
3903    *pos = dpos;
3904    Some(v)
3905}
3906
3907#[inline]
3908fn write_uvarint(mut x: u64, out: &mut Vec<u8>) {
3909    while x >= 0x80 {
3910        out.push((x as u8) | 0x80);
3911        x >>= 7;
3912    }
3913    out.push(x as u8);
3914}
3915
3916#[inline]
3917fn read_uvarint(buf: &[u8], pos: &mut usize) -> Option<u64> {
3918    let mut result = 0u64;
3919    let mut shift = 0u32;
3920    loop {
3921        let b = *buf.get(*pos)?;
3922        *pos += 1;
3923        if shift >= 64 {
3924            return None; // overlong / overflow
3925        }
3926        result |= u64::from(b & 0x7f) << shift;
3927        if b & 0x80 == 0 {
3928            return Some(result);
3929        }
3930        shift += 7;
3931    }
3932}
3933
3934#[inline]
3935fn zigzag(x: i64) -> u64 {
3936    ((x << 1) ^ (x >> 63)) as u64
3937}
3938
3939#[inline]
3940fn unzigzag(x: u64) -> i64 {
3941    ((x >> 1) as i64) ^ -((x & 1) as i64)
3942}
3943
3944#[inline]
3945fn write_str(s: &str, out: &mut Vec<u8>) {
3946    write_uvarint(s.len() as u64, out);
3947    out.extend_from_slice(s.as_bytes());
3948}
3949
3950#[inline]
3951fn read_str(buf: &[u8], pos: &mut usize) -> Option<String> {
3952    let n = read_uvarint(buf, pos)? as usize;
3953    let bytes = buf.get(*pos..pos.checked_add(n)?)?;
3954    *pos += n;
3955    String::from_utf8(bytes.to_vec()).ok()
3956}
3957
3958/// Write a flat string array: count, each element's byte length (varint, derived
3959/// from the cumulative `ends`), then the whole bytes blob in one copy.
3960/// Dictionary-encode a string column (`T_STRINGS_DICT`): each distinct string is shipped once,
3961/// then a bit-packed per-row index into that dictionary — the string twin of [`dict_encode`].
3962/// A win exactly when cardinality is low (categorical labels); `emit_best_string_column` keeps
3963/// it only if it beats the plain flat array, so it is never larger.
3964fn dict_encode_strings(data: &[u8], ends: &[u32]) -> Vec<u8> {
3965    let n = ends.len();
3966    let mut dict: Vec<&[u8]> = Vec::new();
3967    let mut index_of: std::collections::HashMap<&[u8], u64> = std::collections::HashMap::new();
3968    let mut indices: Vec<u64> = Vec::with_capacity(n);
3969    let mut prev = 0u32;
3970    for &e in ends {
3971        let s = &data[prev as usize..e as usize];
3972        prev = e;
3973        let idx = *index_of.entry(s).or_insert_with(|| {
3974            dict.push(s);
3975            (dict.len() - 1) as u64
3976        });
3977        indices.push(idx);
3978    }
3979    let mut out = vec![T_STRINGS_DICT];
3980    write_uvarint(dict.len() as u64, &mut out);
3981    for d in &dict {
3982        write_uvarint(d.len() as u64, &mut out);
3983        out.extend_from_slice(d);
3984    }
3985    write_uvarint(n as u64, &mut out);
3986    let iw = if dict.len() <= 1 {
3987        0
3988    } else {
3989        (64 - ((dict.len() - 1) as u64).leading_zeros()) as u8
3990    };
3991    out.push(iw);
3992    if iw > 0 {
3993        out.extend_from_slice(&bitpack(&indices, iw));
3994    }
3995    out
3996}
3997
3998/// Emit the smaller of the plain flat string array and the dictionary form. The flat array is
3999/// always a candidate, so the result is never larger than `T_STRINGS`.
4000/// The `(start, end)` byte range of each string in a flat `data`/`ends` column.
4001fn string_slices<'a>(data: &'a [u8], ends: &[u32]) -> Vec<&'a [u8]> {
4002    let mut out = Vec::with_capacity(ends.len());
4003    let mut prev = 0usize;
4004    for &e in ends {
4005        out.push(&data[prev..e as usize]);
4006        prev = e as usize;
4007    }
4008    out
4009}
4010
4011/// If every string is `<prefix><n><suffix>` for a common `prefix`/`suffix` and an AFFINE sequence of
4012/// integers `n = base + i·stride` whose EXACT decimal spelling is the middle (so reconstruction is
4013/// byte-perfect — no zero-padding / `+` quirks), return the templated encoding: the two affixes once +
4014/// `(base, stride, count)`. Sequential-id URLs / paths / labels (`item_0…item_999`, `…/items/0…`)
4015/// collapse from O(n) to a handful of bytes — the string twin of the affine int generator.
4016fn try_template_encode(data: &[u8], ends: &[u32]) -> Option<Vec<u8>> {
4017    let n = ends.len();
4018    if n < 3 {
4019        return None;
4020    }
4021    let strs = string_slices(data, ends);
4022    let first = strs[0];
4023    // Common prefix (byte-wise).
4024    let mut prefix_len = first.len();
4025    for s in &strs[1..] {
4026        let lim = prefix_len.min(s.len());
4027        let mut i = 0;
4028        while i < lim && first[i] == s[i] {
4029            i += 1;
4030        }
4031        prefix_len = i;
4032    }
4033    // Common suffix of the PREFIX-STRIPPED remainders (so prefix and suffix never overlap).
4034    let mut suffix_len = first.len() - prefix_len;
4035    for s in &strs[1..] {
4036        let lim = suffix_len.min(s.len() - prefix_len);
4037        let mut i = 0;
4038        while i < lim && first[first.len() - 1 - i] == s[s.len() - 1 - i] {
4039            i += 1;
4040        }
4041        suffix_len = i;
4042    }
4043    // Parse each middle as an i64 whose canonical decimal spelling is exactly the middle bytes.
4044    let mut nums = Vec::with_capacity(n);
4045    for s in &strs {
4046        let mid = &s[prefix_len..s.len() - suffix_len];
4047        let mid_str = std::str::from_utf8(mid).ok()?;
4048        let num: i64 = mid_str.parse().ok()?;
4049        if num.to_string().as_bytes() != mid {
4050            return None;
4051        }
4052        nums.push(num);
4053    }
4054    let (base, stride) = detect_affine(&nums)?;
4055    let mut c = vec![T_STRINGS_TEMPLATE];
4056    write_uvarint(prefix_len as u64, &mut c);
4057    c.extend_from_slice(&first[..prefix_len]);
4058    write_uvarint(suffix_len as u64, &mut c);
4059    c.extend_from_slice(&first[first.len() - suffix_len..]);
4060    write_uvarint(zigzag(base), &mut c);
4061    write_uvarint(zigzag(stride), &mut c);
4062    write_uvarint(n as u64, &mut c);
4063    Some(c)
4064}
4065
4066/// Front-code the column IN ORDER: each string ships `(shared-prefix-len-with-the-previous, suffix)`.
4067/// Sorted or hierarchical columns — file paths, object-store keys, zero-padded ids, sorted labels —
4068/// have adjacent strings that share long prefixes, so only the per-row delta goes on the wire. Order
4069/// is preserved (a column, not a set), so it round-trips a column the dictionary can't crush (all
4070/// strings distinct) and the template can't (non-affine / zero-padded middles). When no two adjacent
4071/// strings share a prefix it costs ~1 extra byte per row over flat — `emit_best_string_column`'s
4072/// `consider` then keeps the flat form, so this is never a loss.
4073fn front_code_strings(data: &[u8], ends: &[u32]) -> Vec<u8> {
4074    let strs = string_slices(data, ends);
4075    let mut out = vec![T_STRINGS_FRONT];
4076    write_uvarint(strs.len() as u64, &mut out);
4077    let mut prev: &[u8] = &[];
4078    for s in &strs {
4079        let lim = prev.len().min(s.len());
4080        let mut common = 0usize;
4081        while common < lim && prev[common] == s[common] {
4082            common += 1;
4083        }
4084        // Back off to a UTF-8 char boundary so the shipped suffix is itself valid UTF-8.
4085        while common > 0 && common < s.len() && (s[common] & 0xC0) == 0x80 {
4086            common -= 1;
4087        }
4088        write_uvarint(common as u64, &mut out);
4089        write_uvarint((s.len() - common) as u64, &mut out);
4090        out.extend_from_slice(&s[common..]);
4091        prev = s;
4092    }
4093    out
4094}
4095
4096/// The bit-packed bool baseline: `T_BOOLS` + count + 8 booleans per byte (LSB-first).
4097fn bool_bitpack(v: &[bool], out: &mut Vec<u8>) {
4098    out.push(T_BOOLS);
4099    write_uvarint(v.len() as u64, out);
4100    let mut cur = 0u8;
4101    let mut nbits = 0u8;
4102    for &b in v {
4103        cur |= u8::from(b) << nbits;
4104        nbits += 1;
4105        if nbits == 8 {
4106            out.push(cur);
4107            cur = 0;
4108            nbits = 0;
4109        }
4110    }
4111    if nbits > 0 {
4112        out.push(cur);
4113    }
4114}
4115
4116/// Bound on the cyclic period a bool column is probed for — small, since real bool cycles are tiny
4117/// (1 = constant, 2 = alternating, 7 = a weekly flag); a 256-bit block is still only 32 bytes.
4118const BOOL_PERIOD_CAP: usize = 256;
4119
4120/// The MINIMAL period `1 ≤ p ≤ n/2` such that `v[i] == v[i-p]` everywhere (so the column is exactly
4121/// `block[i % p]`). `p == 1` is a constant column (all-true / all-false); `p == 2` is alternating.
4122fn detect_bool_period(v: &[bool]) -> Option<usize> {
4123    let n = v.len();
4124    let cap = (n / 2).min(BOOL_PERIOD_CAP);
4125    'p: for p in 1..=cap {
4126        for i in p..n {
4127            if v[i] != v[i - p] {
4128                continue 'p;
4129            }
4130        }
4131        return Some(p);
4132    }
4133    None
4134}
4135
4136/// Ship `period p + count + one p-bit block`; the decoder replays `block[i % p]`.
4137fn bool_periodic_encode(v: &[bool], p: usize) -> Vec<u8> {
4138    let mut c = vec![T_BOOLS_PERIODIC];
4139    write_uvarint(p as u64, &mut c);
4140    write_uvarint(v.len() as u64, &mut c);
4141    let mut cur = 0u8;
4142    let mut nbits = 0u8;
4143    for &b in &v[..p] {
4144        cur |= u8::from(b) << nbits;
4145        nbits += 1;
4146        if nbits == 8 {
4147            c.push(cur);
4148            cur = 0;
4149            nbits = 0;
4150        }
4151    }
4152    if nbits > 0 {
4153        c.push(cur);
4154    }
4155    c
4156}
4157
4158/// Ship `first value + the run lengths` (runs alternate). One big run, or a handful of clustered
4159/// flips, collapses to a few varints; a high-flip column makes this larger than bit-pack, and
4160/// `emit_best_bool_column`'s `consider` then keeps the bit-packed form.
4161fn bool_rle_encode(v: &[bool]) -> Vec<u8> {
4162    let mut c = vec![T_BOOLS_RLE];
4163    write_uvarint(v.len() as u64, &mut c);
4164    if v.is_empty() {
4165        c.push(0); // `first` placeholder so the frame layout matches the decoder (bit-pack wins anyway)
4166        write_uvarint(0, &mut c);
4167        return c;
4168    }
4169    c.push(v[0] as u8);
4170    let mut runs: Vec<u64> = Vec::new();
4171    let mut cur = v[0];
4172    let mut len = 0u64;
4173    for &b in v {
4174        if b == cur {
4175            len += 1;
4176        } else {
4177            runs.push(len);
4178            cur = b;
4179            len = 1;
4180        }
4181    }
4182    runs.push(len);
4183    write_uvarint(runs.len() as u64, &mut c);
4184    for r in runs {
4185        write_uvarint(r, &mut c);
4186    }
4187    c
4188}
4189
4190fn emit_best_bool_column(v: &[bool], out: &mut Vec<u8>) {
4191    let mut best = Vec::new();
4192    bool_bitpack(v, &mut best);
4193    if let Some(p) = detect_bool_period(v) {
4194        consider(&mut best, bool_periodic_encode(v, p));
4195    }
4196    consider(&mut best, bool_rle_encode(v));
4197    out.extend_from_slice(&best);
4198}
4199
4200/// The longest common BYTE prefix and the longest common BYTE suffix (of the prefix-stripped
4201/// remainders, so they never overlap) shared by EVERY string, each clamped back to a UTF-8 char
4202/// boundary. Shared by the affix and template encoders.
4203fn common_affix_lens(strs: &[&[u8]]) -> (usize, usize) {
4204    let first = strs[0];
4205    let mut prefix_len = first.len();
4206    for s in &strs[1..] {
4207        let lim = prefix_len.min(s.len());
4208        let mut i = 0;
4209        while i < lim && first[i] == s[i] {
4210            i += 1;
4211        }
4212        prefix_len = i;
4213    }
4214    while prefix_len > 0 && prefix_len < first.len() && (first[prefix_len] & 0xC0) == 0x80 {
4215        prefix_len -= 1;
4216    }
4217    let mut suffix_len = first.len() - prefix_len;
4218    for s in &strs[1..] {
4219        let lim = suffix_len.min(s.len() - prefix_len);
4220        let mut i = 0;
4221        while i < lim && first[first.len() - 1 - i] == s[s.len() - 1 - i] {
4222            i += 1;
4223        }
4224        suffix_len = i;
4225    }
4226    while suffix_len > 0 && (first[first.len() - suffix_len] & 0xC0) == 0x80 {
4227        suffix_len -= 1;
4228    }
4229    (prefix_len, suffix_len)
4230}
4231
4232/// If every string is `<prefix><middle><suffix>` for a common `prefix`/`suffix` (ARBITRARY middles —
4233/// no affine constraint), ship the two affixes ONCE + each middle. Catches the column the dictionary
4234/// can't (all distinct), the template can't (non-affine middles), and front-coding can't (the shared
4235/// part is a SUFFIX): emails `…@example.com`, files `…​.log`, wrapped ids `v…​.json`. Worthwhile only
4236/// when there's a shared affix; `consider` keeps the flat form otherwise.
4237fn try_affix_encode(data: &[u8], ends: &[u32]) -> Option<Vec<u8>> {
4238    let n = ends.len();
4239    if n < 2 {
4240        return None;
4241    }
4242    let strs = string_slices(data, ends);
4243    let (prefix_len, suffix_len) = common_affix_lens(&strs);
4244    if prefix_len + suffix_len == 0 {
4245        return None;
4246    }
4247    let first = strs[0];
4248    let mut c = vec![T_STRINGS_AFFIX];
4249    write_uvarint(prefix_len as u64, &mut c);
4250    c.extend_from_slice(&first[..prefix_len]);
4251    write_uvarint(suffix_len as u64, &mut c);
4252    c.extend_from_slice(&first[first.len() - suffix_len..]);
4253    write_uvarint(n as u64, &mut c);
4254    for s in &strs {
4255        let mid = &s[prefix_len..s.len() - suffix_len];
4256        write_uvarint(mid.len() as u64, &mut c);
4257        c.extend_from_slice(mid);
4258    }
4259    Some(c)
4260}
4261
4262fn emit_best_string_column(data: &[u8], ends: &[u32], out: &mut Vec<u8>) {
4263    let mut best = Vec::new();
4264    write_string_array_from_ends(&mut best, data, ends);
4265    consider(&mut best, dict_encode_strings(data, ends));
4266    consider(&mut best, front_code_strings(data, ends));
4267    if let Some(tpl) = try_template_encode(data, ends) {
4268        consider(&mut best, tpl);
4269    }
4270    if let Some(affix) = try_affix_encode(data, ends) {
4271        consider(&mut best, affix);
4272    }
4273    out.extend_from_slice(&best);
4274}
4275
4276fn write_string_array_from_ends(out: &mut Vec<u8>, data: &[u8], ends: &[u32]) {
4277    out.push(T_STRINGS);
4278    write_uvarint(ends.len() as u64, out);
4279    let mut prev = 0u32;
4280    for &e in ends {
4281        write_uvarint(u64::from(e - prev), out);
4282        prev = e;
4283    }
4284    out.extend_from_slice(data);
4285}
4286
4287/// If `v` is a non-empty run of structs that all share one `type_name` and the
4288/// same field-name set, return `(type_name, sorted_field_names)` — the schema for a
4289/// columnar [`T_STRUCTS`] encoding. `None` otherwise (the list stays boxed). The
4290/// field order is canonical (sorted), matching the per-struct [`T_STRUCT`] path, so
4291/// a round-trip is byte-stable.
4292fn struct_schema(v: &[RuntimeValue]) -> Option<(String, Vec<String>)> {
4293    let first = match v.first()? {
4294        RuntimeValue::Struct(s) => s,
4295        _ => return None,
4296    };
4297    let mut names: Vec<String> = first.fields.keys().cloned().collect();
4298    names.sort();
4299    // A columnar store needs ≥1 column to carry the row count — a zero-field struct
4300    // list stays boxed (encodes as the generic per-element list).
4301    if names.is_empty() {
4302        return None;
4303    }
4304    for item in v {
4305        match item {
4306            RuntimeValue::Struct(s)
4307                if s.type_name == first.type_name
4308                    && s.fields.len() == names.len()
4309                    && names.iter().all(|n| s.fields.contains_key(n)) => {}
4310            _ => return None,
4311        }
4312    }
4313    Some((first.type_name.clone(), names))
4314}
4315
4316/// Emit a homogeneous struct list: the schema (type name + field names) followed by
4317/// `encode_columns`. With no schema cache active it is the self-describing `T_STRUCTS`
4318/// (schema inline). With a cache, the first occurrence of a schema is a `T_STRUCTS_DEF`
4319/// (schema inline + registered under an id, still self-decodable) and later ones are a
4320/// `T_STRUCTS_REF` (just the id) — the cross-message win. The columns are identical in
4321/// all three forms, so the only difference is whether the schema strings are present.
4322/// Write a struct schema (type name + field names) — the exact byte layout
4323/// [`read_struct_schema`] consumes. Shared by the struct-list and single-struct
4324/// schema-dictionary paths so their `def` forms can never drift.
4325fn write_struct_schema(type_name: &str, field_names: &[String], out: &mut Vec<u8>) {
4326    write_str(type_name, out);
4327    write_uvarint(field_names.len() as u64, out);
4328    for f in field_names {
4329        write_str(f, out);
4330    }
4331}
4332
4333fn emit_struct_list(
4334    type_name: &str,
4335    field_names: &[String],
4336    n: usize,
4337    out: &mut Vec<u8>,
4338    encode_columns: impl FnOnce(&mut Vec<u8>) -> Result<(), String>,
4339) -> Result<(), String> {
4340    let write_schema = |out: &mut Vec<u8>| write_struct_schema(type_name, field_names, out);
4341    if let Some(id) = type_registry_id(type_name, field_names) {
4342        // Both ends share this type's definition (the program-derived registry): ship the
4343        // small id and the columns only — type/field NAMES never go on the wire. The
4344        // struct-list analog of `T_STRUCT_TID`; takes precedence over the schema cache,
4345        // mirroring the single-struct path.
4346        out.push(T_STRUCTS_TID);
4347        write_uvarint(id as u64, out);
4348        write_uvarint(n as u64, out);
4349        return encode_columns(out);
4350    }
4351    match schema_send(type_name, field_names) {
4352        SchemaEmit::Inline => {
4353            out.push(T_STRUCTS);
4354            write_schema(out);
4355        }
4356        SchemaEmit::SeqDef(id) => {
4357            out.push(T_STRUCTS_DEF);
4358            write_uvarint(id as u64, out);
4359            write_schema(out);
4360        }
4361        SchemaEmit::SeqRef(id) => {
4362            out.push(T_STRUCTS_REF);
4363            write_uvarint(id as u64, out);
4364        }
4365        SchemaEmit::CaDef => {
4366            out.push(T_STRUCTS_CDEF);
4367            write_schema(out);
4368        }
4369        SchemaEmit::CaRef(fp) => {
4370            out.push(T_STRUCTS_CREF);
4371            out.extend_from_slice(&fp.to_le_bytes());
4372        }
4373    }
4374    write_uvarint(n as u64, out);
4375    encode_columns(out)
4376}
4377
4378/// Encode a homogeneous struct list in the random-access `T_STRUCTS_VIEW` layout: the
4379/// shared schema (type + sorted field names) once, then a per-ROW byte-offset table, then
4380/// each row's own per-FIELD byte-offset table followed by its values. A `WireView` reaches
4381/// ANY (row, field) in O(1) via the two offset tables — the record-list analog of the
4382/// single-struct view, beating Cap'n Proto's `items.get(i).get_field()` on open + access.
4383/// `field_names` MUST be canonically sorted and `get(row, field_index)` returns that row's
4384/// value for `field_names[field_index]`, so the Boxed and columnar `Structs` sources emit
4385/// byte-identical output for the same logical data.
4386fn emit_structs_view(
4387    type_name: &str,
4388    field_names: &[String],
4389    n: usize,
4390    out: &mut Vec<u8>,
4391    mut get: impl FnMut(usize, usize) -> RuntimeValue,
4392) -> Result<(), String> {
4393    let f = field_names.len();
4394    out.push(T_STRUCTS_VIEW);
4395    write_str(type_name, out);
4396    write_uvarint(f as u64, out);
4397    for name in field_names {
4398        write_str(name, out);
4399    }
4400    write_uvarint(n as u64, out);
4401    let row_table_pos = out.len();
4402    out.resize(row_table_pos + n * 4, 0);
4403    let rows_start = out.len();
4404    let mut row_offsets: Vec<u32> = Vec::with_capacity(n);
4405    for r in 0..n {
4406        row_offsets.push((out.len() - rows_start) as u32);
4407        let field_table_pos = out.len();
4408        out.resize(field_table_pos + f * 4, 0);
4409        let values_start = out.len();
4410        let mut field_offsets: Vec<u32> = Vec::with_capacity(f);
4411        for fi in 0..f {
4412            field_offsets.push((out.len() - values_start) as u32);
4413            native_encode(&get(r, fi), out)?;
4414        }
4415        for (i, off) in field_offsets.iter().enumerate() {
4416            out[field_table_pos + i * 4..field_table_pos + i * 4 + 4]
4417                .copy_from_slice(&off.to_le_bytes());
4418        }
4419    }
4420    for (i, off) in row_offsets.iter().enumerate() {
4421        out[row_table_pos + i * 4..row_table_pos + i * 4 + 4].copy_from_slice(&off.to_le_bytes());
4422    }
4423    Ok(())
4424}
4425
4426/// The columnar fast path for [`emit_structs_view`]: writes each (row, field) cell straight
4427/// from the typed column instead of materializing a `RuntimeValue` per cell (the boxed `get`
4428/// allocates a fresh `Rc<String>` for every text cell — the dominant cost on record lists).
4429/// Byte-identical to `emit_structs_view` for the same logical data.
4430fn emit_structs_view_columnar(
4431    type_name: &str,
4432    field_names: &[String],
4433    columns: &[ListRepr],
4434    out: &mut Vec<u8>,
4435) -> Result<(), String> {
4436    let f = field_names.len();
4437    let n = columns.first().map_or(0, |c| c.len());
4438    out.push(T_STRUCTS_VIEW);
4439    write_str(type_name, out);
4440    write_uvarint(f as u64, out);
4441    for name in field_names {
4442        write_str(name, out);
4443    }
4444    write_uvarint(n as u64, out);
4445    let row_table_pos = out.len();
4446    out.resize(row_table_pos + n * 4, 0);
4447    let rows_start = out.len();
4448    let mut row_offsets: Vec<u32> = Vec::with_capacity(n);
4449    let mut field_offsets: Vec<u32> = Vec::with_capacity(f);
4450    for r in 0..n {
4451        row_offsets.push((out.len() - rows_start) as u32);
4452        let field_table_pos = out.len();
4453        out.resize(field_table_pos + f * 4, 0);
4454        let values_start = out.len();
4455        field_offsets.clear();
4456        for col in columns {
4457            field_offsets.push((out.len() - values_start) as u32);
4458            write_view_cell(col, r, out)?;
4459        }
4460        for (i, off) in field_offsets.iter().enumerate() {
4461            out[field_table_pos + i * 4..field_table_pos + i * 4 + 4]
4462                .copy_from_slice(&off.to_le_bytes());
4463        }
4464    }
4465    for (i, off) in row_offsets.iter().enumerate() {
4466        out[row_table_pos + i * 4..row_table_pos + i * 4 + 4].copy_from_slice(&off.to_le_bytes());
4467    }
4468    Ok(())
4469}
4470
4471/// Write one struct-view cell straight from its typed column, matching `native_encode`'s
4472/// tagged bytes exactly. Uncommon column types fall back to materialize-then-encode.
4473fn write_view_cell(col: &ListRepr, row: usize, out: &mut Vec<u8>) -> Result<(), String> {
4474    match col {
4475        ListRepr::Ints(v) => {
4476            out.push(T_INT);
4477            write_uvarint(zigzag(v[row]), out);
4478        }
4479        ListRepr::IntsI32(v) => {
4480            out.push(T_INT);
4481            write_uvarint(zigzag(v[row] as i64), out);
4482        }
4483        ListRepr::Floats(v) => {
4484            out.push(T_FLOAT);
4485            put_f64(out, v[row]);
4486        }
4487        ListRepr::Bools(v) => out.push(if v[row] { T_TRUE } else { T_FALSE }),
4488        ListRepr::Strings { data, ends, .. } => {
4489            let start = if row == 0 { 0 } else { ends[row - 1] as usize };
4490            let end = ends[row] as usize;
4491            let s = &data[start..end];
4492            out.push(T_TEXT);
4493            write_uvarint(s.len() as u64, out);
4494            out.extend_from_slice(s);
4495        }
4496        other => native_encode(
4497            &other
4498                .get(row)
4499                .ok_or("struct-view column row out of bounds")?,
4500            out,
4501        )?,
4502    }
4503    Ok(())
4504}
4505
4506/// Encode a record list as a FIXED-stride view (`T_STRUCTS_FVIEW`): every row is the same width
4507/// (Int/Float = 8 B raw, Bool = 1 B, Text = an 8 B (offset,len) into a trailing string blob), so a
4508/// reader reaches any (row, field) by ARITHMETIC — no row/field offset tables at all. Smaller than
4509/// the variable view and a near-memcpy encode, while still O(1) random-access. The `indexed fast`
4510/// form: the struct-view composed with the fixed numeric dial. `None` (out untouched) if any column
4511/// is not a fixed-width leaf, so the caller falls back to the variable offset-table view.
4512fn emit_structs_view_fixed(
4513    type_name: &str,
4514    field_names: &[String],
4515    columns: &[ListRepr],
4516    out: &mut Vec<u8>,
4517) -> Option<()> {
4518    let kinds = columns_fview_kinds(columns)?; // checked BEFORE any write — out stays clean on None
4519    let f = field_names.len();
4520    let n = columns.first().map_or(0, |c| c.len());
4521    let (_, stride) = fview_layout(&kinds);
4522    out.push(T_STRUCTS_FVIEW);
4523    write_str(type_name, out);
4524    write_uvarint(f as u64, out);
4525    for name in field_names {
4526        write_str(name, out);
4527    }
4528    out.extend_from_slice(&kinds);
4529    write_uvarint(n as u64, out);
4530    out.reserve(n.saturating_mul(stride));
4531    let mut blob: Vec<u8> = Vec::new();
4532    for r in 0..n {
4533        for col in columns {
4534            match col {
4535                ListRepr::Ints(v) => out.extend_from_slice(&v[r].to_le_bytes()),
4536                ListRepr::IntsI32(v) => out.extend_from_slice(&(v[r] as i64).to_le_bytes()),
4537                ListRepr::Floats(v) => put_f64(out, v[r]),
4538                ListRepr::Bools(v) => out.push(v[r] as u8),
4539                ListRepr::Strings { data, ends, .. } => {
4540                    let start = if r == 0 { 0 } else { ends[r - 1] as usize };
4541                    let end = ends[r] as usize;
4542                    let off = blob.len() as u32;
4543                    let len = (end - start) as u32;
4544                    blob.extend_from_slice(&data[start..end]);
4545                    out.extend_from_slice(&off.to_le_bytes());
4546                    out.extend_from_slice(&len.to_le_bytes());
4547                }
4548                // `columns_fview_kinds` already proved every column is one of the above.
4549                _ => unreachable!("non-fixed-viewable column passed the kind check"),
4550            }
4551        }
4552    }
4553    write_uvarint(blob.len() as u64, out);
4554    out.extend_from_slice(&blob);
4555    Some(())
4556}
4557
4558/// Emit an 8-byte-aligned i64 column (`T_INTS_ALIGNED`) the receiver reads as `&[i64]` with no
4559/// copy. The pad lands the blob's body offset ≡ 7 mod 8 → ≡ 0 mod 8 once the (≡ 1 mod 8) frame
4560/// header is prepended, so the slice cast is sound. Shared by the `RuntimeValue` encode path and
4561/// the build-in-place [`build_columnar_record`] API so both produce byte-identical aligned columns.
4562fn emit_aligned_i64(v: &[i64], out: &mut Vec<u8>) {
4563    out.push(T_INTS_ALIGNED);
4564    write_uvarint(v.len() as u64, out);
4565    let after_count = out.len();
4566    let pad = (14 - after_count % 8) % 8;
4567    out.push(pad as u8);
4568    out.resize(out.len() + pad, 0);
4569    #[cfg(target_endian = "little")]
4570    {
4571        // SAFETY: an `&[i64]` reinterpreted as `&[u8]` of the same byte length.
4572        let raw = unsafe { std::slice::from_raw_parts(v.as_ptr().cast::<u8>(), v.len() * 8) };
4573        out.extend_from_slice(raw);
4574    }
4575    #[cfg(target_endian = "big")]
4576    {
4577        out.reserve(v.len() * 8);
4578        for &n in v {
4579            out.extend_from_slice(&n.to_le_bytes());
4580        }
4581    }
4582}
4583
4584/// Emit an 8-byte-aligned f64 column (`T_FLOATS_ALIGNED`), the float twin of [`emit_aligned_i64`].
4585fn emit_aligned_f64(v: &[f64], out: &mut Vec<u8>) {
4586    out.push(T_FLOATS_ALIGNED);
4587    write_uvarint(v.len() as u64, out);
4588    let after_count = out.len();
4589    let pad = (14 - after_count % 8) % 8;
4590    out.push(pad as u8);
4591    out.resize(out.len() + pad, 0);
4592    put_f64s(out, v);
4593}
4594
4595/// One column of a [`build_columnar_record`] message — a borrowed slice that lands in the wire's
4596/// zero-copy aligned layout with no intermediate `RuntimeValue`.
4597#[derive(Clone, Copy)]
4598pub enum WireColumn<'a> {
4599    /// An i64 column → `T_INTS_ALIGNED`, read back as `&[i64]`.
4600    Ints(&'a [i64]),
4601    /// An f64 column → `T_FLOATS_ALIGNED`, read back as `&[f64]`.
4602    Floats(&'a [f64]),
4603}
4604
4605/// Build a columnar record message **in place** — Cap'n Proto's home turf. The named columns are
4606/// written DIRECTLY into the offset-table `T_STRUCT_VIEW` + `T_*_ALIGNED` wire layout from borrowed
4607/// slices: no intermediate `RuntimeValue`, no second serialize pass over the data (each column is a
4608/// single `memcpy`). The receiver opens it with [`view_message`] and reads ANY column in O(1) and
4609/// zero-copy via [`WireView::struct_field`] + [`WireView::as_i64_slice`]/[`WireView::as_f64_slice`].
4610///
4611/// This is the encode side of the dual zero-encode / zero-decode story: where capnp builds the
4612/// message in its wire buffer and reads it in place, this writes the same aligned layout in one pass
4613/// and reads it back with no decode — while staying name-elided and 24–34 % smaller on the wire.
4614pub fn build_columnar_record(
4615    from: &str,
4616    type_name: &str,
4617    fields: &[(&str, WireColumn)],
4618) -> Vec<u8> {
4619    // Canonical field order (by name) — byte-identical to the `RuntimeValue` struct-view path and
4620    // deterministic regardless of the caller's insertion order.
4621    let mut fields: Vec<(&str, WireColumn)> = fields.to_vec();
4622    fields.sort_by(|a, b| a.0.cmp(b.0));
4623    let mut out = Vec::with_capacity(from.len() + type_name.len() + 32 + fields.len() * 64);
4624    write_str(from, &mut out);
4625    out.push(T_STRUCT_VIEW);
4626    write_str(type_name, &mut out);
4627    write_uvarint(fields.len() as u64, &mut out);
4628    for (name, _) in &fields {
4629        write_str(name, &mut out);
4630    }
4631    let table_pos = out.len();
4632    out.resize(table_pos + fields.len() * 4, 0);
4633    let values_start = out.len();
4634    let mut offsets: Vec<u32> = Vec::with_capacity(fields.len());
4635    for (_, col) in fields {
4636        offsets.push((out.len() - values_start) as u32);
4637        match col {
4638            WireColumn::Ints(d) => emit_aligned_i64(d, &mut out),
4639            WireColumn::Floats(d) => emit_aligned_f64(d, &mut out),
4640        }
4641    }
4642    for (i, off) in offsets.iter().enumerate() {
4643        out[table_pos + i * 4..table_pos + i * 4 + 4].copy_from_slice(&off.to_le_bytes());
4644    }
4645    frame(
4646        WireCodec::Native,
4647        current_integrity(),
4648        WireCompression::None,
4649        out,
4650    )
4651}
4652
4653/// Encode a `ListRepr` to the wire — the packed/columnar path. Homogeneous arrays
4654/// go out as one tag + the typed buffer (no per-element tag, no boxing); a `Structs`
4655/// repr streams its in-memory columns straight out (a near-memcpy). Shared by the
4656/// `RuntimeValue::List` arm and, recursively, by struct columns.
4657fn encode_list_repr(repr: &ListRepr, out: &mut Vec<u8>) -> Result<(), String> {
4658    let _depth = DepthGuard::enter()?;
4659    // Flat mode: one uniform, columnar-free `T_LIST` regardless of the in-memory repr — the
4660    // format the shared wire core decodes. Every element is a fully tagged value, so a native
4661    // decoder needs no columnar/string/struct/inductive special cases.
4662    // A packed `Words32` list ALSO takes this fully-tagged path: it is a forge-only pin optimization of a
4663    // `Seq of Word32` (previously `Boxed`), so encoding it as tagged `Word` values keeps the wire bytes
4664    // byte-identical to the old boxed encoding — no new columnar tag, no decoder change.
4665    if flat_lists() || matches!(repr, ListRepr::Words32(_)) {
4666        let values = repr.to_values();
4667        out.push(T_LIST);
4668        write_uvarint(values.len() as u64, out);
4669        for x in &values {
4670            native_encode(x, out)?;
4671        }
4672        return Ok(());
4673    }
4674    match repr {
4675        // A `Words32` list always took the fully-tagged flat path above (byte-identical to the old boxed
4676        // `Seq of Word32` encoding), so it never reaches this columnar dispatch.
4677        ListRepr::Words32(_) => unreachable!("Words32 encodes via the flat/tagged path"),
4678        // Integer arrays dispatch on the sender's numeric strategy; each lands on its
4679        // own tag, so the decoder reconstructs it regardless. First, if structural
4680        // encoding is on and the whole column is an exact affine progression, ship
4681        // the generating formula `(base, stride, n)` instead of the data.
4682        ListRepr::Ints(v) => {
4683            if struct_view_on() {
4684                emit_aligned_i64(v, out);
4685                return Ok(());
4686            }
4687            if structure() == WireStructure::Affine {
4688                if let Some((base, stride)) = detect_affine(v) {
4689                    out.push(T_INTS_AFFINE);
4690                    write_uvarint(zigzag(base), out);
4691                    write_uvarint(zigzag(stride), out);
4692                    write_uvarint(v.len() as u64, out);
4693                    return Ok(());
4694                }
4695            }
4696            if structure() == WireStructure::Auto {
4697                emit_best_int_column(v, out);
4698                return Ok(());
4699            }
4700            match numerics() {
4701                WireNumerics::Varint => {
4702                    out.push(T_INTS);
4703                    leb128_encode(out, v.iter().copied(), v.len());
4704                }
4705                WireNumerics::Fixed => {
4706                    out.push(T_INTS_FIXED);
4707                    fixed_encode_i64(out, v);
4708                }
4709                WireNumerics::GroupVarint => {
4710                    out.push(T_INTS_GV);
4711                    gv_encode(out, v.iter().copied(), v.len());
4712                }
4713            }
4714        }
4715        ListRepr::IntsI32(v) => {
4716            if structure() == WireStructure::Auto {
4717                let widened: Vec<i64> = v.iter().map(|&n| n as i64).collect();
4718                emit_best_int_column(&widened, out);
4719                return Ok(());
4720            }
4721            match numerics() {
4722                WireNumerics::Varint => {
4723                    out.push(T_INTS);
4724                    leb128_encode(out, v.iter().map(|&n| n as i64), v.len());
4725                }
4726                WireNumerics::Fixed => {
4727                    out.push(T_INTS_FIXED);
4728                    write_uvarint(v.len() as u64, out);
4729                    out.reserve(v.len() * 8);
4730                    for &n in v {
4731                        out.extend_from_slice(&(n as i64).to_le_bytes());
4732                    }
4733                }
4734                WireNumerics::GroupVarint => {
4735                    out.push(T_INTS_GV);
4736                    gv_encode(out, v.iter().map(|&n| n as i64), v.len());
4737                }
4738            }
4739        }
4740        ListRepr::Floats(v) => {
4741            if struct_view_on() {
4742                emit_aligned_f64(v, out);
4743                return Ok(());
4744            }
4745            // The float dial is decided AHEAD OF TIME: under `Off` this path is a pure memcpy and
4746            // never inspects the data (the lightning-quick, know-the-shape-beforehand hot path). The
4747            // closed-form generators and the shrinking menu below run ONLY when the caller opts into
4748            // structural analysis (`Affine`/`Auto`) — mirroring the integer contract exactly.
4749            let st = structure();
4750            // Ship the GENERATOR, not the data — the float twin of the int closed-forms. A constant
4751            // column is one f64 + count; a bit-exact `base + i·stride` column is three numbers. Both
4752            // are LOSSLESS (constant by `to_bits`, affine verified bit-exact), so they only ever fire
4753            // when reconstruction is perfect, and they are always smaller than the n×8 raw form.
4754            if matches!(st, WireStructure::Affine | WireStructure::Auto) {
4755                if let Some(bits) = detect_float_const(v) {
4756                    out.push(T_FLOATS_CONST);
4757                    out.extend_from_slice(&bits.to_le_bytes());
4758                    write_uvarint(v.len() as u64, out);
4759                    return Ok(());
4760                }
4761                if let Some((base, stride)) = detect_float_affine(v) {
4762                    out.push(T_FLOATS_AFFINE);
4763                    put_f64(out, base);
4764                    put_f64(out, stride);
4765                    write_uvarint(v.len() as u64, out);
4766                    return Ok(());
4767                }
4768            }
4769            // The `Auto` size-shrinking menu: a sparse / mostly-one-value column ships its dominant
4770            // value + outliers; a `base·ratioⁱ` or a cyclic `pattern[i % p]` column ships the
4771            // generator, not the samples. Each is an O(n) scan, so it runs ONLY under `Auto` — the
4772            // default path never pays for it. Every candidate is kept only when genuinely smaller
4773            // than the raw `memcpy` floor. XOR-delta is the separate `WireFloats::XorDelta` DIAL
4774            // (independent of structure), so it is tried whenever that dial is selected.
4775            let sparse: Option<Vec<u8>> = if st == WireStructure::Auto {
4776                detect_float_sparse(v).map(|(dom, exc)| {
4777                    let mut c = vec![T_FLOATS_SPARSE];
4778                    c.extend_from_slice(&dom.to_le_bytes());
4779                    write_uvarint(v.len() as u64, &mut c);
4780                    write_uvarint(exc.len() as u64, &mut c);
4781                    let mut prev = 0usize;
4782                    for (i, bits) in &exc {
4783                        write_uvarint((i - prev) as u64, &mut c);
4784                        prev = *i;
4785                        c.extend_from_slice(&bits.to_le_bytes());
4786                    }
4787                    c
4788                })
4789            } else {
4790                None
4791            };
4792            let xor: Option<Vec<u8>> = if floats_mode() == WireFloats::XorDelta {
4793                let mut body = Vec::with_capacity(v.len() + 2);
4794                floats_xor_encode(&mut body, v);
4795                if body.len() < floats_memcpy_body_len(v.len()) {
4796                    let mut c = vec![T_FLOATS_XOR];
4797                    c.extend_from_slice(&body);
4798                    Some(c)
4799                } else {
4800                    None
4801                }
4802            } else {
4803                None
4804            };
4805            let geometric: Option<Vec<u8>> = if st == WireStructure::Auto {
4806                detect_float_geometric(v).map(|(base, ratio)| {
4807                    let mut c = vec![T_FLOATS_GEOMETRIC];
4808                    put_f64(&mut c, base);
4809                    put_f64(&mut c, ratio);
4810                    write_uvarint(v.len() as u64, &mut c);
4811                    c
4812                })
4813            } else {
4814                None
4815            };
4816            let periodic: Option<Vec<u8>> = if st == WireStructure::Auto {
4817                detect_float_period(v).map(|p| {
4818                    let mut c = vec![T_FLOATS_PERIODIC];
4819                    write_uvarint(p as u64, &mut c);
4820                    write_uvarint(v.len() as u64, &mut c);
4821                    put_f64s(&mut c, &v[..p]);
4822                    c
4823                })
4824            } else {
4825                None
4826            };
4827            if let Some(c) = [sparse, xor, geometric, periodic]
4828                .into_iter()
4829                .flatten()
4830                .min_by_key(Vec::len)
4831            {
4832                // The memcpy column is `1` (tag) + body; keep the candidate only if it is smaller.
4833                if c.len() < floats_memcpy_body_len(v.len()) + 1 {
4834                    out.extend_from_slice(&c);
4835                    return Ok(());
4836                }
4837            }
4838            out.push(T_FLOATS);
4839            write_uvarint(v.len() as u64, out);
4840            // One pass over the column: each element's little-endian bytes, a NaN written canonically.
4841            put_f64s(out, v);
4842        }
4843        ListRepr::Bools(v) => {
4844            // Under the `Auto` per-column menu, run the bool generator bake-off (periodic / RLE / bit-
4845            // pack, smallest wins); otherwise the plain bit-packed array, byte-identical to before.
4846            if structure() == WireStructure::Auto {
4847                emit_best_bool_column(v, out);
4848            } else {
4849                bool_bitpack(v, out);
4850            }
4851        }
4852        ListRepr::Strings { data, ends, .. } => {
4853            // Under the `Auto` per-column menu, try the dictionary form (categorical labels) and
4854            // ship the smaller; otherwise the plain flat array.
4855            if structure() == WireStructure::Auto {
4856                emit_best_string_column(data, ends, out);
4857            } else {
4858                write_string_array_from_ends(out, data, ends);
4859            }
4860        }
4861        // A columnar struct store: the schema (inline, or by reference when a schema
4862        // cache is active), then each in-memory column streamed straight out.
4863        ListRepr::Structs {
4864            type_name,
4865            field_names,
4866            columns,
4867        } => {
4868            let n = columns.first().map_or(0, |c| c.len());
4869            if struct_view_on() {
4870                // Random-access record-list view: O(1) (row, field) reads. `field_names` is
4871                // canonically sorted (from `struct_schema`) and `columns[fi]` is its column.
4872                // With the fixed numeric dial (`indexed fast`) over all-fixed-width columns, the
4873                // FIXED-stride view drops the offset tables (smaller + pure-arithmetic O(1));
4874                // otherwise the columnar offset-table view, written straight from the typed
4875                // columns (no per-cell `RuntimeValue`), byte-identical to the boxed `get` path.
4876                if numerics() == WireNumerics::Fixed
4877                    && emit_structs_view_fixed(type_name, field_names, columns, out).is_some()
4878                {
4879                    return Ok(());
4880                }
4881                return emit_structs_view_columnar(type_name, field_names, columns, out);
4882            }
4883            emit_struct_list(type_name, field_names, n, out, |out| {
4884                for col in columns {
4885                    encode_list_repr(col, out)?;
4886                }
4887                Ok(())
4888            })?;
4889        }
4890        // A columnar enum store (tagged union): the type once, a constructor
4891        // dictionary with arities, the per-row constructor-index column, then the
4892        // dense per-constructor argument columns. Nullary enums emit just the
4893        // dictionary + index column. `ranks` are recomputed on decode (not sent).
4894        ListRepr::Inductives {
4895            inductive_type,
4896            ctor_dict,
4897            ctors,
4898            ranks: _,
4899            arg_cols,
4900        } => {
4901            out.push(T_INDUCTIVES);
4902            write_str(inductive_type, out);
4903            write_uvarint(ctor_dict.len() as u64, out);
4904            for (c, name) in ctor_dict.iter().enumerate() {
4905                write_str(name, out);
4906                write_uvarint(arg_cols[c].len() as u64, out); // arity
4907            }
4908            let idx: Vec<i64> = ctors.iter().map(|&c| c as i64).collect();
4909            encode_list_repr(&ListRepr::Ints(idx), out)?;
4910            for cols in arg_cols {
4911                for col in cols {
4912                    encode_list_repr(col, out)?;
4913                }
4914            }
4915        }
4916        // A received lazy view being re-sent: materialize its rows/elements once, then encode
4917        // through the normal path (re-columnarizes / re-views per the active dial).
4918        ListRepr::WireStructs { .. } | ListRepr::WireColumn { .. } => {
4919            let materialized = ListRepr::from_values(repr.to_values());
4920            return encode_list_repr(&materialized, out);
4921        }
4922        ListRepr::Boxed(v) => {
4923            if !v.is_empty() && v.iter().all(|x| matches!(x, RuntimeValue::Text(_))) {
4924                // ALL strings (e.g. a string-literal list). Under the `Auto` per-column menu, run the
4925                // FULL string-column menu — flat / dictionary / template — the same one the columnar
4926                // `Strings` repr gets (so categorical labels dictionary and `item_0…item_n` collapses
4927                // to a template); otherwise the plain flat array, byte-identical to the boxed path.
4928                let mut sdata = Vec::new();
4929                let mut sends = Vec::with_capacity(v.len());
4930                for x in v {
4931                    if let RuntimeValue::Text(s) = x {
4932                        sdata.extend_from_slice(s.as_bytes());
4933                        sends.push(sdata.len() as u32);
4934                    }
4935                }
4936                if structure() == WireStructure::Auto {
4937                    emit_best_string_column(&sdata, &sends, out);
4938                } else {
4939                    write_string_array_from_ends(out, &sdata, &sends);
4940                }
4941            } else if let Some((type_name, field_names)) = struct_schema(v) {
4942                if struct_view_on() {
4943                    // Random-access record-list view: O(1) (row, field) reads. `struct_schema`
4944                    // returns sorted field names, matching the columnar `Structs` repr above.
4945                    return emit_structs_view(&type_name, &field_names, v.len(), out, |row, fi| {
4946                        match &v[row] {
4947                            RuntimeValue::Struct(sv) => {
4948                                sv.fields.get(&field_names[fi]).cloned().unwrap()
4949                            }
4950                            _ => unreachable!("struct_schema guaranteed all-struct"),
4951                        }
4952                    });
4953                }
4954                // A homogeneous struct list (stored boxed) packs COLUMNAR via the same
4955                // schema-inline-or-by-reference path as the in-memory `Structs` repr.
4956                emit_struct_list(&type_name, &field_names, v.len(), out, |out| {
4957                    for fname in &field_names {
4958                        let column: Vec<RuntimeValue> = v
4959                            .iter()
4960                            .map(|s| match s {
4961                                RuntimeValue::Struct(sv) => sv.fields.get(fname).cloned().unwrap(),
4962                                _ => unreachable!("struct_schema guaranteed all-struct"),
4963                            })
4964                            .collect();
4965                        encode_list_repr(&ListRepr::from_values(column), out)?;
4966                    }
4967                    Ok(())
4968                })?;
4969            } else if let Some(ind) = ListRepr::build_inductives(v) {
4970                // A homogeneous enum list (stored boxed) packs columnar via the same
4971                // tagged-union encoding as the in-memory `Inductives` repr.
4972                encode_list_repr(&ind, out)?;
4973            } else {
4974                // A mixed list keeps per-element tags.
4975                out.push(T_LIST);
4976                write_uvarint(v.len() as u64, out);
4977                for x in v {
4978                    native_encode(x, out)?;
4979                }
4980            }
4981        }
4982    }
4983    Ok(())
4984}
4985
4986/// Encode a `Money` to the wire: its Decimal amount (sign + LE coefficient + scale) then the
4987/// ISO-4217 code. `#[inline(never)]` so its locals stay out of the recursive `native_encode` frame.
4988#[inline(never)]
4989fn encode_money(m: &logicaffeine_base::Money, out: &mut Vec<u8>) {
4990    out.push(T_MONEY);
4991    let (negative, magnitude, scale) = m.amount.to_le_bytes();
4992    out.push(negative as u8);
4993    write_uvarint(magnitude.len() as u64, out);
4994    out.extend_from_slice(&magnitude);
4995    write_uvarint(scale as u64, out);
4996    let code = m.currency.code.as_bytes();
4997    write_uvarint(code.len() as u64, out);
4998    out.extend_from_slice(code);
4999}
5000
5001/// Decode a `Money` from the wire (inverse of [`encode_money`]). `#[inline(never)]` so its locals
5002/// stay out of the recursive `native_decode` frame.
5003#[inline(never)]
5004fn decode_money(buf: &[u8], pos: &mut usize) -> Option<RuntimeValue> {
5005    let negative = *buf.get(*pos)? != 0;
5006    *pos += 1;
5007    let len = read_uvarint(buf, pos)? as usize;
5008    let bytes = buf.get(*pos..pos.checked_add(len)?)?;
5009    *pos += len;
5010    let scale = u32::try_from(read_uvarint(buf, pos)?).ok()?;
5011    let amount = logicaffeine_base::Decimal::from_le_bytes(negative, bytes, scale);
5012    let clen = read_uvarint(buf, pos)? as usize;
5013    let cbytes = buf.get(*pos..pos.checked_add(clen)?)?;
5014    *pos += clen;
5015    let code = std::str::from_utf8(cbytes).ok()?;
5016    let currency =
5017        logicaffeine_base::money::currency::by_code(code).unwrap_or(logicaffeine_base::Currency {
5018            code: "XXX",
5019            scale: 0,
5020        });
5021    Some(RuntimeValue::Money(Rc::new(logicaffeine_base::Money {
5022        amount,
5023        currency,
5024    })))
5025}
5026
5027/// A UUID is a fixed 16 big-endian bytes — no length prefix needed.
5028fn encode_uuid(u: &logicaffeine_base::Uuid, out: &mut Vec<u8>) {
5029    out.push(T_UUID);
5030    out.extend_from_slice(u.as_bytes());
5031}
5032
5033/// Decode a `Uuid` from the wire (inverse of [`encode_uuid`]): the next 16 bytes verbatim.
5034fn decode_uuid(buf: &[u8], pos: &mut usize) -> Option<RuntimeValue> {
5035    let bytes = buf.get(*pos..pos.checked_add(16)?)?;
5036    *pos += 16;
5037    let mut arr = [0u8; 16];
5038    arr.copy_from_slice(bytes);
5039    Some(RuntimeValue::Uuid(Rc::new(
5040        logicaffeine_base::Uuid::from_bytes(arr),
5041    )))
5042}
5043
5044/// Write a value as tagged varint bytes. `Err` for a non-portable value (with the
5045/// same messages the JSON path produces), caught at the exact offending node.
5046/// Why the wire refuses a value holding a real number a function of the program defines.
5047fn never_sent() -> String {
5048    format!(
5049        "{} stays with its program and is never sent",
5050        logicaffeine_base::exact_real::STAYS_WITH_ITS_PROGRAM
5051    )
5052}
5053
5054fn native_encode(v: &RuntimeValue, out: &mut Vec<u8>) -> Result<(), String> {
5055    let _depth = DepthGuard::enter()?;
5056    // Rc-dedup (G8): a shared subtree's repeat occurrences ship as a tiny backref; its first writes a
5057    // `T_SHARED_DEF` header here and then falls through to encode the value normally. No-op when the
5058    // dedup knob is off or the value isn't shared, so the default wire is byte-unchanged.
5059    if dedup_encode_prefix(v, out) {
5060        return Ok(());
5061    }
5062    match v {
5063        RuntimeValue::Nothing => out.push(T_NOTHING),
5064        RuntimeValue::Bool(false) => out.push(T_FALSE),
5065        RuntimeValue::Bool(true) => out.push(T_TRUE),
5066        RuntimeValue::Int(n) => {
5067            out.push(T_INT);
5068            write_uvarint(zigzag(*n), out);
5069        }
5070        RuntimeValue::Word(w) => {
5071            out.push(T_WORD);
5072            out.push(w.width() as u8);
5073            write_uvarint(w.to_u64(), out);
5074        }
5075        RuntimeValue::Lanes(_) => {
5076            return Err(
5077                "a SIMD lane vector is a transient compute value, not a wire type".to_string(),
5078            );
5079        }
5080        RuntimeValue::ComplexF(c) => {
5081            out.push(T_COMPLEXF);
5082            put_f64(out, c.re());
5083            put_f64(out, c.im());
5084        }
5085        // An out-of-i64 integer: ship sign + length + little-endian magnitude bytes —
5086        // exact (no base conversion), the typed alternative to a lossy JSON number.
5087        RuntimeValue::BigInt(b) => {
5088            out.push(T_BIGINT);
5089            let (negative, magnitude) = b.to_le_bytes();
5090            out.push(negative as u8);
5091            write_uvarint(magnitude.len() as u64, out);
5092            out.extend_from_slice(&magnitude);
5093        }
5094        // An exact fraction: signed numerator then positive denominator, each as
5095        // sign?+length+little-endian magnitude — `1/3` survives where a JSON number rounds.
5096        RuntimeValue::Rational(r) => {
5097            out.push(T_RATIONAL);
5098            let (num_negative, num_magnitude) = r.numerator().to_le_bytes();
5099            out.push(num_negative as u8);
5100            write_uvarint(num_magnitude.len() as u64, out);
5101            out.extend_from_slice(&num_magnitude);
5102            let (_den_sign, den_magnitude) = r.denominator().to_le_bytes();
5103            write_uvarint(den_magnitude.len() as u64, out);
5104            out.extend_from_slice(&den_magnitude);
5105        }
5106        // An exact base-10 fixed-point (money): sign + coefficient magnitude + base-10
5107        // scale — `19.99` survives bit-exact, scale and all (no lossy f64 round-trip).
5108        RuntimeValue::Decimal(d) => {
5109            out.push(T_DECIMAL);
5110            let (negative, magnitude, scale) = d.to_le_bytes();
5111            out.push(negative as u8);
5112            write_uvarint(magnitude.len() as u64, out);
5113            out.extend_from_slice(&magnitude);
5114            write_uvarint(scale as u64, out);
5115        }
5116        // Money: its Decimal amount, then the ISO-4217 currency code. Body lives in an
5117        // `#[inline(never)]` helper so its locals do not enlarge this RECURSIVE encoder's stack
5118        // frame (the codec recurses per nesting level; a fat frame overflows deep-but-finite values).
5119        RuntimeValue::Money(m) => encode_money(m, out),
5120        RuntimeValue::Uuid(u) => encode_uuid(u, out),
5121        // An exact complex number: its real then imaginary part, each shipped as a rational
5122        // (sign + numerator len+LE + denominator len+LE). `i·i = −1` survives bit-exact.
5123        RuntimeValue::Complex(c) => {
5124            out.push(T_COMPLEX);
5125            for part in [c.re(), c.im()] {
5126                let (neg, num) = part.numerator().to_le_bytes();
5127                out.push(neg as u8);
5128                write_uvarint(num.len() as u64, out);
5129                out.extend_from_slice(&num);
5130                let (_den_sign, den) = part.denominator().to_le_bytes();
5131                write_uvarint(den.len() as u64, out);
5132                out.extend_from_slice(&den);
5133            }
5134        }
5135        // A ℤ/nℤ element: its non-negative residue then its modulus, each len-prefixed LE.
5136        RuntimeValue::Modular(m) => {
5137            out.push(T_MODULAR);
5138            let (_, value) = m.value().to_le_bytes();
5139            write_uvarint(value.len() as u64, out);
5140            out.extend_from_slice(&value);
5141            let (_, modulus) = m.modulus().to_le_bytes();
5142            write_uvarint(modulus.len() as u64, out);
5143            out.extend_from_slice(&modulus);
5144        }
5145        RuntimeValue::Float(f) => {
5146            out.push(T_FLOAT);
5147            put_f64(out, *f);
5148        }
5149        RuntimeValue::Char(c) => {
5150            out.push(T_CHAR);
5151            write_uvarint(*c as u64, out);
5152        }
5153        RuntimeValue::Text(s) => {
5154            out.push(T_TEXT);
5155            write_str(s, out);
5156        }
5157        RuntimeValue::Duration(n) => {
5158            out.push(T_DURATION);
5159            write_uvarint(zigzag(*n), out);
5160        }
5161        RuntimeValue::Date(n) => {
5162            out.push(T_DATE);
5163            write_uvarint(zigzag(*n as i64), out);
5164        }
5165        RuntimeValue::Moment(n) => {
5166            out.push(T_MOMENT);
5167            write_uvarint(zigzag(*n), out);
5168        }
5169        RuntimeValue::Span { months, days } => {
5170            out.push(T_SPAN);
5171            write_uvarint(zigzag(*months as i64), out);
5172            write_uvarint(zigzag(*days as i64), out);
5173        }
5174        RuntimeValue::Time(n) => {
5175            out.push(T_TIME);
5176            write_uvarint(zigzag(*n), out);
5177        }
5178        RuntimeValue::Peer(topic) => {
5179            out.push(T_PEER);
5180            write_str(topic, out);
5181        }
5182        RuntimeValue::List(items) => encode_list_repr(&items.borrow(), out)?,
5183        RuntimeValue::Tuple(items) => {
5184            out.push(T_TUPLE);
5185            write_uvarint(items.len() as u64, out);
5186            for x in items.iter() {
5187                native_encode(x, out)?;
5188            }
5189        }
5190        RuntimeValue::Set(items) => {
5191            let b = items.borrow();
5192            if !b.is_empty() && b.iter().all(|x| matches!(x, RuntimeValue::Int(_))) {
5193                // EXTREME SYMMETRY BREAKING: a homogeneous int set, sorted to its canonical
5194                // representative, is a strictly-MONOTONE column — the ideal input to the G5 int menu.
5195                // A consecutive set {1..n} collapses to T_INTS_AFFINE (base+stride+count, NO data); a
5196                // clustered set to delta+RLE. Same members in any order → byte-identical wire.
5197                let mut ints: Vec<i64> = b
5198                    .iter()
5199                    .map(|x| {
5200                        if let RuntimeValue::Int(n) = x {
5201                            *n
5202                        } else {
5203                            unreachable!()
5204                        }
5205                    })
5206                    .collect();
5207                ints.sort_unstable();
5208                ints.dedup();
5209                out.push(T_SET_INTS);
5210                emit_best_int_column(&ints, out);
5211            } else if !b.is_empty() && b.iter().all(|x| matches!(x, RuntimeValue::Text(_))) {
5212                // EXTREME SYMMETRY BREAKING (strings): sort to canonical order, then FRONT-CODE each
5213                // member as (shared-prefix-length-with-the-previous, suffix). Sorted similar strings
5214                // share long prefixes — "apple"/"apply"/"apricot" or "user_1".."user_999" or URL paths
5215                // — so only the deltas go on the wire. Same set in any order → byte-identical.
5216                let mut strs: Vec<String> = b
5217                    .iter()
5218                    .map(|x| {
5219                        if let RuntimeValue::Text(t) = x {
5220                            (**t).clone()
5221                        } else {
5222                            unreachable!()
5223                        }
5224                    })
5225                    .collect();
5226                strs.sort_unstable();
5227                strs.dedup();
5228                out.push(T_SET_STRINGS);
5229                write_uvarint(strs.len() as u64, out);
5230                let mut prev: &str = "";
5231                for s in &strs {
5232                    // common char-prefix length in BYTES (char-boundary-safe by construction).
5233                    let common: usize = s
5234                        .chars()
5235                        .zip(prev.chars())
5236                        .take_while(|(a, b)| a == b)
5237                        .map(|(a, _)| a.len_utf8())
5238                        .sum();
5239                    write_uvarint(common as u64, out);
5240                    write_str(&s[common..], out);
5241                    prev = s;
5242                }
5243            } else {
5244                // SYMMETRY BREAKING ON THE WIRE (general case). A Set is ORDER-INVARIANT — the decoder
5245                // rebuilds a Set, so element order carries NO information. Encode each element, then
5246                // sort by encoded bytes to ship the CANONICAL representative. Same set in any insertion
5247                // order → BYTE-IDENTICAL wire (content-addressing / dedup / cached keying / FEC hold).
5248                // Mirrors the canonical `T_MAP` (sort-by-encoded-key) / `T_STRUCT` (sort-by-field-name).
5249                out.push(T_SET);
5250                let mut encoded: Vec<Vec<u8>> = Vec::with_capacity(b.len());
5251                for x in b.iter() {
5252                    let mut xb = Vec::new();
5253                    native_encode(x, &mut xb)?;
5254                    encoded.push(xb);
5255                }
5256                encoded.sort_unstable();
5257                write_uvarint(encoded.len() as u64, out);
5258                for xb in &encoded {
5259                    out.extend_from_slice(xb);
5260                }
5261            }
5262        }
5263        RuntimeValue::Map(m) => {
5264            let b = m.borrow();
5265            if !b.is_empty() && b.keys().all(|k| matches!(k, RuntimeValue::Int(_))) {
5266                // SYMMETRY BREAKING (int-keyed map → struct-of-arrays). Sort entries by NUMERIC key
5267                // (canonical — insertion order carries nothing), then ship TWO columns: the keys as a
5268                // best int column (G5 menu: a {0..n} key range collapses to affine base+stride+count,
5269                // NO per-key data), and the values as a best-encoded LIST (composing the entire column
5270                // menu — int/float/string/struct columns). An affine int→int map crushes BOTH sides.
5271                let mut pairs: Vec<(i64, RuntimeValue)> = b
5272                    .iter()
5273                    .map(|(k, v)| match k {
5274                        RuntimeValue::Int(n) => (*n, v.clone()),
5275                        _ => unreachable!("all keys verified Int"),
5276                    })
5277                    .collect();
5278                pairs.sort_by_key(|(k, _)| *k);
5279                let keys: Vec<i64> = pairs.iter().map(|(k, _)| *k).collect();
5280                let vals: Vec<RuntimeValue> = pairs.into_iter().map(|(_, v)| v).collect();
5281                out.push(T_MAP_INTKEY);
5282                emit_best_int_column(&keys, out);
5283                // The value column crushes UNCONDITIONALLY (like the keys + the int-set), independent
5284                // of the structure dial: a 1-byte value-kind selects an int column (the int→int crush)
5285                // or the general per-value fallback. (kind 2+ — string/struct value columns — is a
5286                // future additive refinement, never a re-encoding of existing kinds.)
5287                if vals.iter().all(|v| matches!(v, RuntimeValue::Int(_))) {
5288                    out.push(1u8);
5289                    let int_vals: Vec<i64> = vals
5290                        .iter()
5291                        .map(|v| match v {
5292                            RuntimeValue::Int(n) => *n,
5293                            _ => unreachable!("all values verified Int"),
5294                        })
5295                        .collect();
5296                    emit_best_int_column(&int_vals, out);
5297                } else if vals.iter().all(|v| matches!(v, RuntimeValue::Text(_))) {
5298                    // String value column, FRONT-CODED in key order: each value ships as the shared-
5299                    // prefix length with the PREVIOUS value + the suffix. Sequential keys → sequential
5300                    // values (URLs / paths / ids — the database-table case) share long prefixes, so only
5301                    // the deltas go on the wire. Never worse than kind 0 (an unshared value just ships
5302                    // common=0 + the string). Char-boundary-safe by construction.
5303                    out.push(2u8);
5304                    write_uvarint(vals.len() as u64, out);
5305                    let mut prev: &str = "";
5306                    for v in &vals {
5307                        let s = match v {
5308                            RuntimeValue::Text(t) => t.as_str(),
5309                            _ => unreachable!("all values verified Text"),
5310                        };
5311                        let common: usize = s
5312                            .chars()
5313                            .zip(prev.chars())
5314                            .take_while(|(a, b)| a == b)
5315                            .map(|(a, _)| a.len_utf8())
5316                            .sum();
5317                        write_uvarint(common as u64, out);
5318                        write_str(&s[common..], out);
5319                        prev = s;
5320                    }
5321                } else if vals.iter().all(|v| matches!(v, RuntimeValue::Struct(_))) {
5322                    // Struct value column → the database-table crush. A homogeneous struct list packs
5323                    // COLUMNAR (the schema / field names ship ONCE, then one best-encoded column per
5324                    // field — the id column compresses, a bool column bit-packs) via the existing
5325                    // struct-list encoder; heterogeneous structs degrade to a tagged list. Either way it
5326                    // decodes back to the same value sequence.
5327                    out.push(3u8);
5328                    let vlist = RuntimeValue::List(Rc::new(RefCell::new(ListRepr::from_values(
5329                        vals.clone(),
5330                    ))));
5331                    native_encode(&vlist, out)?;
5332                } else {
5333                    out.push(0u8);
5334                    write_uvarint(vals.len() as u64, out);
5335                    for v in &vals {
5336                        native_encode(v, out)?;
5337                    }
5338                }
5339            } else {
5340                out.push(T_MAP);
5341                // Canonical (general): encode each entry, then sort by encoded key.
5342                let mut entries: Vec<(Vec<u8>, Vec<u8>)> = Vec::with_capacity(b.len());
5343                for (k, val) in b.iter() {
5344                    let mut kb = Vec::new();
5345                    native_encode(k, &mut kb)?;
5346                    let mut vb = Vec::new();
5347                    native_encode(val, &mut vb)?;
5348                    entries.push((kb, vb));
5349                }
5350                entries.sort_by(|a, b| a.0.cmp(&b.0));
5351                write_uvarint(entries.len() as u64, out);
5352                for (kb, vb) in entries {
5353                    out.extend_from_slice(&kb);
5354                    out.extend_from_slice(&vb);
5355                }
5356            }
5357        }
5358        RuntimeValue::Struct(s) => {
5359            // Canonical field order (by name) is both the schema identity and the order
5360            // values are written in. With no cache this stays the self-describing
5361            // `T_STRUCT` (field names inline — byte-identical to before). With a cache
5362            // the schema is sent once and later structs of the same shape ship values
5363            // only (no field-name strings) — the cross-message win for lone structs.
5364            let mut fields: Vec<(&String, &RuntimeValue)> = s.fields.iter().collect();
5365            fields.sort_by(|a, b| a.0.cmp(b.0));
5366            let field_names: Vec<String> = fields.iter().map(|(n, _)| (*n).clone()).collect();
5367            if struct_view_on() {
5368                // Offset-table view: a per-field byte-offset table precedes the values so a
5369                // `WireView` jumps to ANY field in O(1) (Cap'n Proto-class random access),
5370                // never parsing the others. Offsets are backpatched after the values land.
5371                out.push(T_STRUCT_VIEW);
5372                write_str(&s.type_name, out);
5373                write_uvarint(fields.len() as u64, out);
5374                for (name, _) in &fields {
5375                    write_str(name, out);
5376                }
5377                let table_pos = out.len();
5378                out.resize(table_pos + fields.len() * 4, 0);
5379                let values_start = out.len();
5380                let mut offsets: Vec<u32> = Vec::with_capacity(fields.len());
5381                for (_, val) in &fields {
5382                    offsets.push((out.len() - values_start) as u32);
5383                    native_encode(val, out)?;
5384                }
5385                for (i, off) in offsets.iter().enumerate() {
5386                    out[table_pos + i * 4..table_pos + i * 4 + 4]
5387                        .copy_from_slice(&off.to_le_bytes());
5388                }
5389            } else if let Some(id) = type_registry_id(&s.type_name, &field_names) {
5390                // Both ends share this type's definition (the program-derived registry):
5391                // ship its small id and the values only — the type/field NAMES never go on
5392                // the wire. The default-on win that beats raw varint on Logos↔Logos.
5393                out.push(T_STRUCT_TID);
5394                write_uvarint(id as u64, out);
5395                for (_, val) in &fields {
5396                    native_encode(val, out)?;
5397                }
5398            } else {
5399                match schema_send(&s.type_name, &field_names) {
5400                    SchemaEmit::Inline => {
5401                        out.push(T_STRUCT);
5402                        write_str(&s.type_name, out);
5403                        write_uvarint(fields.len() as u64, out);
5404                        for (name, val) in &fields {
5405                            write_str(name, out);
5406                            native_encode(val, out)?;
5407                        }
5408                    }
5409                    SchemaEmit::SeqDef(id) => {
5410                        out.push(T_STRUCT_DEF);
5411                        write_uvarint(id as u64, out);
5412                        write_struct_schema(&s.type_name, &field_names, out);
5413                        for (_, val) in &fields {
5414                            native_encode(val, out)?;
5415                        }
5416                    }
5417                    SchemaEmit::SeqRef(id) => {
5418                        out.push(T_STRUCT_REF);
5419                        write_uvarint(id as u64, out);
5420                        for (_, val) in &fields {
5421                            native_encode(val, out)?;
5422                        }
5423                    }
5424                    SchemaEmit::CaDef => {
5425                        out.push(T_STRUCT_CDEF);
5426                        write_struct_schema(&s.type_name, &field_names, out);
5427                        for (_, val) in &fields {
5428                            native_encode(val, out)?;
5429                        }
5430                    }
5431                    SchemaEmit::CaRef(fp) => {
5432                        out.push(T_STRUCT_CREF);
5433                        out.extend_from_slice(&fp.to_le_bytes());
5434                        for (_, val) in &fields {
5435                            native_encode(val, out)?;
5436                        }
5437                    }
5438                }
5439            }
5440        }
5441        RuntimeValue::Inductive(ind) => {
5442            if let Some((enum_id, ctor_idx)) =
5443                type_registry_enum_id(&ind.inductive_type, &ind.constructor)
5444            {
5445                // Shared registry knows this enum: ship its id + the constructor index,
5446                // names elided (the receiver's ordered constructor list resolves it).
5447                out.push(T_INDUCTIVE_TID);
5448                write_uvarint(enum_id as u64, out);
5449                write_uvarint(ctor_idx as u64, out);
5450                write_uvarint(ind.args.len() as u64, out);
5451                for a in &ind.args {
5452                    native_encode(a, out)?;
5453                }
5454            } else {
5455                out.push(T_INDUCTIVE);
5456                write_str(&ind.inductive_type, out);
5457                write_str(&ind.constructor, out);
5458                write_uvarint(ind.args.len() as u64, out);
5459                for a in &ind.args {
5460                    native_encode(a, out)?;
5461                }
5462            }
5463        }
5464        RuntimeValue::Chan(_) | RuntimeValue::TaskHandle(_) => {
5465            return Err("a channel or task handle cannot be sent over the network".to_string());
5466        }
5467        RuntimeValue::Crdt(_) => {
5468            return Err("a CRDT value is shared via Merge/Sync, not sent inline".to_string());
5469        }
5470        RuntimeValue::Algebraic(_) | RuntimeValue::ExactReal(_) | RuntimeValue::Ordinal(_) => {
5471            let Some((kind, bytes)) = crate::semantics::exact::exact_bytes(v) else {
5472                return Err(never_sent());
5473            };
5474            out.push(T_EXACT);
5475            out.push(kind);
5476            write_uvarint(bytes.len() as u64, out);
5477            out.extend_from_slice(&bytes);
5478        }
5479        RuntimeValue::Hyperreal(_) | RuntimeValue::Hypernatural(_) => {
5480            let Some((kind, name, residues, bytes)) = crate::semantics::exact::scaled_bytes(v) else {
5481                return Err(never_sent());
5482            };
5483            out.push(T_SCALED);
5484            out.push(kind);
5485            write_str(&name, out);
5486            write_uvarint(residues.len() as u64, out);
5487            out.extend_from_slice(&residues);
5488            write_uvarint(bytes.len() as u64, out);
5489            out.extend_from_slice(&bytes);
5490        }
5491        RuntimeValue::Depends(d) => {
5492            let (name, residues) = crate::semantics::exact::scale_parts(&d.scale);
5493            out.push(T_DEPENDS);
5494            write_str(&name, out);
5495            write_uvarint(residues.len() as u64, out);
5496            out.extend_from_slice(&residues);
5497            write_uvarint(d.value.modulus(), out);
5498            for branch in d.value.raw_branches() {
5499                match branch {
5500                    None => out.push(0),
5501                    Some(b) => {
5502                        out.push(1);
5503                        native_encode(&b.0, out)?;
5504                    }
5505                }
5506            }
5507        }
5508        RuntimeValue::Converging(c) => {
5509            out.push(T_CONVERGING);
5510            native_encode(c.value(), out)?;
5511            write_uvarint(c.steps(), out);
5512            write_uvarint(c.revisions(), out);
5513            match c.certificate() {
5514                None => out.push(0),
5515                Some(cert) => {
5516                    out.push(1);
5517                    write_str(cert, out);
5518                }
5519            }
5520        }
5521        // A view belongs to the page that rendered it and never crosses the wire.
5522        RuntimeValue::View(_) | RuntimeValue::Action(_) => {
5523            return Err(
5524                "a view or callback belongs to the page that made it and cannot be sent"
5525                    .to_string(),
5526            );
5527        }
5528        RuntimeValue::Function(f) => {
5529            // Only a SHIPPED pure function (lowered to a sandboxed generator) crosses the
5530            // wire — an ordinary closure (an arena AST body) still cannot, since the receiver
5531            // never compiled it. A `generated` function ships its arity + the generator tree.
5532            match &f.generated {
5533                Some(expr) => {
5534                    out.push(T_FUNC);
5535                    write_uvarint(f.param_names.len() as u64, out);
5536                    serialize_gen(expr, out);
5537                }
5538                None => return Err("a Function cannot be sent over the network".to_string()),
5539            }
5540        }
5541        // A dimensioned quantity: its SI magnitude (rational), its dimension (10 exponent pairs as
5542        // zigzag varints), then the display unit symbol (len + UTF-8) — reconstructed exactly.
5543        RuntimeValue::Quantity(qv) => {
5544            out.push(T_QUANTITY);
5545            let (num_negative, num_magnitude) = qv.q.magnitude_si().numerator().to_le_bytes();
5546            out.push(num_negative as u8);
5547            write_uvarint(num_magnitude.len() as u64, out);
5548            out.extend_from_slice(&num_magnitude);
5549            let (_den_sign, den_magnitude) = qv.q.magnitude_si().denominator().to_le_bytes();
5550            write_uvarint(den_magnitude.len() as u64, out);
5551            out.extend_from_slice(&den_magnitude);
5552            let dim = qv.q.dimension();
5553            for d in logicaffeine_base::BaseDim::ALL {
5554                let e = dim.exponent(d);
5555                write_uvarint(zigzag(e.numerator() as i64), out);
5556                write_uvarint(zigzag(e.denominator() as i64), out);
5557            }
5558            let sym = qv.unit.symbol.as_bytes();
5559            write_uvarint(sym.len() as u64, out);
5560            out.extend_from_slice(sym);
5561        }
5562    }
5563    Ok(())
5564}
5565
5566/// A cap on a length prefix's pre-allocation, so a corrupt huge count can't ask
5567/// for gigabytes up front; the actual reads still bound-check every element.
5568const PREALLOC_CAP: usize = 4096;
5569
5570/// Reject a decoded element count that exceeds the receiver's `max_elements` budget BEFORE any
5571/// `count`-sized materialization. This is the gate the byte budget cannot provide: a generator column
5572/// (`T_INTS_AFFINE` / `T_INTS_POLY` / `T_GEN`) is a ~10-byte descriptor that expands to `count`
5573/// elements, so a crafted `count = 10^9` is a few bytes on the wire but gigabytes in memory — the
5574/// small-message-huge-output DoS. Returns the count as `usize` when within budget, else `None`.
5575fn bounded_count(n: u64) -> Option<usize> {
5576    let n = n as usize;
5577    (n <= receive_limits().max_elements).then_some(n)
5578}
5579
5580fn native_decode(buf: &[u8], pos: &mut usize) -> Option<RuntimeValue> {
5581    // Bound recursion depth FIRST — a crafted deeply-nested message is refused here (clean `None`),
5582    // never recursed into a stack overflow. Unwinds via `Drop` as the `?`s propagate back up.
5583    let _depth = DecodeDepthGuard::enter()?;
5584    let tag = *buf.get(*pos)?;
5585    *pos += 1;
5586    // Rc-dedup (G8): a backref resolves to the EXACT `Rc` first decoded under that id (sharing
5587    // preserved); a def decodes the value once and registers it. A dangling ref → clean `None`.
5588    if tag == T_SHARED_REF {
5589        let id = read_uvarint(buf, pos)?;
5590        return DECODE_MEMO.with(|c| c.borrow().get(&id).cloned());
5591    }
5592    if tag == T_SHARED_DEF {
5593        let id = read_uvarint(buf, pos)?;
5594        let v = native_decode(buf, pos)?;
5595        DECODE_MEMO.with(|c| c.borrow_mut().insert(id, v.clone()));
5596        return Some(v);
5597    }
5598    Some(match tag {
5599        T_NOTHING => RuntimeValue::Nothing,
5600        T_FALSE => RuntimeValue::Bool(false),
5601        T_TRUE => RuntimeValue::Bool(true),
5602        T_INT => RuntimeValue::Int(unzigzag(read_uvarint(buf, pos)?)),
5603        T_WORD => {
5604            let width = *buf.get(*pos)? as u32;
5605            *pos += 1;
5606            let bits = read_uvarint(buf, pos)?;
5607            RuntimeValue::Word(logicaffeine_base::WordVal::from_u64(width, bits)?)
5608        }
5609        T_BIGINT => {
5610            let negative = *buf.get(*pos)? != 0;
5611            *pos += 1;
5612            let len = read_uvarint(buf, pos)? as usize;
5613            let bytes = buf.get(*pos..pos.checked_add(len)?)?;
5614            *pos += len;
5615            RuntimeValue::from_bigint(logicaffeine_base::BigInt::from_le_bytes(negative, bytes))
5616        }
5617        T_RATIONAL => {
5618            let num_negative = *buf.get(*pos)? != 0;
5619            *pos += 1;
5620            let num_len = read_uvarint(buf, pos)? as usize;
5621            let num_bytes = buf.get(*pos..pos.checked_add(num_len)?)?;
5622            *pos += num_len;
5623            let num = logicaffeine_base::BigInt::from_le_bytes(num_negative, num_bytes);
5624            let den_len = read_uvarint(buf, pos)? as usize;
5625            let den_bytes = buf.get(*pos..pos.checked_add(den_len)?)?;
5626            *pos += den_len;
5627            let den = logicaffeine_base::BigInt::from_le_bytes(false, den_bytes);
5628            // A zero/garbage denominator is rejected (None) rather than panicking.
5629            RuntimeValue::from_rational(logicaffeine_base::Rational::new(num, den)?)
5630        }
5631        T_DECIMAL => {
5632            let negative = *buf.get(*pos)? != 0;
5633            *pos += 1;
5634            let len = read_uvarint(buf, pos)? as usize;
5635            let bytes = buf.get(*pos..pos.checked_add(len)?)?;
5636            *pos += len;
5637            let scale = u32::try_from(read_uvarint(buf, pos)?).ok()?;
5638            // Decimal does NOT downsize on a whole value (the scale is meaning), so build
5639            // the variant directly rather than through a downsizing chokepoint.
5640            RuntimeValue::Decimal(Rc::new(logicaffeine_base::Decimal::from_le_bytes(
5641                negative, bytes, scale,
5642            )))
5643        }
5644        // Body in an `#[inline(never)]` helper to keep this recursive decoder's frame small.
5645        T_MONEY => decode_money(buf, pos)?,
5646        T_UUID => decode_uuid(buf, pos)?,
5647        T_EXACT => {
5648            let kind = *buf.get(*pos)?;
5649            *pos += 1;
5650            let n = read_uvarint(buf, pos)? as usize;
5651            let bytes = buf.get(*pos..pos.checked_add(n)?)?;
5652            *pos += n;
5653            crate::semantics::exact::from_exact_bytes(kind, bytes)?
5654        }
5655        T_SCALED => {
5656            let kind = *buf.get(*pos)?;
5657            *pos += 1;
5658            let name = read_str(buf, pos)?;
5659            let rn = read_uvarint(buf, pos)? as usize;
5660            let residues = buf.get(*pos..pos.checked_add(rn)?)?.to_vec();
5661            *pos += rn;
5662            let n = read_uvarint(buf, pos)? as usize;
5663            let bytes = buf.get(*pos..pos.checked_add(n)?)?;
5664            *pos += n;
5665            crate::semantics::exact::from_scaled_bytes(kind, &name, &residues, bytes)?
5666        }
5667        T_DEPENDS => {
5668            let name = read_str(buf, pos)?;
5669            let rn = read_uvarint(buf, pos)? as usize;
5670            let residues = buf.get(*pos..pos.checked_add(rn)?)?.to_vec();
5671            *pos += rn;
5672            let modulus = read_uvarint(buf, pos)?;
5673            if modulus == 0 || modulus > logicaffeine_base::exact_codec::MAX_WIRE_LEN as u64 {
5674                return None;
5675            }
5676            let mut branches = Vec::with_capacity(modulus as usize);
5677            for _ in 0..modulus {
5678                branches.push(match *buf.get(*pos)? {
5679                    0 => {
5680                        *pos += 1;
5681                        None
5682                    }
5683                    1 => {
5684                        *pos += 1;
5685                        Some(native_decode(buf, pos)?)
5686                    }
5687                    _ => return None,
5688                });
5689            }
5690            crate::semantics::exact::depends_from_parts(&name, &residues, modulus, branches)?
5691        }
5692        T_CONVERGING => {
5693            let value = native_decode(buf, pos)?;
5694            if matches!(value, RuntimeValue::Converging(_)) {
5695                return None;
5696            }
5697            let steps = read_uvarint(buf, pos)?;
5698            let revisions = read_uvarint(buf, pos)?;
5699            let converging = match *buf.get(*pos)? {
5700                0 => {
5701                    *pos += 1;
5702                    logicaffeine_base::limit_value::Converging::provisional(value, steps, revisions)
5703                }
5704                1 => {
5705                    *pos += 1;
5706                    if steps != 0 || revisions != 0 {
5707                        return None;
5708                    }
5709                    let cert = read_str(buf, pos)?;
5710                    logicaffeine_base::limit_value::Converging::settled(value, &cert)
5711                }
5712                _ => return None,
5713            };
5714            RuntimeValue::Converging(Rc::new(converging))
5715        }
5716        T_COMPLEX => {
5717            // Read the real part (a rational), then the imaginary part (a rational).
5718            let re_neg = *buf.get(*pos)? != 0;
5719            *pos += 1;
5720            let re_nlen = read_uvarint(buf, pos)? as usize;
5721            let re_nb = buf.get(*pos..pos.checked_add(re_nlen)?)?;
5722            *pos += re_nlen;
5723            let re_num = logicaffeine_base::BigInt::from_le_bytes(re_neg, re_nb);
5724            let re_dlen = read_uvarint(buf, pos)? as usize;
5725            let re_db = buf.get(*pos..pos.checked_add(re_dlen)?)?;
5726            *pos += re_dlen;
5727            let re = logicaffeine_base::Rational::new(
5728                re_num,
5729                logicaffeine_base::BigInt::from_le_bytes(false, re_db),
5730            )?;
5731            let im_neg = *buf.get(*pos)? != 0;
5732            *pos += 1;
5733            let im_nlen = read_uvarint(buf, pos)? as usize;
5734            let im_nb = buf.get(*pos..pos.checked_add(im_nlen)?)?;
5735            *pos += im_nlen;
5736            let im_num = logicaffeine_base::BigInt::from_le_bytes(im_neg, im_nb);
5737            let im_dlen = read_uvarint(buf, pos)? as usize;
5738            let im_db = buf.get(*pos..pos.checked_add(im_dlen)?)?;
5739            *pos += im_dlen;
5740            let im = logicaffeine_base::Rational::new(
5741                im_num,
5742                logicaffeine_base::BigInt::from_le_bytes(false, im_db),
5743            )?;
5744            RuntimeValue::Complex(Rc::new(logicaffeine_base::Complex::new(re, im)))
5745        }
5746        T_MODULAR => {
5747            let vlen = read_uvarint(buf, pos)? as usize;
5748            let vb = buf.get(*pos..pos.checked_add(vlen)?)?;
5749            *pos += vlen;
5750            let v = logicaffeine_base::BigInt::from_le_bytes(false, vb);
5751            let nlen = read_uvarint(buf, pos)? as usize;
5752            let nb = buf.get(*pos..pos.checked_add(nlen)?)?;
5753            *pos += nlen;
5754            let n = logicaffeine_base::BigInt::from_le_bytes(false, nb);
5755            RuntimeValue::Modular(Rc::new(logicaffeine_base::Modular::new(v, n)?))
5756        }
5757        T_QUANTITY => {
5758            // SI magnitude (rational): sign + numerator (len+LE) + denominator (len+LE).
5759            let num_neg = *buf.get(*pos)? != 0;
5760            *pos += 1;
5761            let nlen = read_uvarint(buf, pos)? as usize;
5762            let nb = buf.get(*pos..pos.checked_add(nlen)?)?;
5763            *pos += nlen;
5764            let num = logicaffeine_base::BigInt::from_le_bytes(num_neg, nb);
5765            let dlen = read_uvarint(buf, pos)? as usize;
5766            let db = buf.get(*pos..pos.checked_add(dlen)?)?;
5767            *pos += dlen;
5768            let magnitude = logicaffeine_base::Rational::new(
5769                num,
5770                logicaffeine_base::BigInt::from_le_bytes(false, db),
5771            )?;
5772            // Dimension: 10 exponent (numerator, denominator) pairs as zig-zag varints.
5773            let mut exps = [logicaffeine_base::Exp::ZERO; logicaffeine_base::BaseDim::COUNT];
5774            for slot in exps.iter_mut() {
5775                let en = unzigzag(read_uvarint(buf, pos)?) as i32;
5776                let ed = unzigzag(read_uvarint(buf, pos)?) as i32;
5777                *slot = logicaffeine_base::Exp::new(en, if ed == 0 { 1 } else { ed });
5778            }
5779            let dim = logicaffeine_base::Dimension::from_exps(exps);
5780            // Display unit symbol — resolve by name, else fall back to the SI/dimension display.
5781            let sym = read_str(buf, pos)?;
5782            let unit = logicaffeine_base::quantity::units::by_name(&sym)
5783                .filter(|u| u.dimension == dim)
5784                .unwrap_or_else(|| {
5785                    logicaffeine_base::Unit::linear("", dim, logicaffeine_base::Rational::one())
5786                });
5787            RuntimeValue::Quantity(Rc::new(crate::interpreter::QuantityValue {
5788                q: logicaffeine_base::Quantity::si(magnitude, dim),
5789                unit,
5790            }))
5791        }
5792        T_FLOAT => {
5793            let b: [u8; 8] = buf.get(*pos..pos.checked_add(8)?)?.try_into().ok()?;
5794            *pos += 8;
5795            RuntimeValue::Float(f64::from_le_bytes(b))
5796        }
5797        T_COMPLEXF => {
5798            let re: [u8; 8] = buf.get(*pos..pos.checked_add(8)?)?.try_into().ok()?;
5799            *pos += 8;
5800            let im: [u8; 8] = buf.get(*pos..pos.checked_add(8)?)?.try_into().ok()?;
5801            *pos += 8;
5802            RuntimeValue::ComplexF(Rc::new(logicaffeine_base::ComplexF::new(
5803                f64::from_le_bytes(re),
5804                f64::from_le_bytes(im),
5805            )))
5806        }
5807        T_CHAR => RuntimeValue::Char(char::from_u32(
5808            u32::try_from(read_uvarint(buf, pos)?).ok()?,
5809        )?),
5810        T_TEXT => RuntimeValue::Text(Rc::new(read_str(buf, pos)?)),
5811        T_DURATION => RuntimeValue::Duration(unzigzag(read_uvarint(buf, pos)?)),
5812        T_DATE => RuntimeValue::Date(i32::try_from(unzigzag(read_uvarint(buf, pos)?)).ok()?),
5813        T_MOMENT => RuntimeValue::Moment(unzigzag(read_uvarint(buf, pos)?)),
5814        T_SPAN => RuntimeValue::Span {
5815            months: i32::try_from(unzigzag(read_uvarint(buf, pos)?)).ok()?,
5816            days: i32::try_from(unzigzag(read_uvarint(buf, pos)?)).ok()?,
5817        },
5818        T_TIME => RuntimeValue::Time(unzigzag(read_uvarint(buf, pos)?)),
5819        T_PEER => RuntimeValue::Peer(Rc::new(read_str(buf, pos)?)),
5820        // A mixed list rebuilds as `Boxed` directly — never re-specialized, so a
5821        // round-trip is byte-stable (only genuinely-homogeneous lists are packed).
5822        T_LIST => RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Boxed(read_seq(buf, pos)?)))),
5823        T_INTS
5824        | T_INTS_AFFINE
5825        | T_INTS_GEOMETRIC
5826        | T_INTS_PERIODIC
5827        | T_INTS_SPARSE
5828        | T_INTS_POLY
5829        | T_GEN
5830        | T_BYTES
5831        | T_INTS_DELTA
5832        | T_INTS_DOD
5833        | T_INTS_FOR
5834        | T_INTS_RLE
5835        | T_INTS_DICT
5836        | describe::T_INTS_LRECUR
5837        | describe::T_INTS_LFSR
5838        | describe::T_INTS_FCSR => {
5839            // The Auto column menu decodes through the shared engine (single source of the format);
5840            // the receiver's element budget is threaded in so the DoS gate is unchanged.
5841            let v =
5842                describe::decode_int_column_body(tag, buf, pos, receive_limits().max_elements, 0)?;
5843            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Ints(v))))
5844        }
5845        T_INTS_GV => RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Ints(gv_decode_dispatch(
5846            buf, pos,
5847        )?)))),
5848        // A shipped callable function: read the arity + the bounded generator tree, rebuild a
5849        // self-contained closure the receiver can invoke (the body is the sandboxed generator).
5850        T_FUNC => {
5851            if !receive_limits().accept_computed {
5852                return None;
5853            }
5854            let arity = read_uvarint(buf, pos)? as usize;
5855            if arity > 16 {
5856                return None;
5857            }
5858            let mut budget = MAX_GEN_NODES;
5859            let expr = deserialize_gen(buf, pos, &mut budget, 0)?;
5860            let param_names: Vec<logicaffeine_base::Symbol> = (0..arity)
5861                .map(logicaffeine_base::Symbol::from_index)
5862                .collect();
5863            RuntimeValue::Function(Box::new(ClosureValue {
5864                body_index: usize::MAX,
5865                captured_env: std::collections::HashMap::default(),
5866                param_names,
5867                generated: Some(Rc::new(expr)),
5868            }))
5869        }
5870        T_INTS_FIXED => {
5871            let n = read_uvarint(buf, pos)? as usize;
5872            let nbytes = n.checked_mul(8)?;
5873            let raw = buf.get(*pos..pos.checked_add(nbytes)?)?;
5874            *pos += nbytes;
5875            // Direct memory transfer: copy the little-endian bytes straight into a
5876            // fresh `Vec<i64>` (one `memcpy`), then take ownership.
5877            #[cfg(target_endian = "little")]
5878            let v: Vec<i64> = {
5879                let mut v = Vec::<i64>::with_capacity(n);
5880                // SAFETY: `raw` is exactly `n * 8` bytes; copy into the capacity of
5881                // a properly-aligned `Vec<i64>`, then set its length.
5882                unsafe {
5883                    std::ptr::copy_nonoverlapping(
5884                        raw.as_ptr(),
5885                        v.as_mut_ptr().cast::<u8>(),
5886                        nbytes,
5887                    );
5888                    v.set_len(n);
5889                }
5890                v
5891            };
5892            #[cfg(target_endian = "big")]
5893            let v: Vec<i64> = raw
5894                .chunks_exact(8)
5895                .map(|c| i64::from_le_bytes(c.try_into().unwrap()))
5896                .collect();
5897            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Ints(v))))
5898        }
5899        // 8-byte-aligned i64 blob: skip the pad, then the same memcpy as T_INTS_FIXED.
5900        T_INTS_ALIGNED => {
5901            let n = read_uvarint(buf, pos)? as usize;
5902            let pad = *buf.get(*pos)? as usize;
5903            *pos += 1 + pad;
5904            let nbytes = n.checked_mul(8)?;
5905            let raw = buf.get(*pos..pos.checked_add(nbytes)?)?;
5906            *pos += nbytes;
5907            #[cfg(target_endian = "little")]
5908            let v: Vec<i64> = {
5909                let mut v = Vec::<i64>::with_capacity(n);
5910                // SAFETY: `raw` is exactly `n * 8` bytes; copy into a properly-aligned
5911                // `Vec<i64>`'s capacity, then set its length.
5912                unsafe {
5913                    std::ptr::copy_nonoverlapping(
5914                        raw.as_ptr(),
5915                        v.as_mut_ptr().cast::<u8>(),
5916                        nbytes,
5917                    );
5918                    v.set_len(n);
5919                }
5920                v
5921            };
5922            #[cfg(target_endian = "big")]
5923            let v: Vec<i64> = raw
5924                .chunks_exact(8)
5925                .map(|c| i64::from_le_bytes(c.try_into().unwrap()))
5926                .collect();
5927            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Ints(v))))
5928        }
5929        T_FLOATS => {
5930            let n = bounded_count(read_uvarint(buf, pos)?)?;
5931            let nbytes = n.checked_mul(8)?;
5932            let raw = buf.get(*pos..pos.checked_add(nbytes)?)?;
5933            *pos += nbytes;
5934            // Direct memory transfer: copy the little-endian bytes straight into a
5935            // fresh `Vec<f64>` (one `memcpy`), then take ownership — no per-element
5936            // decode. Bounds were just checked, so the copy reads exactly `nbytes`.
5937            #[cfg(target_endian = "little")]
5938            let v: Vec<f64> = {
5939                let mut v = Vec::<f64>::with_capacity(n);
5940                // SAFETY: `raw` has exactly `n * 8` bytes; we copy them into the
5941                // capacity of a properly-aligned `Vec<f64>`, then set its length.
5942                unsafe {
5943                    std::ptr::copy_nonoverlapping(
5944                        raw.as_ptr(),
5945                        v.as_mut_ptr().cast::<u8>(),
5946                        nbytes,
5947                    );
5948                    v.set_len(n);
5949                }
5950                v
5951            };
5952            #[cfg(target_endian = "big")]
5953            let v: Vec<f64> = raw
5954                .chunks_exact(8)
5955                .map(|c| f64::from_le_bytes(c.try_into().unwrap()))
5956                .collect();
5957            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Floats(v))))
5958        }
5959        // Constant float column: one f64 + count → fill.
5960        T_FLOATS_CONST => {
5961            let bits = u64::from_le_bytes(buf.get(*pos..pos.checked_add(8)?)?.try_into().ok()?);
5962            *pos += 8;
5963            let n = bounded_count(read_uvarint(buf, pos)?)?;
5964            let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
5965            v.resize(n, f64::from_bits(bits));
5966            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Floats(v))))
5967        }
5968        // Affine float column: reconstruct `base + i·stride` with the SAME f64 ops the encoder
5969        // verified bit-exact against, so it round-trips perfectly.
5970        T_FLOATS_AFFINE => {
5971            let base = f64::from_le_bytes(buf.get(*pos..pos.checked_add(8)?)?.try_into().ok()?);
5972            *pos += 8;
5973            let stride = f64::from_le_bytes(buf.get(*pos..pos.checked_add(8)?)?.try_into().ok()?);
5974            *pos += 8;
5975            let n = bounded_count(read_uvarint(buf, pos)?)?;
5976            let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
5977            for i in 0..n {
5978                v.push(base + (i as f64) * stride);
5979            }
5980            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Floats(v))))
5981        }
5982        // Sparse float column: fill with the dominant value, then patch the delta-indexed outliers.
5983        T_FLOATS_SPARSE => {
5984            let dom = f64::from_bits(u64::from_le_bytes(
5985                buf.get(*pos..pos.checked_add(8)?)?.try_into().ok()?,
5986            ));
5987            *pos += 8;
5988            let n = bounded_count(read_uvarint(buf, pos)?)?;
5989            let num_exc = bounded_count(read_uvarint(buf, pos)?)?;
5990            let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
5991            v.resize(n, dom);
5992            let mut idx = 0usize;
5993            for _ in 0..num_exc {
5994                idx = idx.checked_add(read_uvarint(buf, pos)? as usize)?;
5995                let bits = u64::from_le_bytes(buf.get(*pos..pos.checked_add(8)?)?.try_into().ok()?);
5996                *pos += 8;
5997                *v.get_mut(idx)? = f64::from_bits(bits);
5998            }
5999            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Floats(v))))
6000        }
6001        // Cyclic float column: read the period block, then emit `block[i % p]` for `i in 0..n`.
6002        T_FLOATS_PERIODIC => {
6003            let p = bounded_count(read_uvarint(buf, pos)?)?;
6004            if p == 0 {
6005                return None;
6006            }
6007            let n = bounded_count(read_uvarint(buf, pos)?)?;
6008            let mut block = Vec::with_capacity(p.min(PREALLOC_CAP));
6009            for _ in 0..p {
6010                let bits = u64::from_le_bytes(buf.get(*pos..pos.checked_add(8)?)?.try_into().ok()?);
6011                *pos += 8;
6012                block.push(f64::from_bits(bits));
6013            }
6014            let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
6015            for i in 0..n {
6016                v.push(block[i % p]);
6017            }
6018            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Floats(v))))
6019        }
6020        // Geometric float column: replay `base · ratio^i` with the SAME `cur *= ratio` accumulation
6021        // the encoder verified bit-exact against.
6022        T_FLOATS_GEOMETRIC => {
6023            let base = f64::from_le_bytes(buf.get(*pos..pos.checked_add(8)?)?.try_into().ok()?);
6024            *pos += 8;
6025            let ratio = f64::from_le_bytes(buf.get(*pos..pos.checked_add(8)?)?.try_into().ok()?);
6026            *pos += 8;
6027            let n = bounded_count(read_uvarint(buf, pos)?)?;
6028            let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
6029            let mut cur = base;
6030            for _ in 0..n {
6031                v.push(cur);
6032                cur *= ratio;
6033            }
6034            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Floats(v))))
6035        }
6036        // 8-byte-aligned f64 blob: skip the pad, then the same memcpy as T_FLOATS.
6037        T_FLOATS_ALIGNED => {
6038            let n = read_uvarint(buf, pos)? as usize;
6039            let pad = *buf.get(*pos)? as usize;
6040            *pos += 1 + pad;
6041            let nbytes = n.checked_mul(8)?;
6042            let raw = buf.get(*pos..pos.checked_add(nbytes)?)?;
6043            *pos += nbytes;
6044            #[cfg(target_endian = "little")]
6045            let v: Vec<f64> = {
6046                let mut v = Vec::<f64>::with_capacity(n);
6047                // SAFETY: `raw` is exactly `n * 8` bytes; copy into a properly-aligned
6048                // `Vec<f64>`'s capacity, then set its length.
6049                unsafe {
6050                    std::ptr::copy_nonoverlapping(
6051                        raw.as_ptr(),
6052                        v.as_mut_ptr().cast::<u8>(),
6053                        nbytes,
6054                    );
6055                    v.set_len(n);
6056                }
6057                v
6058            };
6059            #[cfg(target_endian = "big")]
6060            let v: Vec<f64> = raw
6061                .chunks_exact(8)
6062                .map(|c| f64::from_le_bytes(c.try_into().unwrap()))
6063                .collect();
6064            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Floats(v))))
6065        }
6066        // Lossless XOR-delta float array: undo the running XOR and rebuild each f64
6067        // from its exact bits (NaN/Inf/±0/subnormals preserved).
6068        T_FLOATS_XOR => {
6069            let n = read_uvarint(buf, pos)? as usize;
6070            let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
6071            let mut prev = 0u64;
6072            for _ in 0..n {
6073                let bits = read_uvarint(buf, pos)? ^ prev;
6074                prev = bits;
6075                v.push(f64::from_bits(bits));
6076            }
6077            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Floats(v))))
6078        }
6079        T_BOOLS => {
6080            let n = read_uvarint(buf, pos)? as usize;
6081            let nbytes = n.div_ceil(8);
6082            let bits = buf.get(*pos..pos.checked_add(nbytes)?)?;
6083            *pos += nbytes;
6084            let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
6085            for i in 0..n {
6086                v.push((bits[i / 8] >> (i % 8)) & 1 == 1);
6087            }
6088            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Bools(v))))
6089        }
6090        // Cyclic bool column: read the p-bit block, replay `block[i % p]` for `i in 0..n`.
6091        T_BOOLS_PERIODIC => {
6092            let p = bounded_count(read_uvarint(buf, pos)?)?;
6093            if p == 0 {
6094                return None;
6095            }
6096            let n = bounded_count(read_uvarint(buf, pos)?)?;
6097            let block_bytes = p.div_ceil(8);
6098            let raw = buf.get(*pos..pos.checked_add(block_bytes)?)?;
6099            *pos += block_bytes;
6100            let block: Vec<bool> = (0..p).map(|i| (raw[i / 8] >> (i % 8)) & 1 == 1).collect();
6101            let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
6102            for i in 0..n {
6103                v.push(block[i % p]);
6104            }
6105            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Bools(v))))
6106        }
6107        // Run-length bool column: alternate from the first value, emitting each run length in turn.
6108        T_BOOLS_RLE => {
6109            let n = bounded_count(read_uvarint(buf, pos)?)?;
6110            let first = *buf.get(*pos)? != 0;
6111            *pos += 1;
6112            let nruns = bounded_count(read_uvarint(buf, pos)?)?;
6113            let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
6114            let mut cur = first;
6115            for _ in 0..nruns {
6116                let runlen = bounded_count(read_uvarint(buf, pos)?)?;
6117                // The runs must reconstruct EXACTLY `n` elements — a corrupt over-run is refused.
6118                if v.len().checked_add(runlen)? > n {
6119                    return None;
6120                }
6121                v.resize(v.len() + runlen, cur);
6122                cur = !cur;
6123            }
6124            if v.len() != n {
6125                return None;
6126            }
6127            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Bools(v))))
6128        }
6129        T_STRINGS => {
6130            let n = read_uvarint(buf, pos)? as usize;
6131            let mut ends = Vec::with_capacity(n.min(PREALLOC_CAP));
6132            let mut total: u64 = 0;
6133            for _ in 0..n {
6134                total = total.checked_add(read_uvarint(buf, pos)?)?;
6135                ends.push(u32::try_from(total).ok()?);
6136            }
6137            let total = usize::try_from(total).ok()?;
6138            let raw = buf.get(*pos..pos.checked_add(total)?)?;
6139            *pos += total;
6140            // The concatenation of valid UTF-8 strings is itself valid UTF-8;
6141            // validate the whole blob once (SIMD-fast in std) so element access can
6142            // trust it, and reject corrupt data here. One bulk copy into the buffer.
6143            if std::str::from_utf8(raw).is_err() {
6144                return None;
6145            }
6146            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::strings(raw.to_vec(), ends))))
6147        }
6148        // Templated string column: rebuild `prefix + (base + i·stride) + suffix` for `i in 0..n`.
6149        T_STRINGS_TEMPLATE => {
6150            let plen = bounded_count(read_uvarint(buf, pos)?)?;
6151            let prefix = buf.get(*pos..pos.checked_add(plen)?)?.to_vec();
6152            *pos += plen;
6153            let slen = bounded_count(read_uvarint(buf, pos)?)?;
6154            let suffix = buf.get(*pos..pos.checked_add(slen)?)?.to_vec();
6155            *pos += slen;
6156            let base = unzigzag(read_uvarint(buf, pos)?);
6157            let stride = unzigzag(read_uvarint(buf, pos)?);
6158            let n = bounded_count(read_uvarint(buf, pos)?)?;
6159            // The affixes came from valid strings; a corrupt frame that isn't UTF-8 is refused.
6160            if std::str::from_utf8(&prefix).is_err() || std::str::from_utf8(&suffix).is_err() {
6161                return None;
6162            }
6163            let mut data = Vec::new();
6164            let mut ends = Vec::with_capacity(n.min(PREALLOC_CAP));
6165            for i in 0..n {
6166                let num = base.wrapping_add((i as i64).wrapping_mul(stride));
6167                data.extend_from_slice(&prefix);
6168                data.extend_from_slice(num.to_string().as_bytes());
6169                data.extend_from_slice(&suffix);
6170                ends.push(u32::try_from(data.len()).ok()?);
6171            }
6172            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::strings(data, ends))))
6173        }
6174        // Front-coded string column: rebuild each string as `prev[..common] + suffix`.
6175        T_STRINGS_FRONT => {
6176            let n = bounded_count(read_uvarint(buf, pos)?)?;
6177            let mut data: Vec<u8> = Vec::new();
6178            let mut ends = Vec::with_capacity(n.min(PREALLOC_CAP));
6179            let mut prev: Vec<u8> = Vec::new();
6180            for _ in 0..n {
6181                let common = bounded_count(read_uvarint(buf, pos)?)?;
6182                let suffix_len = bounded_count(read_uvarint(buf, pos)?)?;
6183                // Can't share more prefix than the previous string has — a corrupt frame is refused.
6184                if common > prev.len() {
6185                    return None;
6186                }
6187                let suffix = buf.get(*pos..pos.checked_add(suffix_len)?)?;
6188                *pos += suffix_len;
6189                let mut s = Vec::with_capacity(common.checked_add(suffix_len)?);
6190                s.extend_from_slice(&prev[..common]);
6191                s.extend_from_slice(suffix);
6192                data.extend_from_slice(&s);
6193                ends.push(u32::try_from(data.len()).ok()?);
6194                prev = s;
6195            }
6196            // The concatenation must be valid UTF-8 (front-coding cuts on char boundaries; a corrupt
6197            // `common`/suffix that lands mid-char is rejected here rather than producing bad strings).
6198            if std::str::from_utf8(&data).is_err() {
6199                return None;
6200            }
6201            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::strings(data, ends))))
6202        }
6203        // Affixed string column: rebuild each string as `prefix + middle_i + suffix`.
6204        T_STRINGS_AFFIX => {
6205            let plen = bounded_count(read_uvarint(buf, pos)?)?;
6206            let prefix = buf.get(*pos..pos.checked_add(plen)?)?.to_vec();
6207            *pos += plen;
6208            let slen = bounded_count(read_uvarint(buf, pos)?)?;
6209            let suffix = buf.get(*pos..pos.checked_add(slen)?)?.to_vec();
6210            *pos += slen;
6211            let n = bounded_count(read_uvarint(buf, pos)?)?;
6212            let mut data: Vec<u8> = Vec::new();
6213            let mut ends = Vec::with_capacity(n.min(PREALLOC_CAP));
6214            for _ in 0..n {
6215                let mid_len = bounded_count(read_uvarint(buf, pos)?)?;
6216                let mid = buf.get(*pos..pos.checked_add(mid_len)?)?;
6217                *pos += mid_len;
6218                data.extend_from_slice(&prefix);
6219                data.extend_from_slice(mid);
6220                data.extend_from_slice(&suffix);
6221                ends.push(u32::try_from(data.len()).ok()?);
6222            }
6223            // Affixes + middles all came from valid strings; reject a corrupt non-UTF-8 frame.
6224            if std::str::from_utf8(&data).is_err() {
6225                return None;
6226            }
6227            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::strings(data, ends))))
6228        }
6229        // Dictionary string column: the distinct strings once, then a bit-packed index per row.
6230        // Reconstruct the flat `Strings` buffer by replaying each row's dictionary entry.
6231        T_STRINGS_DICT => {
6232            let d = read_uvarint(buf, pos)? as usize;
6233            let mut dict: Vec<&[u8]> = Vec::with_capacity(d.min(PREALLOC_CAP));
6234            for _ in 0..d {
6235                let len = read_uvarint(buf, pos)? as usize;
6236                let s = buf.get(*pos..pos.checked_add(len)?)?;
6237                if std::str::from_utf8(s).is_err() {
6238                    return None;
6239                }
6240                dict.push(s);
6241                *pos += len;
6242            }
6243            let n = read_uvarint(buf, pos)? as usize;
6244            let iw = *buf.get(*pos)?;
6245            *pos += 1;
6246            if iw > 64 {
6247                return None;
6248            }
6249            let mut out_data: Vec<u8> = Vec::new();
6250            let mut out_ends: Vec<u32> = Vec::with_capacity(n.min(PREALLOC_CAP));
6251            let push = |s: &[u8], out_data: &mut Vec<u8>, out_ends: &mut Vec<u32>| {
6252                out_data.extend_from_slice(s);
6253                out_ends.push(out_data.len() as u32);
6254            };
6255            if iw == 0 {
6256                if n > 0 {
6257                    let s = *dict.first()?;
6258                    // Decompression-bomb guard (Invariant X): iw==0 encodes n identical
6259                    // strings in ZERO index bytes, so `n` is ungated by input size. Reject if
6260                    // the expansion (data bytes + 4-byte end offsets) would exceed the budget.
6261                    if n.checked_mul(s.len().max(4))? > MAX_DECODE_BYTES {
6262                        return None;
6263                    }
6264                    for _ in 0..n {
6265                        push(s, &mut out_data, &mut out_ends);
6266                    }
6267                }
6268            } else {
6269                let nbytes = n.checked_mul(iw as usize)?.div_ceil(8);
6270                let bytes = buf.get(*pos..pos.checked_add(nbytes)?)?;
6271                *pos += nbytes;
6272                for ix in bitunpack(bytes, n, iw)? {
6273                    push(*dict.get(ix as usize)?, &mut out_data, &mut out_ends);
6274                }
6275            }
6276            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::strings(out_data, out_ends))))
6277        }
6278        T_TUPLE => RuntimeValue::Tuple(Rc::new(read_seq(buf, pos)?)),
6279        T_SET => RuntimeValue::Set(Rc::new(RefCell::new(read_seq(buf, pos)?))),
6280        T_SET_INTS => {
6281            // The body is a best-encoded int column (any `T_INTS_*` form); decode it as a list —
6282            // the recovered ints ARE the set's (already canonical-sorted) members.
6283            let ints = match native_decode(buf, pos)? {
6284                RuntimeValue::List(l) => l.borrow().to_values(),
6285                _ => return None,
6286            };
6287            RuntimeValue::Set(Rc::new(RefCell::new(ints)))
6288        }
6289        T_SET_STRINGS => {
6290            // Front-coded: reconstruct each member from `common` bytes of the previous + the suffix.
6291            let n = read_uvarint(buf, pos)? as usize;
6292            // A length prefix can never exceed the bytes left — every entry needs at least
6293            // one wire byte — so cap the pre-allocation by the remaining input. A lying `n`
6294            // from a corrupt or hostile frame can no longer trigger a giant OOM allocation;
6295            // the loop below then fails cleanly when the buffer runs out. Invariant X.
6296            let mut items = Vec::with_capacity(n.min(buf.len().saturating_sub(*pos)));
6297            let mut prev = String::new();
6298            for _ in 0..n {
6299                let common = read_uvarint(buf, pos)? as usize;
6300                if common > prev.len() || !prev.is_char_boundary(common) {
6301                    return None;
6302                }
6303                let suffix = read_str(buf, pos)?;
6304                let s = format!("{}{}", &prev[..common], suffix);
6305                items.push(RuntimeValue::Text(Rc::new(s.clone())));
6306                prev = s;
6307            }
6308            RuntimeValue::Set(Rc::new(RefCell::new(items)))
6309        }
6310        T_MAP => {
6311            let n = read_uvarint(buf, pos)?;
6312            let mut m = MapStorage::default();
6313            for _ in 0..n {
6314                let k = native_decode(buf, pos)?;
6315                let v = native_decode(buf, pos)?;
6316                m.insert(k, v);
6317            }
6318            RuntimeValue::Map(Rc::new(RefCell::new(m)))
6319        }
6320        T_MAP_INTKEY => {
6321            // The int key column, a value-kind byte, then the values. Re-pair positionally — keys were
6322            // written in numeric order, values in the corresponding order.
6323            let keys = match native_decode(buf, pos)? {
6324                RuntimeValue::List(l) => l.borrow().to_values(),
6325                _ => return None,
6326            };
6327            let kind = *buf.get(*pos)?;
6328            *pos += 1;
6329            let vals: Vec<RuntimeValue> = match kind {
6330                1 => match native_decode(buf, pos)? {
6331                    RuntimeValue::List(l) => l.borrow().to_values(),
6332                    _ => return None,
6333                },
6334                2 => {
6335                    // Front-coded string column: reconstruct each value from `common` bytes of the
6336                    // previous + the suffix (mirrors T_SET_STRINGS).
6337                    let n = read_uvarint(buf, pos)? as usize;
6338                    let mut vs = Vec::with_capacity(n.min(keys.len().saturating_add(1)));
6339                    let mut prev = String::new();
6340                    for _ in 0..n {
6341                        let common = read_uvarint(buf, pos)? as usize;
6342                        if common > prev.len() || !prev.is_char_boundary(common) {
6343                            return None;
6344                        }
6345                        let suffix = read_str(buf, pos)?;
6346                        let s = format!("{}{}", &prev[..common], suffix);
6347                        vs.push(RuntimeValue::Text(Rc::new(s.clone())));
6348                        prev = s;
6349                    }
6350                    vs
6351                }
6352                3 => match native_decode(buf, pos)? {
6353                    // Columnar struct value list (or a tagged list for heterogeneous structs).
6354                    RuntimeValue::List(l) => l.borrow().to_values(),
6355                    _ => return None,
6356                },
6357                0 => {
6358                    let n = read_uvarint(buf, pos)? as usize;
6359                    let mut vs = Vec::with_capacity(n.min(keys.len().saturating_add(1)));
6360                    for _ in 0..n {
6361                        vs.push(native_decode(buf, pos)?);
6362                    }
6363                    vs
6364                }
6365                _ => return None,
6366            };
6367            if keys.len() != vals.len() {
6368                return None;
6369            }
6370            let mut m = MapStorage::default();
6371            for (k, v) in keys.into_iter().zip(vals.into_iter()) {
6372                m.insert(k, v);
6373            }
6374            RuntimeValue::Map(Rc::new(RefCell::new(m)))
6375        }
6376        T_STRUCT => {
6377            let type_name = read_str(buf, pos)?;
6378            let n = read_uvarint(buf, pos)?;
6379            let mut fields =
6380                std::collections::HashMap::with_capacity((n as usize).min(PREALLOC_CAP));
6381            for _ in 0..n {
6382                let name = read_str(buf, pos)?;
6383                let val = native_decode(buf, pos)?;
6384                fields.insert(name, val);
6385            }
6386            RuntimeValue::Struct(Box::new(StructValue { type_name, fields }))
6387        }
6388        // Single-struct schema DEFINITION (sequential): id + schema inline (registered),
6389        // then values in field order. Self-decodable even without a cache.
6390        T_STRUCT_DEF => {
6391            let id = read_uvarint(buf, pos)? as u32;
6392            let (type_name, field_names) = read_struct_schema(buf, pos)?;
6393            if !schema_recv_register_seq(id, &type_name, &field_names) {
6394                return None; // out-of-order / conflicting schema definition
6395            }
6396            decode_struct_values(buf, pos, type_name, field_names)?
6397        }
6398        // Single-struct schema REFERENCE (sequential): id resolved against the cache,
6399        // then values only. `None` (clean) if the schema was never defined here.
6400        T_STRUCT_REF => {
6401            let id = read_uvarint(buf, pos)? as u32;
6402            let (type_name, field_names) = schema_recv_lookup_seq(id)?;
6403            decode_struct_values(buf, pos, type_name, field_names)?
6404        }
6405        // Single-struct schema DEFINITION (content-addressed): schema inline (its
6406        // fingerprint derived + registered), then values. A fingerprint that conflicts
6407        // with a different cached schema is rejected.
6408        T_STRUCT_CDEF => {
6409            let (type_name, field_names) = read_struct_schema(buf, pos)?;
6410            if !schema_recv_register_ca(&type_name, &field_names) {
6411                return None; // fingerprint collision with a different schema
6412            }
6413            decode_struct_values(buf, pos, type_name, field_names)?
6414        }
6415        // Single-struct schema REFERENCE (content-addressed): an 8-byte fingerprint,
6416        // then values. `None` (clean) if no definition for it was seen (reorder/loss).
6417        T_STRUCT_CREF => {
6418            let raw = buf.get(*pos..pos.checked_add(8)?)?;
6419            let fp = u64::from_le_bytes(raw.try_into().ok()?);
6420            *pos += 8;
6421            let (type_name, field_names) = schema_recv_lookup_ca(fp)?;
6422            decode_struct_values(buf, pos, type_name, field_names)?
6423        }
6424        // Type-id elided struct: resolve the id against the shared registry (the receiver
6425        // runs the same program), then read the values. Unknown id → None (clean fail).
6426        T_STRUCT_TID => {
6427            let id = read_uvarint(buf, pos)? as u32;
6428            let (type_name, field_names) = type_registry_schema(id)?;
6429            decode_struct_values(buf, pos, type_name, field_names)?
6430        }
6431        // Offset-table view struct: read the schema, SKIP the offset table (a full decode
6432        // reads the values sequentially; the table is only for `WireView` random access).
6433        T_STRUCT_VIEW => {
6434            let type_name = read_str(buf, pos)?;
6435            let n = read_uvarint(buf, pos)? as usize;
6436            let mut field_names = Vec::with_capacity(n.min(PREALLOC_CAP));
6437            for _ in 0..n {
6438                field_names.push(read_str(buf, pos)?);
6439            }
6440            *pos = pos.checked_add(n.checked_mul(4)?)?; // skip the offset table
6441            if *pos > buf.len() {
6442                return None;
6443            }
6444            decode_struct_values(buf, pos, type_name, field_names)?
6445        }
6446        // Random-access record-list view: shared schema, then the row table (skipped), then
6447        // each row's field table (skipped) + values. We zip the rows back into structs and
6448        // re-columnarize via `from_values`, so re-encoding is byte-stable with the original.
6449        T_STRUCTS_VIEW => {
6450            let type_name = read_str(buf, pos)?;
6451            let f = read_uvarint(buf, pos)? as usize;
6452            let mut field_names = Vec::with_capacity(f.min(PREALLOC_CAP));
6453            for _ in 0..f {
6454                field_names.push(read_str(buf, pos)?);
6455            }
6456            let n = read_uvarint(buf, pos)? as usize;
6457            *pos = pos.checked_add(n.checked_mul(4)?)?; // skip the row offset table
6458            if *pos > buf.len() {
6459                return None;
6460            }
6461            let mut rows = Vec::with_capacity(n.min(PREALLOC_CAP));
6462            for _ in 0..n {
6463                *pos = pos.checked_add(f.checked_mul(4)?)?; // skip this row's field offset table
6464                if *pos > buf.len() {
6465                    return None;
6466                }
6467                rows.push(decode_struct_values(
6468                    buf,
6469                    pos,
6470                    type_name.clone(),
6471                    field_names.clone(),
6472                )?);
6473            }
6474            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::from_values(rows))))
6475        }
6476        // FIXED-stride record-list view: read the shared schema + the F kind bytes, then read
6477        // each row's cells by arithmetic (no offset tables), resolving FK_TEXT against the blob.
6478        T_STRUCTS_FVIEW => {
6479            let type_name = read_str(buf, pos)?;
6480            let f = read_uvarint(buf, pos)? as usize;
6481            let mut field_names = Vec::with_capacity(f.min(PREALLOC_CAP));
6482            for _ in 0..f {
6483                field_names.push(read_str(buf, pos)?);
6484            }
6485            let kinds = buf.get(*pos..pos.checked_add(f)?)?.to_vec();
6486            *pos += f;
6487            let n = read_uvarint(buf, pos)? as usize;
6488            let (offsets, stride) = fview_layout(&kinds);
6489            let rows_start = *pos;
6490            let rows_len = n.checked_mul(stride)?;
6491            let rows_bytes = buf.get(rows_start..rows_start.checked_add(rows_len)?)?;
6492            *pos = rows_start.checked_add(rows_len)?;
6493            let blob_len = read_uvarint(buf, pos)? as usize;
6494            let blob = buf.get(*pos..pos.checked_add(blob_len)?)?;
6495            *pos = pos.checked_add(blob_len)?;
6496            // Decode each field STRAIGHT into a typed column (no per-row HashMap, no `from_values`
6497            // re-scan) — the decode twin of the columnar encode. `field_names` is already the
6498            // canonical sorted order, so this is the same `Structs` repr `from_values` would build.
6499            let mut columns: Vec<ListRepr> = Vec::with_capacity(f);
6500            for j in 0..f {
6501                let off = offsets[j];
6502                let col = match kinds[j] {
6503                    FK_INT => {
6504                        let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
6505                        for r in 0..n {
6506                            let base = r.checked_mul(stride)?.checked_add(off)?;
6507                            v.push(i64::from_le_bytes(
6508                                rows_bytes
6509                                    .get(base..base.checked_add(8)?)?
6510                                    .try_into()
6511                                    .ok()?,
6512                            ));
6513                        }
6514                        ListRepr::Ints(v)
6515                    }
6516                    FK_FLOAT => {
6517                        let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
6518                        for r in 0..n {
6519                            let base = r.checked_mul(stride)?.checked_add(off)?;
6520                            v.push(f64::from_le_bytes(
6521                                rows_bytes
6522                                    .get(base..base.checked_add(8)?)?
6523                                    .try_into()
6524                                    .ok()?,
6525                            ));
6526                        }
6527                        ListRepr::Floats(v)
6528                    }
6529                    FK_BOOL => {
6530                        let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
6531                        for r in 0..n {
6532                            let base = r.checked_mul(stride)?.checked_add(off)?;
6533                            v.push(*rows_bytes.get(base)? != 0);
6534                        }
6535                        ListRepr::Bools(v)
6536                    }
6537                    FK_TEXT => {
6538                        let mut data: Vec<u8> = Vec::new();
6539                        let mut ends: Vec<u32> = Vec::with_capacity(n.min(PREALLOC_CAP));
6540                        for r in 0..n {
6541                            let base = r.checked_mul(stride)?.checked_add(off)?;
6542                            let toff = u32::from_le_bytes(
6543                                rows_bytes
6544                                    .get(base..base.checked_add(4)?)?
6545                                    .try_into()
6546                                    .ok()?,
6547                            ) as usize;
6548                            let tlen = u32::from_le_bytes(
6549                                rows_bytes
6550                                    .get(base.checked_add(4)?..base.checked_add(8)?)?
6551                                    .try_into()
6552                                    .ok()?,
6553                            ) as usize;
6554                            data.extend_from_slice(blob.get(toff..toff.checked_add(tlen)?)?);
6555                            ends.push(data.len() as u32);
6556                        }
6557                        ListRepr::strings(data, ends)
6558                    }
6559                    _ => return None,
6560                };
6561                columns.push(col);
6562            }
6563            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Structs {
6564                type_name,
6565                field_names,
6566                columns,
6567            })))
6568        }
6569        T_INDUCTIVE => {
6570            let inductive_type = read_str(buf, pos)?;
6571            let constructor = read_str(buf, pos)?;
6572            let args = read_seq(buf, pos)?;
6573            RuntimeValue::Inductive(std::rc::Rc::new(InductiveValue {
6574                inductive_type,
6575                constructor,
6576                args,
6577            }))
6578        }
6579        // Type-id elided enum: resolve the enum id + constructor index against the shared
6580        // registry, then read the args. Unknown id / out-of-range index → None (clean).
6581        T_INDUCTIVE_TID => {
6582            let enum_id = read_uvarint(buf, pos)? as u32;
6583            let ctor_idx = read_uvarint(buf, pos)? as usize;
6584            let (inductive_type, ctors) = type_registry_enum_schema(enum_id)?;
6585            let constructor = ctors.get(ctor_idx)?.clone();
6586            let args = read_seq(buf, pos)?;
6587            RuntimeValue::Inductive(std::rc::Rc::new(InductiveValue {
6588                inductive_type,
6589                constructor,
6590                args,
6591            }))
6592        }
6593        // Columnar struct list: schema once, then one self-describing packed column
6594        // per field; we read the columns and zip them back into N structs. Decoding
6595        // to `Boxed` keeps re-encoding byte-stable (the schema re-derives identically).
6596        // Self-describing struct list: schema inline, then the columns.
6597        T_STRUCTS => {
6598            let (type_name, field_names) = read_struct_schema(buf, pos)?;
6599            decode_struct_columns(buf, pos, type_name, field_names)?
6600        }
6601        // Shared-registry struct list: resolve the type id (names elided), then columns.
6602        // `None` (clean) if the id can't be resolved against this decoder's registry.
6603        T_STRUCTS_TID => {
6604            let id = read_uvarint(buf, pos)? as u32;
6605            let (type_name, field_names) = type_registry_schema(id)?;
6606            decode_struct_columns(buf, pos, type_name, field_names)?
6607        }
6608        // Sequential schema DEFINITION: id + schema inline (registered), then columns.
6609        // Self-decodable even without a cache.
6610        T_STRUCTS_DEF => {
6611            let id = read_uvarint(buf, pos)? as u32;
6612            let (type_name, field_names) = read_struct_schema(buf, pos)?;
6613            if !schema_recv_register_seq(id, &type_name, &field_names) {
6614                return None; // out-of-order / conflicting schema definition
6615            }
6616            decode_struct_columns(buf, pos, type_name, field_names)?
6617        }
6618        // Sequential schema REFERENCE: id resolved against the cache, then columns.
6619        // `None` (clean) if the schema was never defined to this decoder.
6620        T_STRUCTS_REF => {
6621            let id = read_uvarint(buf, pos)? as u32;
6622            let (type_name, field_names) = schema_recv_lookup_seq(id)?;
6623            decode_struct_columns(buf, pos, type_name, field_names)?
6624        }
6625        // Content-addressed schema DEFINITION: schema inline (the fingerprint is
6626        // derived from it), registered under its fingerprint, then columns. A
6627        // fingerprint that conflicts with a different cached schema is rejected.
6628        T_STRUCTS_CDEF => {
6629            let (type_name, field_names) = read_struct_schema(buf, pos)?;
6630            if !schema_recv_register_ca(&type_name, &field_names) {
6631                return None; // fingerprint collision with a different schema
6632            }
6633            decode_struct_columns(buf, pos, type_name, field_names)?
6634        }
6635        // Content-addressed schema REFERENCE: an 8-byte fingerprint, then columns.
6636        // `None` (clean) if no definition for that fingerprint was seen (reorder/loss).
6637        T_STRUCTS_CREF => {
6638            let raw = buf.get(*pos..pos.checked_add(8)?)?;
6639            let fp = u64::from_le_bytes(raw.try_into().ok()?);
6640            *pos += 8;
6641            let (type_name, field_names) = schema_recv_lookup_ca(fp)?;
6642            decode_struct_columns(buf, pos, type_name, field_names)?
6643        }
6644        // Columnar enum list (tagged union): type once + constructor dictionary with
6645        // arities + the per-row index column + dense per-constructor arg columns.
6646        // Decodes STRAIGHT into the columnar `Inductives` repr (no per-row rebuild);
6647        // `ranks` are recomputed here. Nullary enums have all-zero arities.
6648        T_INDUCTIVES => {
6649            let inductive_type = read_str(buf, pos)?;
6650            let d = read_uvarint(buf, pos)? as usize;
6651            let mut ctor_dict = Vec::with_capacity(d.min(PREALLOC_CAP));
6652            let mut arities = Vec::with_capacity(d.min(PREALLOC_CAP));
6653            for _ in 0..d {
6654                ctor_dict.push(read_str(buf, pos)?);
6655                arities.push(read_uvarint(buf, pos)? as usize);
6656            }
6657            // The constructor-index column → `ctors: Vec<u32>` (each index < d).
6658            let idx = match native_decode(buf, pos)? {
6659                RuntimeValue::List(l) => Rc::try_unwrap(l)
6660                    .map(RefCell::into_inner)
6661                    .unwrap_or_else(|rc| rc.borrow().clone()),
6662                _ => return None,
6663            };
6664            let mut ctors: Vec<u32> = Vec::with_capacity(idx.len().min(PREALLOC_CAP));
6665            for v in idx.to_values() {
6666                match v {
6667                    RuntimeValue::Int(i) if i >= 0 && (i as usize) < d => ctors.push(i as u32),
6668                    _ => return None,
6669                }
6670            }
6671            // The dense per-constructor argument columns.
6672            let mut arg_cols: Vec<Vec<ListRepr>> = Vec::with_capacity(d.min(PREALLOC_CAP));
6673            for &arity in &arities {
6674                let mut cols = Vec::with_capacity(arity.min(PREALLOC_CAP));
6675                for _ in 0..arity {
6676                    let col = match native_decode(buf, pos)? {
6677                        RuntimeValue::List(l) => Rc::try_unwrap(l)
6678                            .map(RefCell::into_inner)
6679                            .unwrap_or_else(|rc| rc.borrow().clone()),
6680                        _ => return None,
6681                    };
6682                    cols.push(col);
6683                }
6684                arg_cols.push(cols);
6685            }
6686            // Recompute ranks and validate each constructor's column lengths.
6687            let mut counts = vec![0u32; d];
6688            let mut ranks = Vec::with_capacity(ctors.len());
6689            for &c in &ctors {
6690                ranks.push(counts[c as usize]);
6691                counts[c as usize] += 1;
6692            }
6693            for c in 0..d {
6694                if arg_cols[c]
6695                    .iter()
6696                    .any(|col| col.len() != counts[c] as usize)
6697                {
6698                    return None; // a column whose length disagrees with the constructor count
6699                }
6700            }
6701            RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Inductives {
6702                inductive_type,
6703                ctor_dict,
6704                ctors,
6705                ranks,
6706                arg_cols,
6707            })))
6708        }
6709        _ => return None,
6710    })
6711}
6712
6713fn read_seq(buf: &[u8], pos: &mut usize) -> Option<Vec<RuntimeValue>> {
6714    let n = bounded_count(read_uvarint(buf, pos)?)?;
6715    let mut v = Vec::with_capacity(n.min(PREALLOC_CAP));
6716    for _ in 0..n {
6717        v.push(native_decode(buf, pos)?);
6718    }
6719    Some(v)
6720}
6721
6722/// Read a struct schema (type name + field names) from the wire.
6723fn read_struct_schema(buf: &[u8], pos: &mut usize) -> Option<(String, Vec<String>)> {
6724    let type_name = read_str(buf, pos)?;
6725    let k = read_uvarint(buf, pos)? as usize;
6726    let mut field_names = Vec::with_capacity(k.min(PREALLOC_CAP));
6727    for _ in 0..k {
6728        field_names.push(read_str(buf, pos)?);
6729    }
6730    Some((type_name, field_names))
6731}
6732
6733/// Rebuild a single struct from a known schema (the schema-dictionary forms): one
6734/// value per field, in the schema's canonical field order, zipped back by name.
6735fn decode_struct_values(
6736    buf: &[u8],
6737    pos: &mut usize,
6738    type_name: String,
6739    field_names: Vec<String>,
6740) -> Option<RuntimeValue> {
6741    let mut fields = std::collections::HashMap::with_capacity(field_names.len().min(PREALLOC_CAP));
6742    for name in field_names {
6743        let val = native_decode(buf, pos)?;
6744        fields.insert(name, val);
6745    }
6746    Some(RuntimeValue::Struct(Box::new(StructValue {
6747        type_name,
6748        fields,
6749    })))
6750}
6751
6752/// Read a struct list's body (the row count + one self-describing column per field)
6753/// given its schema, decoding STRAIGHT into the columnar `Structs` repr (no per-row
6754/// rebuild). A zero-field schema — which our encoder never emits — falls back to
6755/// boxed empty structs so the row count survives.
6756fn decode_struct_columns(
6757    buf: &[u8],
6758    pos: &mut usize,
6759    type_name: String,
6760    field_names: Vec<String>,
6761) -> Option<RuntimeValue> {
6762    let k = field_names.len();
6763    let n = read_uvarint(buf, pos)? as usize;
6764    let mut columns: Vec<ListRepr> = Vec::with_capacity(k.min(PREALLOC_CAP));
6765    for _ in 0..k {
6766        // Keep each decoded column AS its packed `ListRepr` (no `to_values`):
6767        // `native_decode` just minted this `Rc`, so `try_unwrap` takes the inner
6768        // buffer without cloning.
6769        let col = match native_decode(buf, pos)? {
6770            RuntimeValue::List(l) => Rc::try_unwrap(l)
6771                .map(RefCell::into_inner)
6772                .unwrap_or_else(|rc| rc.borrow().clone()),
6773            _ => return None,
6774        };
6775        if col.len() != n {
6776            return None; // a column whose length disagrees with the row count
6777        }
6778        columns.push(col);
6779    }
6780    Some(if columns.is_empty() {
6781        let rows = (0..n)
6782            .map(|_| {
6783                RuntimeValue::Struct(Box::new(StructValue {
6784                    type_name: type_name.clone(),
6785                    fields: std::collections::HashMap::new(),
6786                }))
6787            })
6788            .collect();
6789        RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Boxed(rows))))
6790    } else {
6791        RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Structs {
6792            type_name,
6793            field_names,
6794            columns,
6795        })))
6796    })
6797}
6798
6799fn header(codec: WireCodec, integrity: WireIntegrity, compression: WireCompression) -> u8 {
6800    let c = if matches!(codec, WireCodec::Json) {
6801        H_JSON
6802    } else {
6803        0
6804    };
6805    let i = if matches!(integrity, WireIntegrity::Checked) {
6806        H_CHECKED
6807    } else {
6808        0
6809    };
6810    let z = if compression == WireCompression::None {
6811        0
6812    } else {
6813        H_COMPRESSED | (compression_id(compression) << 2)
6814    };
6815    c | i | z
6816}
6817
6818/// Wrap a body in its frame: the header byte, then (for `Checked`) an 8-byte
6819/// FNV-1a checksum over the (possibly compressed) body, then the body.
6820fn frame(
6821    codec: WireCodec,
6822    integrity: WireIntegrity,
6823    compression: WireCompression,
6824    body: Vec<u8>,
6825) -> Vec<u8> {
6826    let h = header(codec, integrity, compression);
6827    match integrity {
6828        WireIntegrity::Raw => {
6829            let mut out = Vec::with_capacity(body.len() + 1);
6830            out.push(h);
6831            out.extend_from_slice(&body);
6832            out
6833        }
6834        WireIntegrity::Checked => {
6835            let mut out = Vec::with_capacity(body.len() + 9);
6836            out.push(h);
6837            out.extend_from_slice(&fnv1a_64(&body).to_le_bytes());
6838            out.extend_from_slice(&body);
6839            out
6840        }
6841    }
6842}
6843
6844/// Strip the frame: return `(codec, compressed, body)`, verifying the checksum in
6845/// `Checked` mode. `None` on an unknown header, a short frame, or a checksum
6846/// mismatch. The checksum is verified BEFORE the caller inflates, so a corrupt
6847/// message never reaches the decompressor.
6848/// Decode the frame header into `(codec, compression, body)`. When `verify` is set and the
6849/// message carries a checksum, the body is FNV-validated (O(body)) — corruption → `None`.
6850/// A zero-copy view passes `verify = false` so opening a message is always O(1) (a checksum
6851/// hash would defeat random access; the view trusts the bytes, like Cap'n Proto / Arrow).
6852fn unframe_with(bytes: &[u8], verify: bool) -> Option<(WireCodec, WireCompression, &[u8])> {
6853    let (&h, rest) = bytes.split_first()?;
6854    if h & !H_KNOWN != 0 {
6855        return None; // an unknown format bit is set
6856    }
6857    let codec = if h & H_JSON != 0 {
6858        WireCodec::Json
6859    } else {
6860        WireCodec::Native
6861    };
6862    let compression = if h & H_COMPRESSED == 0 {
6863        WireCompression::None
6864    } else {
6865        match (h & H_CODEC) >> 2 {
6866            0 => WireCompression::Deflate,
6867            1 => WireCompression::Lz4,
6868            2 => WireCompression::Zstd,
6869            _ => return None, // a reserved codec id
6870        }
6871    };
6872    let body = if h & H_CHECKED != 0 {
6873        if rest.len() < 8 {
6874            return None;
6875        }
6876        let (sum, body) = rest.split_at(8);
6877        if verify {
6878            let expected = u64::from_le_bytes(sum.try_into().ok()?);
6879            if fnv1a_64(body) != expected {
6880                return None;
6881            }
6882        }
6883        body
6884    } else {
6885        rest
6886    };
6887    Some((codec, compression, body))
6888}
6889
6890/// Decode the frame, validating the integrity checksum if present (the full-decode path).
6891fn unframe(bytes: &[u8]) -> Option<(WireCodec, WireCompression, &[u8])> {
6892    unframe_with(bytes, true)
6893}
6894
6895/// FNV-1a, 64-bit — a small, fast, dependency-free checksum. Not cryptographic
6896/// (it detects corruption, not a forged message); a signing layer is separate. The
6897/// constants are part of the wire, so they must never change.
6898fn fnv1a_64(bytes: &[u8]) -> u64 {
6899    let mut hash = 0xcbf2_9ce4_8422_2325u64;
6900    for &b in bytes {
6901        hash ^= b as u64;
6902        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
6903    }
6904    hash
6905}
6906
6907/// The process integrity default — `Checked`, unless `LOGOS_WIRE=raw` opts into
6908/// the bare fast path. Read once.
6909pub(crate) fn default_integrity() -> WireIntegrity {
6910    static MODE: std::sync::OnceLock<WireIntegrity> = std::sync::OnceLock::new();
6911    *MODE.get_or_init(|| match std::env::var("LOGOS_WIRE").ok().as_deref() {
6912        Some("raw") => WireIntegrity::Raw,
6913        _ => WireIntegrity::Checked,
6914    })
6915}
6916
6917thread_local! {
6918    static INTEGRITY_OVERRIDE: std::cell::Cell<Option<WireIntegrity>> = const { std::cell::Cell::new(None) };
6919}
6920
6921/// The latency↔safety dial: run `f` with the checksum on (`Checked`) or off (`Raw`),
6922/// overriding the process default for the duration. Scoped — never leaks. `Raw` is
6923/// the fastest path (the FNV checksum is the bulk of small-message encode cost);
6924/// `Checked` detects corruption. Pairs with `with_numerics`/`with_compression_codec`.
6925pub fn with_integrity<T>(integrity: WireIntegrity, f: impl FnOnce() -> T) -> T {
6926    let prev = INTEGRITY_OVERRIDE.with(|c| c.replace(Some(integrity)));
6927    let out = f();
6928    INTEGRITY_OVERRIDE.with(|c| c.set(prev));
6929    out
6930}
6931
6932/// The integrity in force for a plain `message_to_wire`: a scoped [`with_integrity`]
6933/// override if set, else the process default.
6934fn current_integrity() -> WireIntegrity {
6935    INTEGRITY_OVERRIDE
6936        .with(std::cell::Cell::get)
6937        .unwrap_or_else(default_integrity)
6938}
6939
6940/// Varint-encoded bincode: small ints and lengths cost a byte or two, so an array
6941/// of small numbers is genuinely compact (vs. the fixed 8-byte ints of the default
6942/// config). Both peers run this same code, so the encoding is shared by construction.
6943fn wire_options() -> impl bincode::Options {
6944    bincode::DefaultOptions::new()
6945}
6946
6947/// The canonical bytes of a wire value — used only to order map entries so the
6948/// encoding is independent of the source map's hash iteration order.
6949fn canon_bytes(w: &WireValue) -> Vec<u8> {
6950    use bincode::Options;
6951    wire_options().serialize(w).unwrap_or_default()
6952}
6953
6954#[cfg(test)]
6955#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/concurrency/marshal/tests.rs"]
6956mod tests;