Skip to main content

hurray_core/
error.rs

1/// Crate-level error type for `hurray-core`.
2#[derive(Debug, thiserror::Error)]
3#[non_exhaustive]
4pub enum Error {
5    /// An element type is not supported or recognized.
6    #[error("unsupported element type: {0}")]
7    UnsupportedElementType(String),
8
9    /// A shape or stride value is invalid.
10    #[error("invalid shape or stride: {0}")]
11    InvalidShape(String),
12
13    /// A buffer alignment requirement was not satisfied.
14    ///
15    /// This variant is preserved for callers that check alignment against an
16    /// externally observed value (e.g., checking the actual base-address
17    /// alignment of a pointer). For descriptor-level validation, prefer
18    /// [`Error::AlignmentNotPowerOfTwo`] and [`Error::AlignmentBelowMinimum`].
19    #[error("buffer alignment error: expected {expected}-byte alignment, got {actual}")]
20    AlignmentError { expected: usize, actual: usize },
21
22    /// The `alignment` field is not a power of two.
23    ///
24    /// The spec requires that `alignment` is always a power of two
25    /// (see `docs/spec/buffer-protocol.md § Alignment`).
26    #[error("alignment {alignment} is not a power of two")]
27    AlignmentNotPowerOfTwo { alignment: u32 },
28
29    /// The `alignment` field is below the minimum required for a non-empty buffer.
30    ///
31    /// Non-empty buffers (those with `byte_size > 0`) MUST declare an alignment
32    /// of at least [`crate::MIN_BUFFER_ALIGNMENT`] (64 bytes) to guarantee
33    /// compatibility with all current SIMD instruction sets.
34    #[error(
35        "alignment {alignment} is below the minimum of {minimum} bytes required for non-empty buffers"
36    )]
37    AlignmentBelowMinimum { alignment: u32, minimum: u32 },
38
39    /// The device tag byte `0xFF` is permanently invalid.
40    ///
41    /// This sentinel is reserved by the spec and MUST be rejected by all
42    /// conforming readers (see `docs/spec/buffer-protocol.md § Device Tags`).
43    #[error("invalid device tag: 0x{0:02X} is permanently reserved")]
44    InvalidDeviceTag(u8),
45
46    /// The device tag falls in the range `0x04`–`0xEF` reserved for future spec versions.
47    ///
48    /// Implementations MUST NOT assign semantics to tags in this range.
49    #[error("reserved device tag: 0x{0:02X} is reserved for future specification versions")]
50    ReservedDeviceTag(u8),
51
52    /// All buffers in a tensor descriptor must reside on the same device.
53    ///
54    /// The `expected` and `found` fields are raw wire bytes so that the error
55    /// message is independent of which device tags the current implementation
56    /// recognises.
57    #[error("device tag mismatch: expected 0x{expected:02X}, found 0x{found:02X}")]
58    DeviceTagMismatch {
59        /// The wire byte of the first buffer's device tag.
60        expected: u8,
61        /// The wire byte of the mismatching buffer's device tag.
62        found: u8,
63    },
64
65    /// Buffer list is empty; at least one buffer handle is required.
66    ///
67    /// Returned by [`crate::validate_colocation`] when called with an empty
68    /// slice — there is no device tag to validate against.
69    #[error("buffer list is empty")]
70    EmptyBufferList,
71
72    /// The memory class byte is `0xFF`, which is permanently reserved by the spec.
73    ///
74    /// Readers MUST reject a buffer handle whose `memory_class` is `0xFF`.
75    #[error("invalid memory class: 0x{0:02X} is permanently reserved")]
76    InvalidMemoryClass(u8),
77
78    /// The memory class byte falls in the range `0x04`–`0xEF` reserved for future spec versions.
79    ///
80    /// Readers MUST reject a buffer handle with a `memory_class` in this range.
81    #[error("reserved memory class: 0x{0:02X} is reserved for future specification versions")]
82    ReservedMemoryClass(u8),
83
84    /// All buffers in a tensor descriptor must share the same memory class.
85    ///
86    /// The `expected` and `found` fields are raw wire bytes so that the error
87    /// message is independent of which memory classes the current implementation
88    /// recognises.
89    #[error("memory class mismatch: expected 0x{expected:02X}, found 0x{found:02X}")]
90    MemoryClassMismatch {
91        /// The wire byte of the first buffer's memory class.
92        expected: u8,
93        /// The wire byte of the mismatching buffer's memory class.
94        found: u8,
95    },
96
97    /// A quantization descriptor is malformed.
98    #[error("invalid quantization descriptor: {0}")]
99    InvalidQuantization(String),
100
101    /// The quantization descriptor payload is shorter than the minimum required.
102    #[error("quantization descriptor too short: have {found} bytes, need {needed}")]
103    QuantizationDescriptorTooShort {
104        /// Number of bytes available.
105        found: usize,
106        /// Number of bytes required.
107        needed: usize,
108    },
109
110    /// The scheme tag `0x00` or `0xFF` is permanently reserved by the spec.
111    ///
112    /// A reader MUST reject any descriptor whose `scheme_tag` is `0x00` or `0xFF`.
113    #[error("invalid quantization scheme tag: 0x{0:02X} is permanently reserved")]
114    InvalidQuantizationSchemeTag(u8),
115
116    /// The scheme tag falls in a range reserved for future specification versions.
117    ///
118    /// Ranges: `0x60`–`0xEF`. Implementations MUST NOT assign semantics to these tags.
119    #[error(
120        "reserved quantization scheme tag: 0x{0:02X} is reserved for future specification versions"
121    )]
122    ReservedQuantizationSchemeTag(u8),
123
124    /// The scheme tag is in the implementation-private range `0xF0`–`0xFE`.
125    ///
126    /// Private scheme tags have unconstrained payloads beyond the 4-byte header;
127    /// `hurray-core` cannot interpret them. Callers that need private scheme
128    /// support must handle the raw bytes at a higher layer.
129    ///
130    /// WHY rejected here: the payload beyond the 4-byte header is unconstrained
131    /// for private tags, giving this crate nothing useful to return. Callers that
132    /// need private schemes handle the raw bytes at a higher layer (design decision #1).
133    #[error(
134        "private quantization scheme tag: 0x{0:02X} is implementation-private and not interpretable by this crate"
135    )]
136    PrivateQuantizationSchemeTag(u8),
137
138    /// The scheme tag is in an allocated range but not assigned to any known scheme.
139    #[error("unknown quantization scheme tag: 0x{0:02X}")]
140    UnknownQuantizationSchemeTag(u8),
141
142    /// The `scheme_version` field exceeds the highest version this implementation supports.
143    ///
144    /// Per the spec, a reader MUST reject a descriptor whose `scheme_version` exceeds
145    /// the highest version defined for the given `scheme_tag`.
146    #[error(
147        "unsupported scheme version: scheme tag 0x{tag:02X} version {version} is not supported (highest supported: {supported})"
148    )]
149    UnsupportedSchemeVersion {
150        /// The scheme tag being decoded.
151        tag: u8,
152        /// The version found on the wire.
153        version: u8,
154        /// The highest version this implementation supports for this tag.
155        supported: u8,
156    },
157
158    /// Reserved `flags` bits are set in the quantization descriptor header.
159    ///
160    /// Per the spec, a reader MUST reject a descriptor with any reserved `flags` bit set.
161    #[error("reserved quantization flags bits set: 0x{flags:04X} (reserved mask: 0x{mask:04X})")]
162    ReservedQuantizationFlagsBits {
163        /// The full flags value found on the wire.
164        flags: u16,
165        /// The bitmask of bits that must be zero.
166        mask: u16,
167    },
168
169    /// The `block_size` field is out of range or not a power of two for the given scheme.
170    #[error(
171        "invalid block_size {block_size} for scheme 0x{scheme_tag:02X}: must be a power of two in [{min}, {max}]"
172    )]
173    InvalidBlockSize {
174        /// The quantization scheme tag.
175        scheme_tag: u8,
176        /// The block size found on the wire.
177        block_size: u32,
178        /// Minimum permitted block size for this scheme.
179        min: u32,
180        /// Maximum permitted block size for this scheme.
181        max: u32,
182    },
183
184    /// The tensor's storage `type_tag` is not valid for the given quantization scheme.
185    ///
186    /// Each quantization scheme defines a fixed set of permitted storage types.
187    #[error(
188        "element type 0x{type_tag:02X} is not a valid storage type for scheme 0x{scheme_tag:02X}"
189    )]
190    InvalidStorageTypeForScheme {
191        /// The quantization scheme tag.
192        scheme_tag: u8,
193        /// The storage type tag found in the tensor descriptor.
194        type_tag: u8,
195    },
196
197    /// The quantization axis index is out of bounds for the tensor's rank.
198    ///
199    /// Per the spec, `axis` MUST satisfy `axis < rank`.
200    #[error("quantization axis {axis} out of bounds: rank is {rank}")]
201    QuantizationAxisOutOfBounds {
202        /// The axis value from the quantization descriptor.
203        axis: u32,
204        /// The rank of the tensor descriptor.
205        rank: u32,
206    },
207
208    /// The tensor shape along the quantization axis is incompatible with the block size.
209    ///
210    /// For MXFP, `shape[axis]` must be a positive multiple of `block_size`.
211    /// For per-block-affine and NF4, `block_size` must not exceed `shape[axis]`
212    /// when `shape[axis] > 0`.
213    #[error(
214        "quantization shape mismatch on axis {axis}: shape[axis] = {shape_axis}, block_size = {block_size}: {reason}"
215    )]
216    QuantizationShapeMismatch {
217        /// The quantization axis.
218        axis: u32,
219        /// The resolved size of `shape[axis]`.
220        shape_axis: u64,
221        /// The block size from the quantization descriptor.
222        block_size: u32,
223        /// A human-readable description of the constraint violated.
224        reason: &'static str,
225    },
226
227    /// The `zero_point` value is outside the representable range of the storage type.
228    ///
229    /// Per the spec, `zero_point` MUST lie within the representable range of the
230    /// storage type (e.g., `[0, 255]` for `uint8`).
231    #[error(
232        "zero_point {zero_point} is outside the range of storage type 0x{type_tag:02X}: [{min}, {max}]"
233    )]
234    ZeroPointOutOfRange {
235        /// The storage type tag.
236        type_tag: u8,
237        /// The zero-point value that was rejected.
238        zero_point: i32,
239        /// Minimum representable value for the storage type.
240        min: i64,
241        /// Maximum representable value for the storage type.
242        max: i64,
243    },
244
245    /// A quantization-parameter buffer index aliases the tensor data buffer.
246    ///
247    /// Per the spec, quantization-parameter buffers MUST occupy distinct indices
248    /// from the tensor data buffer.
249    #[error("quantization parameter buffer index {index} aliases the tensor data buffer index")]
250    QuantizationBufferAliasesData {
251        /// The offending quantization-parameter buffer index.
252        index: u32,
253    },
254
255    /// A quantization-parameter buffer index is out of range.
256    ///
257    /// The index must be less than `buffer_count` in the tensor descriptor's
258    /// buffer table.
259    #[error(
260        "quantization parameter buffer index {index} is out of range (buffer_count = {buffer_count})"
261    )]
262    QuantizationBufferIndexOutOfRange {
263        /// The out-of-range buffer index.
264        index: u32,
265        /// The number of buffers in the tensor descriptor's buffer table.
266        buffer_count: u32,
267    },
268
269    /// The type tag `0x00` or `0xFF` is explicitly invalid per the spec.
270    ///
271    /// These two sentinels are permanently reserved and MUST be rejected by all
272    /// conforming readers regardless of operating mode.
273    #[error(
274        "invalid type tag: 0x{0:02X} is permanently reserved and must never appear in a descriptor"
275    )]
276    InvalidTypeTag(u8),
277
278    /// The type tag falls in a range reserved for future specification versions.
279    ///
280    /// Reserved ranges: `0x47` and `0x80`–`0xEF`. Implementations MUST NOT
281    /// assign semantics to these tags.
282    #[error("reserved type tag: 0x{0:02X} is reserved for future specification versions")]
283    ReservedTypeTag(u8),
284
285    /// The type tag is not recognized by this implementation.
286    ///
287    /// This covers the private-extension range `0xF0`–`0xFE` and any other
288    /// unassigned tag value not covered by [`Error::InvalidTypeTag`] or
289    /// [`Error::ReservedTypeTag`].
290    #[error("unknown type tag: 0x{0:02X} is not recognized by this implementation")]
291    UnknownTypeTag(u8),
292
293    /// The tensor rank exceeds the maximum of 64 defined by the spec.
294    #[error("rank {rank} exceeds the maximum permitted rank of {max}")]
295    RankExceedsMaximum {
296        /// The rank value that was rejected.
297        rank: u32,
298        /// The maximum permitted rank (`64`).
299        max: u32,
300    },
301
302    /// Layout tag `0x00` or `0xFF` is permanently invalid.
303    ///
304    /// These sentinels are reserved by the spec and MUST be rejected by all
305    /// conforming readers regardless of operating mode
306    /// (see `docs/spec/memory-layout.md § Layout Taxonomy`).
307    #[error("invalid layout tag: 0x{0:02X} is permanently reserved")]
308    InvalidLayoutTag(u8),
309
310    /// Layout tag is in a range reserved for future specification versions.
311    ///
312    /// Reserved ranges: `0x0C`–`0x3F`, `0x41`–`0x7F`, `0x80`–`0xEF`.
313    /// (`0x0B` is the composite / virtual head (ADR-027) and `0x40` is Hilbert;
314    /// both are named tags in this crate.)
315    /// Implementations MUST NOT assign semantics to these tags in strict mode.
316    #[error("reserved layout tag: 0x{0:02X} is reserved for future specification versions")]
317    ReservedLayoutTag(u8),
318
319    /// A tag with a named layout descriptor was passed to the permissive
320    /// [`UnknownLayout`](crate::layout::UnknownLayout) constructor.
321    ///
322    /// "Unknown" is what lets a permissive reader relay a tag it does not
323    /// understand. Applied to a tag this implementation *does* understand, it
324    /// becomes a bypass: `Unknown` has no buffer count and no shape constraints,
325    /// so such a descriptor would skip every check the named variant applies and
326    /// then encode to a wire tag a conforming reader parses as that named layout.
327    #[error("layout tag 0x{0:02X} has a named descriptor and must not be wrapped as unknown")]
328    NamedLayoutTag(u8),
329
330    /// Layout tag is in the private-extension range `0xF0`–`0xFE`.
331    ///
332    /// Private extension layouts have unconstrained payloads beyond the standard
333    /// header fields; strict-mode readers reject them unless both parties have
334    /// agreed on semantics out of band (see `docs/spec/memory-layout.md § Extension Layouts`).
335    #[error("private layout tag: 0x{0:02X} is in the private-extension range (0xF0–0xFE)")]
336    PrivateLayoutTag(u8),
337
338    /// Layout tag is not recognized by this implementation.
339    ///
340    /// Covers any allocated but unassigned tag not already matched by
341    /// [`Error::InvalidLayoutTag`] or [`Error::ReservedLayoutTag`].
342    #[error("unknown layout tag: 0x{0:02X} is not recognized by this implementation")]
343    UnknownLayoutTag(u8),
344
345    /// A layout descriptor field is invalid.
346    ///
347    /// The inner message describes the specific field and the constraint violated.
348    #[error("invalid layout descriptor: {0}")]
349    InvalidLayout(String),
350
351    /// The number of index components does not match the tensor rank.
352    #[error("index rank {index_rank} does not match shape rank {shape_rank}")]
353    IndexRankMismatch {
354        index_rank: usize,
355        shape_rank: usize,
356    },
357
358    /// An index component exceeds the dimension size.
359    #[error("index[{dim}] = {index} is out of range [0, {size})")]
360    IndexOutOfRange { dim: u32, index: u64, size: u64 },
361
362    /// Arithmetic overflow when computing an element or byte address.
363    #[error("address arithmetic overflow")]
364    AddressOverflow,
365
366    /// The computed byte address falls outside the buffer bounds.
367    #[error("byte address falls outside buffer bounds [0, {buffer_size})")]
368    ByteAddressOverflow { buffer_size: u64 },
369
370    /// This layout requires multi-buffer access via an inherent method, not the trait.
371    #[error("layout tag 0x{layout_tag:02X} requires multi-buffer access; use the layout-specific method")]
372    LayoutRequiresMultiBuffer { layout_tag: u8 },
373
374    /// Recursive layout nesting depth exceeded the implementation limit of 8 levels.
375    ///
376    /// Despite the name, this guards recursion depth for every recursive
377    /// structure in the crate: [`crate::layout::TiledLayout`]'s nested layout
378    /// payload, and [`crate::composite::CompositeTensor`]'s cross-descriptor
379    /// nesting (a member whose own layout is itself `Composite`). Kept as
380    /// `SubpavingNestingTooDeep` for wire/API stability rather than renamed
381    /// as part of the subpaving removal.
382    #[error("layout nesting depth exceeds the implementation limit of 8 levels")]
383    SubpavingNestingTooDeep,
384
385    /// A DYNAMIC dimension cannot be used for element addressing.
386    #[error("dimension {dim} is DYNAMIC and cannot be used for element addressing")]
387    DynamicDimInIndexing { dim: u32 },
388
389    /// Arithmetic overflow in Morton or Hilbert index computation.
390    #[error("index arithmetic overflow in space-filling curve computation")]
391    IndexArithmeticOverflow,
392
393    // ── Descriptor (Layer 4) errors ───────────────────────────────────────────
394    /// The sync mode byte is not a recognized value.
395    ///
396    /// Valid sync mode bytes are `0x00` (`ProducerSynced`), `0x01` (`Event`),
397    /// and `0x02` (`ConsumerStream`). Bytes `0x03`–`0xFF` are reserved or
398    /// permanently invalid; all conforming readers MUST reject them.
399    ///
400    /// See `docs/spec/buffer-protocol.md § Synchronization Mode` and ADR-018.
401    #[error("invalid sync mode byte: 0x{0:02X} (reserved or invalid)")]
402    InvalidSyncMode(u8),
403
404    /// Magic bytes are not `"HRRY"` (`0x48 0x52 0x52 0x59`).
405    #[error("invalid magic: expected 48 52 52 59, got {got:02X?}")]
406    InvalidMagic {
407        /// The four bytes actually found at offset 0.
408        got: [u8; 4],
409    },
410
411    /// `version_major` exceeds the supported major version (`1`).
412    #[error("unsupported descriptor version {major}.{minor}")]
413    UnsupportedDescriptorVersion {
414        /// The major version found on the wire.
415        major: u8,
416        /// The minor version found on the wire.
417        minor: u8,
418    },
419
420    /// `descriptor_length` is less than the minimum valid size (`20`).
421    #[error("descriptor_length {length} is below the minimum of 20 bytes")]
422    DescriptorTooShort {
423        /// The `descriptor_length` value found on the wire.
424        length: u32,
425    },
426
427    /// Cursor ran out of bytes before a field could be read.
428    #[error("descriptor truncated: need {needed} bytes at offset {offset}, have {available}")]
429    DescriptorTruncated {
430        /// Byte offset at which the read was attempted.
431        offset: usize,
432        /// Number of bytes needed.
433        needed: usize,
434        /// Number of bytes actually available.
435        available: usize,
436    },
437
438    /// A reserved flag bit is set.
439    #[error("reserved flag bits set: 0x{flags:08X} (reserved mask: 0x{mask:08X})")]
440    ReservedDescriptorFlagBitsSet {
441        /// The full flags value found on the wire.
442        flags: u32,
443        /// The bitmask of bits that must be zero.
444        mask: u32,
445    },
446
447    /// A reserved field that MUST be `0x00` contains a non-zero byte.
448    #[error("reserved field '{field}' must be 0x00")]
449    ReservedBytesNonZero {
450        /// Human-readable name of the reserved field.
451        field: &'static str,
452    },
453
454    /// `buffer_count` is `0` (minimum is `1`).
455    #[error("buffer_count is 0; at least one buffer handle is required")]
456    EmptyBufferTable,
457
458    /// `HAS_EXTENSION_TYPE` flag ↔ `type_tag` range disagree.
459    #[error(
460        "HAS_EXTENSION_TYPE flag is {flag_set} but type_tag 0x{type_tag:02X} is {type_tag_in_range}"
461    )]
462    ExtensionTypeFlagMismatch {
463        /// Whether the `HAS_EXTENSION_TYPE` flag was set.
464        flag_set: bool,
465        /// The `type_tag` byte found on the wire.
466        type_tag: u8,
467        /// Human-readable range description.
468        type_tag_in_range: &'static str,
469    },
470
471    /// Extension type `bit_width` / `packing_factor` is invalid.
472    #[error(
473        "extension type packing invalid: bit_width={bit_width}, packing_factor={packing_factor}"
474    )]
475    ExtensionTypePackingInvalid {
476        /// The `bit_width` value from the extension type descriptor.
477        bit_width: u32,
478        /// The `packing_factor` value from the extension type descriptor.
479        packing_factor: u8,
480    },
481
482    /// An extension type field carries a value the spec forbids for its family.
483    #[error("extension type field invalid: {reason}")]
484    ExtensionTypeFieldInvalid {
485        /// Which rule was violated, in the spec's own terms.
486        reason: &'static str,
487    },
488
489    /// `shard_offset[k] + shape[k] > parent_shape[k]`.
490    #[error("shard out of bounds on dim {dim}: offset {offset} + size {size} > parent {parent}")]
491    ShardOutOfBounds {
492        /// The dimension index where the constraint was violated.
493        dim: usize,
494        /// The `shard_offset` value for that dimension.
495        offset: u64,
496        /// The tensor `shape` value for that dimension.
497        size: u64,
498        /// The `parent_shape` value for that dimension.
499        parent: u64,
500    },
501
502    /// `computed_mask` has reserved bits set (bits ≥ 6).
503    #[error("statistics computed_mask 0x{mask:08X} has reserved bits set")]
504    StatisticsReservedMaskBitsSet {
505        /// The full `computed_mask` value found on the wire.
506        mask: u32,
507    },
508
509    /// `descriptor_length` declared in the header does not match the actual encoded length.
510    #[error("descriptor_length mismatch: declared {declared}, actual {actual}")]
511    DescriptorLengthMismatch {
512        /// The `descriptor_length` value declared in the header.
513        declared: u32,
514        /// The actual number of bytes consumed when parsing.
515        actual: usize,
516    },
517
518    // ── Composite / Virtual (ADR-027, Layer 4) errors ─────────────────────────
519    /// A composite head (`layout_tag = 0x0B`) declared a non-empty buffer table.
520    #[error(
521        "composite head has {count} buffer(s); a composite head (layout_tag 0x0B) MUST have buffer_count = 0"
522    )]
523    CompositeHeadHasBuffers {
524        /// The (non-zero) buffer count found on the wire.
525        count: u8,
526    },
527
528    /// A composite head declared a non-zero `byte_offset`.
529    #[error(
530        "composite head has byte_offset {byte_offset}; a composite head MUST have byte_offset = 0"
531    )]
532    CompositeHeadHasByteOffset {
533        /// The offending `byte_offset` value.
534        byte_offset: u64,
535    },
536
537    /// A composite head set the `HAS_QUANTIZATION` flag, but a virtual head owns no stored data.
538    #[error("composite head MUST NOT set HAS_QUANTIZATION (a virtual head owns no stored data)")]
539    CompositeHeadHasQuantization,
540
541    /// The `composition_rule` byte `0xFF` is permanently invalid.
542    #[error("invalid composition rule: 0x{0:02X} is permanently reserved")]
543    InvalidCompositionRule(u8),
544
545    /// The `composition_rule` byte falls in the range reserved for future specification
546    /// versions (`0x00` and `0x04`–`0xEF`).
547    #[error("reserved composition rule: 0x{0:02X} is reserved for future specification versions")]
548    ReservedCompositionRule(u8),
549
550    /// The `composition_rule` byte is in the implementation-private range `0xF0`–`0xFE`.
551    #[error(
552        "private composition rule: 0x{0:02X} is implementation-private and not interpretable by this crate"
553    )]
554    PrivateCompositionRule(u8),
555
556    /// The `combine_op` byte is not legal for the given `composition_rule`.
557    #[error("invalid combine_op 0x{combine_op:02X} for composition rule 0x{rule:02X}")]
558    InvalidCombineOp {
559        /// The wire `composition_rule` byte.
560        rule: u8,
561        /// The offending `combine_op` byte.
562        combine_op: u8,
563    },
564
565    /// The `member_count` sentinel `0xFFFFFFFF` (open composite) is RESERVED and not usable in v1.0.
566    #[error("member_count = 0xFFFFFFFF (open composite) is reserved and not usable in v1.0")]
567    OpenCompositeReserved,
568
569    /// The Composite Member section's `member_role` byte is not `0x00` or `0x01`.
570    #[error("invalid composite member_role: 0x{0:02X} (MUST be 0x00 correction or 0x01 base)")]
571    InvalidMemberRole(u8),
572
573    /// A member's `HAS_COMPOSITE_MEMBER` flag disagrees with what its composition rule requires.
574    ///
575    /// Overlay members MUST carry a Composite Member section; partition and group members
576    /// MUST NOT.
577    #[error(
578        "composite member HAS_COMPOSITE_MEMBER presence ({has_flag}) disagrees with composition rule 0x{rule:02X}"
579    )]
580    CompositeMemberFlagMismatch {
581        /// The wire `composition_rule` byte of the enclosing head.
582        rule: u8,
583        /// Whether the member actually carried a Composite Member section.
584        has_flag: bool,
585    },
586
587    /// The number of members actually supplied does not match the head's declared `member_count`.
588    #[error(
589        "composite member_count mismatch: head declared {declared}, but {actual} member(s) were supplied"
590    )]
591    CompositeMemberCountMismatch {
592        /// The `member_count` declared by the head.
593        declared: u32,
594        /// The number of members actually pushed to the validator.
595        actual: usize,
596    },
597
598    /// A partition or overlay member did not carry a shard section (`HAS_SHARD`).
599    #[error(
600        "composite member {index} is missing a shard section (partition/overlay members MUST carry HAS_SHARD)"
601    )]
602    CompositeMemberMissingShard {
603        /// The 0-based index of the offending member.
604        index: usize,
605    },
606
607    /// A member's shard `parent_shape` does not equal the composite head's `shape`.
608    #[error("composite member {index}: shard parent_shape does not equal the head's shape")]
609    CompositeMemberParentShapeMismatch {
610        /// The 0-based index of the offending member.
611        index: usize,
612    },
613
614    /// A member's decoded value type does not equal the composite head's `type_tag`.
615    #[error(
616        "composite member {index}: decoded type 0x{member:02X} does not equal head type_tag 0x{head:02X}"
617    )]
618    CompositeMemberTypeMismatch {
619        /// The 0-based index of the offending member.
620        index: usize,
621        /// The member's own `type_tag` byte.
622        member: u8,
623        /// The head's `type_tag` byte.
624        head: u8,
625    },
626
627    /// An overlay composite's first member is not the base, or a base member appeared at a
628    /// position other than first.
629    #[error(
630        "overlay composite: the base member (member_role = 0x01) MUST be first, and only the first member may be the base"
631    )]
632    CompositeOverlayBaseNotFirst,
633
634    /// An overlay composite's base member does not span the whole index space.
635    #[error(
636        "overlay composite: the base member MUST span the whole index space (zero shard_offset, shape == head shape)"
637    )]
638    CompositeOverlayBaseNotSpanning,
639
640    /// An overlay composite head declared `member_count = 0` (no base member available).
641    #[error(
642        "overlay composite MUST have at least one member (the base); member_count = 0 is rejected"
643    )]
644    CompositeOverlayEmpty,
645
646    /// A partition composite's members leave a gap in the head's index space.
647    #[error(
648        "partition composite: member boxes do not exactly cover the head's index space (gap detected)"
649    )]
650    CompositePartitionGap,
651
652    /// Two partition members' boxes overlap.
653    #[error("partition composite: member {a} and member {b} boxes overlap")]
654    CompositePartitionOverlap {
655        /// The 0-based index of the first overlapping member.
656        a: usize,
657        /// The 0-based index of the second overlapping member.
658        b: usize,
659    },
660
661    /// A layout is virtual (owns no data buffer) and does not support element addressing.
662    ///
663    /// Currently only the composite head (`layout_tag = 0x0B`) is virtual; addressing a
664    /// composite requires resolving to a member first (see `hurray_core::composite`).
665    #[error("layout tag 0x{layout_tag:02X} is virtual (owns no data) and does not support element addressing")]
666    LayoutIsVirtual {
667        /// The virtual layout's wire tag.
668        layout_tag: u8,
669    },
670}
671
672/// Convenience alias for `Result` with [`Error`].
673pub type Result<T> = std::result::Result<T, Error>;