Files
alknet/docs/architecture/decisions/102-reject-tunion-in-aligned-mode.md
T
glm-5.2 c819d99f1d docs(typedef): ADRs 099-102 — Int64/Uint64, aligned-mode restrictions, read factory
ADR-099: Int64/Uint64 as first-class kinds. The POC targets (SFTP
offset: u64, metatensor data_offsets: u64) require 64-bit integers.
The prior Uint64 addition was removed because it was half-finished;
this ADR specifies the complete addition (layout + validator + API).

ADR-100: Reject non-final inline length-prefixed variable fields in
aligned mode. The OffsetMap reserves only 4 bytes (the length prefix),
but write_string writes prefix+data inline — clobbering subsequent
fields. Non-final variable fields must use maxLength or offset-indirect.

ADR-101: Packed-mode read API — engine as SequentialReader factory.
engine.sequential_reader() returned &SequentialReader but read_next
needs &mut self — dead API. Now returns an owned fresh reader.

ADR-102: Reject TUnion in aligned mode for v1. The aligned-mode union
code had three bugs (no variant offsets, first-variant discriminator
offset, misaligned variant region). Unions are the protocol pattern;
mmap formats use structs/arrays. Reversible when a consumer needs it.
2026-07-22 09:36:55 +00:00

111 lines
4.6 KiB
Markdown

# ADR-102: Reject TUnion in Aligned Mode for v1
## Status
Accepted
## Context
The aligned static layout mode (ADR-096) computes fixed byte positions
for each field, enabling random access by field path. `TUnion` in
aligned mode has three implementation problems:
1. **No variant field offsets.** Only the `__discriminator` byte range
is recorded in the `OffsetMap`. Variant field offsets are not
available anywhere in aligned mode — the consumer must recompute
them by hand. This makes `TypedefEngine::read_field` on a union
variant field impossible.
2. **`find_discriminator_field` takes the first variant's offset.** For
a field-name discriminator, the code probes the first variant that
contains the discriminator field and records that offset globally.
If variants order fields differently, the discriminator sits at
different offsets per variant and the recorded range is silently
wrong. The code should validate that the offset is identical across
all variants (or require the discriminator field to be first).
3. **Byte-discriminator union total misaligns the variant.** The union
total is `disc_off + disc_size + variant_max_size`, but the variant
was probed from offset 0 with alignment. A `u8` discriminator
before a `u32`-bearing variant produces a variant region that
starts at an unaligned offset in a mode whose entire purpose is
alignment.
The real question is whether `TUnion` in aligned mode is even needed.
The two consumer profiles are:
- **Protocol consumers** (SFTP, call protocol event types): use packed
sequential mode. `TUnion` with byte-offset discriminators is the
core dispatch mechanism. This is well-supported.
- **mmap consumers** (metatensor, safetensors): use aligned static
mode. These formats are structs and arrays of structs — they don't
use tagged unions. A tensor file has a header struct with tensor
descriptors, not a "which variant is this?" dispatch.
`TUnion` in aligned mode is a combination that no current or planned
consumer needs. Shipping broken semantics for an unused use case is
worse than rejecting it clearly.
## Decision
**Reject `TUnion` in aligned static mode for v1.**
`OffsetMap::compute` returns `TypedefError::Offset` when it encounters
a `TypeDef:Union` field, with a message explaining that unions are not
supported in aligned mode and the consumer should use packed mode (or
restructure as a struct with an explicit discriminator field).
This is a schema-load-time rejection. The consumer learns about the
problem when compiling the schema, not at runtime.
### What is NOT rejected
- `TUnion` in packed sequential mode — this is the core use case
(SFTP `Packet` dispatch, call protocol event types) and is fully
supported by `LayoutBuilder` and `SequentialReader`.
- `TStruct`, `TArray`, and all primitive kinds in aligned mode — these
are the mmap-format primitives and are fully supported.
### Reversal
This is a two-way door. If a future mmap-format consumer needs tagged
unions in aligned mode, the rejection can be lifted and the three
implementation problems fixed. The fix would require:
- Recording per-variant field offsets in the `OffsetMap` (which
variant's offsets to record when variants have different layouts?).
- Validating that field-name discriminators have identical offsets
across all variants.
- Aligning the variant region correctly after the byte discriminator.
These are design questions that should be answered when the use case
arrives, not speculatively now.
## Consequences
### Positive
- **No broken semantics shipped.** The three implementation problems
are removed from the API surface rather than silently producing
wrong offsets.
- **Clear scope boundary.** Aligned mode is for structs and arrays;
packed mode is for protocols (including union dispatch). The
consumer chooses the mode based on the use case.
- **Reversible.** When a real consumer needs aligned-mode unions, the
rejection is lifted and the design questions are worked through with
a concrete use case.
### Negative
- **A schema with a `TUnion` field cannot be compiled in aligned
mode.** A consumer that wants both aligned layout and union dispatch
must use packed mode or restructure. No current consumer needs this.
- **The aligned-mode union code in `offset_map.rs` is dead.** It can
be removed or left as a reference for when the rejection is lifted.
Removing it is cleaner.
## References
- [ADR-096](096-two-layout-modes-packed-vs-aligned.md) — the two layout
modes
- [ADR-097](097-schema-annotations.md) §4 — TUnion discriminators
- `docs/architecture/crates/typedef/layout-engine.md` §"TUnion" —
aligned-mode union sizing