Step 9 (convert tests to BAST format) was a no-op: steps 4-8 converted
the tests as they went. The only remaining reference in
src/tests was the intentional rejection test at
src/schema.rs:462 (asserting the old keyword form is rejected). Full
suite passes: 389 tests (312 lib + 77 integration).
Step 10 (sync architecture docs and ADRs):
Descriptive docs rewritten/updated for BAST:
- schema-layer.md: rewritten for the BAST parser (BastDoc/BastDef/
BastType typed tree, AlkTypeKind enum with to_bast_str/from_bast_str,
what was removed). Points at bast-format.md for the normative format.
- validation.md: rewritten for the two-validator model
(bast_validation for validate_bytes, standard jsonschema for
validate_json). Documents the repurposed build_validator, the
AlkTypeError::Validation uniform payload (D-BAST-009), and what is
removed.
- builder.md: updated all output examples to BAST JSON
(struct_() -> { kind: struct, fields: [...] }; object() -> standard
JSON Schema). Documents build_doc, count(), and the field-name union
fields requirement (D-BAST-005).
- overview.md: updated for BAST (what/why, schema-is-the-format table,
dependencies, architecture pointers, design decisions table).
- README.md (architecture index): updated document table, ADR table
(new ADR-BAST + ADR-VAL-SPLIT, superseded ADR-001), OQ table
(OQ-007/OQ-008 resolutions updated for BAST-native validator), and
key design principles (#1, #2, #7, #10 reworded for BAST).
- data-access.md: updated tunion function signatures to BastUnion and
the variant resolution to return BastType (resolve_typeref for refs).
- layout-engine.md: updated construct signatures
(LayoutBuilder::new(bast_doc, root_name), OffsetMap::compute(&doc),
SequentialReader::new(bast_doc, root_name)), the recursive-walk
description (BAST typed tree), and composite-kind headings
(TStruct/TUnion/TArray -> struct/union/array). Added D-BAST-004
note on array count requirement.
New ADRs:
- ADR-BAST (bast-bast-format.md): the BAST format, meta-schema,
//kind vocabulary, design principles, what is removed, the
enum index bounds bug fix. Supersedes ADR-001's format-specific
content; records D-BAST-001..009.
- ADR-VAL-SPLIT (val-split-two-validator-model.md): the two-validator
model (BAST-native for validate_bytes, standard jsonschema for
validate_json), the repurposed build_validator, the uniform
AlkTypeError::Validation payload. Refines ADR-004's validation
strategy and ADR-010's validation step; records D-BAST-006/007/009.
Amended ADRs (supersession/amendment notes added; original decision
text preserved as historical record):
- ADR-001: format-specific content superseded by ADR-BAST;
purpose/scope and schema-is-the-format principle retained.
- ADR-002: unchanged under the pivot; one-line note that the input
format changed but the modes didn't.
- ADR-003: annotation semantics retained; annotation location moved
to BAST type-level properties (amended by ADR-BAST).
- ADR-004: AlkTypeError enum retained (D-BAST-009); validation
strategy section refined by ADR-VAL-SPLIT.
- ADR-009: builder API surface retained; build() output format
amended to BAST / standard JSON Schema by ADR-BAST (D-BAST-008).
- ADR-010: validate_bytes two-step concept retained; validation step
amended to the BAST-native validator by ADR-VAL-SPLIT.
Other:
- Cargo.toml description: JSON Schema with AlkType:* custom keywords
-> BAST document.
- bast-pivot.md research record: status draft -> implemented, with a
pointer to the ADRs that superseded its decisions.
- bast-implementation.md plan: status draft -> complete, with a note
that step 9 was a no-op and step 10 is this commit.
- open-questions.md: OQ-006/OQ-007/OQ-008 resolutions updated for the
BAST-native validator.
- questions/008-unionvalidator-variant-dispatch.md: added a
post-BAST-pivot note pointing to the current bast_validation
implementation; v0.1.0 resolution text preserved as historical
record.
Verification:
- cargo test --release: 389 pass (312 lib + 77 integration)
- cargo clippy --all-targets -- -D warnings: clean
- cargo doc --no-deps: clean
- cross-reference check: every relative link in the new/updated docs
resolves (verified by script).
14 KiB
status, last_updated
| status | last_updated |
|---|---|
| accepted | 2026-08-15 |
alktype — Schema Layer
The schema layer: the BAST (Binary Abstract Syntax Tree) format and the
typed parser that the layout engines, materializer, and BAST-native
validator walk. BAST replaces the v0.1.0 AlkType:* custom-keyword JSON
Schema format decided in ADR-001; the pivot is recorded in
ADR-BAST and grounded in
D-BAST-001..009.
The normative format specification is
bast-format.md (meta-schema, TypeRef, examples,
validation model). This document describes the implementation — the
typed parser in src/bast.rs and the foundational AlkTypeKind enum
in src/schema.rs — and points at the format spec for shape details.
The 19 BAST Kinds
BAST uses lowercase kind strings ("uint32", "struct", "union",
etc.). The engine represents them as the AlkTypeKind Rust enum — one
variant per kind — providing compile-time exhaustiveness checking and
integer-discriminant dispatch (a jump table) instead of string
comparison at every field access.
| BAST kind | AlkTypeKind |
Rust type | Size | Category |
|---|---|---|---|---|
int8 |
Int8 |
i8 |
1 | fixed |
int16 |
Int16 |
i16 |
2 | fixed |
int32 |
Int32 |
i32 |
4 | fixed |
int64 |
Int64 |
i64 |
8 | fixed |
uint8 |
Uint8 |
u8 |
1 | fixed |
uint16 |
Uint16 |
u16 |
2 | fixed |
uint32 |
Uint32 |
u32 |
4 | fixed |
uint64 |
Uint64 |
u64 |
8 | fixed |
float32 |
Float32 |
f32 |
4 | fixed |
float64 |
Float64 |
f64 |
8 | fixed |
bool |
Boolean |
bool (0x00=false, 0x01=true) |
1 | fixed |
string |
String |
length-prefixed UTF-8 | variable | variable |
bytes |
Bytes |
length-prefixed raw bytes | variable | variable |
timestamp |
Timestamp |
length-prefixed RFC 3339 string | variable | variable |
struct |
Struct |
record of fields | sum of field sizes | composite |
union |
Union |
tagged union | discriminator + variant | composite |
array |
Array |
repeated element | count × element size | composite |
enum |
Enum |
u32 index into enum values | 4 (fixed) | fixed |
record |
Record |
count-prefixed (key, value) pairs | variable | variable |
int64/uint64 are alktype additions — TypeBox's typedef.ts tops
out at 32-bit integers. Required by SFTP Read/Write offset: u64
and metatensor data_offsets. See
ADR-005.
The AlkTypeKind enum
src/schema.rs defines the enum:
pub enum AlkTypeKind {
Int8, Int16, Int32, Int64,
Uint8, Uint16, Uint32, Uint64,
Float32, Float64,
Boolean, Enum,
String, Bytes, Timestamp,
Struct, Union, Array, Record,
}
The enum carries the kind's binary-layout metadata as inherent methods:
| Method | Returns | Notes |
|---|---|---|
to_bast_str(self) |
&'static str |
The lowercase BAST kind string ("uint32") |
from_bast_str(s) |
Result<AlkTypeKind, AlkTypeError> |
Parses a lowercase BAST kind string; AlkTypeError::Schema for unknowns |
type_size(self) |
Option<usize> |
Some(N) for fixed-size kinds; None for variable/composite |
natural_alignment(self) |
usize |
1 for u8/i8/bool, 2 for u16/i16, 4 for u32/i32/f32/enum, 8 for u64/i64/f64, 4 for variable-length (the u32 length prefix), 1 for struct/union/array |
is_fixed_size(self) |
bool |
True for the 12 fixed-size primitive kinds |
is_composite(self) |
bool |
True for Struct, Union, Array, Record |
is_variable_length(self) |
bool |
True for String, Bytes, Timestamp, Record |
needs_endian(self) |
bool |
True for kinds whose read/write takes an Endian parameter |
AlkTypeKind implements Display, backed by to_bast_str so the
layout engines, materializer, validator, and parser surface the
canonical BAST name in error messages. from_bast_str is the inverse
and is the dispatch point the BAST parser uses to map a kind string
to the enum variant (D-BAST-002).
Foundational annotation types
src/schema.rs also defines the two annotation enums (semantics
unchanged from ADR-003; only their location in the document moved —
see ADR-BAST and
bast-format.md §Variable-Length Encoding):
pub enum Endian { Little, Big }
pub enum VariableEncoding { LengthPrefixed, OffsetIndirect }
The Discriminator builder enum lives in
src/builder.rs (the builder's domain); the BAST
parser's typed discriminator view is
BastDiscriminator.
The BAST Parser (bast module)
src/bast.rs is the typed surface over a BAST document. Three
consumers walk the same tree — the layout engines
(offset_map, layout_builder,
sequential_reader), the
materialize layer, and the
bast_validation validator — so a typed view pays for
itself: each walks matched arms over BastType instead of re-parsing
raw JSON at every node. Borrowing (not cloning) the source
serde_json::Value keeps the parse allocation-free beyond the small
typed nodes themselves.
Document shape
Every BAST document has the same top-level shape:
{ "$defs": { "<TypeName>": { ...TypeDef... }, ... } }
- The
$defsblock is required (D-BAST-003). Single-type documents are a special case with one entry. - The root type name is a required parameter to
AlkTypeEngine::compile(bast_doc, root_name, mode, ...)(D-BAST-001). It selects which$defsentry is the top-level type; convention (first entry) is fragile and depends on JSON key order, so an explicit parameter is used instead.
See bast-format.md for the normative TypeDef shapes
(Struct, Union, Enum, FieldDef, TypeRef) and the meta-schema.
Typed tree
The parser produces a borrowed typed tree:
| Type | Role |
|---|---|
BastDoc<'a> |
The parsed document: the root Value, the chosen root name, and the parsed root BastDef. Entry point via BastDoc::new(root, root_name). |
BastDef<'a> |
A named $defs entry — { name, kind: BastDefKind, source }. Only struct/union/enum can live at the top level. |
BastDefKind<'a> |
Struct(BastStruct) / Union(BastUnion) / Enum(BastEnum). |
BastStruct<'a> |
{ endian, align, fields: Vec<BastField>, source }. Field order is the fields array order (BAST design principle #4 — no reliance on serde_json's preserve_order). |
BastField<'a> |
{ name, ty: BastType, endian, align, encoding, max_length, source }. Annotations are field-level properties (ADR-003 semantics, BAST location). |
BastUnion<'a> |
{ endian, discriminator, fields, mapping: Vec<(key, BastType)>, source }. Variant $refs resolve lazily — no compile-time inlining. |
BastDiscriminator<'a> |
Byte { offset, disc_type } / Field { name }. The typed view of the discriminator object. |
BastEnum<'a> |
{ values: Vec<&'a str>, source }. Non-empty (enforced). |
BastType<'a> |
A TypeRef — Primitive(AlkTypeKind) / Ref(BastRef) / Array(BastArray) / Record(BastRecord) / Struct(...) / Union(...) / Enum(...). The central mechanism for typing fields, array elements, record values, and union variants. |
BastRef<'a> |
A $ref restricted to #/$defs/<name>. Carries just the name. |
BastArray<'a> |
{ element: Box<BastType>, count, source }. count is required in v1 (D-BAST-004). |
BastRecord<'a> |
{ values: Box<BastType>, source }. |
All of these are re-exported from the crate root (pub use bast::{...}
in src/lib.rs).
$ref resolution
BAST $refs are always full JSON Pointers restricted to
#/$defs/<name> — no external references, no fragment-only pointers,
no bare names (rejected by the parser). The restriction keeps
resolution a single hash lookup and eliminates the v0.1.0
normalize_refs pass that rewrote TypeBox's bare-name refs.
BastDoc exposes three resolution helpers:
| Method | Purpose |
|---|---|
lookup_def(name) -> Result<&'a Value, AlkTypeError> |
The single hash lookup into $defs. |
resolve_ref(r: &BastRef) -> Result<BastDef, AlkTypeError> |
Resolve a BastRef to its BastDef. |
resolve_typeref(ty: &BastType) -> Result<BastType, AlkTypeError> |
Deref one $ref level, or return the inline type unchanged. The composite-walkers call this. |
resolve_typeref_as_def(ty, path) -> Result<BastDef, AlkTypeError> |
Resolve a BastType to a BastDef, wrapping inline composites in a synthetic def. Convenient for the validator/materializer. |
Variant $refs (union mapping entries) are resolved lazily by
the materializer and validator via these helpers — no
inline_union_variant_refs compile step (removed under BAST). The
parser only records the BastRef target name.
Untrusted input
Every path that walks a BAST document returns
Err(AlkTypeError::Schema) on a malformed document, never
panic!/unreachable!/unwrap (AGENTS.md §3 — the downstream
alkcall consumer accepts schemas from arbitrary internet peers in its
hub/spoke topology). Overflow-safe arithmetic (checked_add,
usize::try_from) is used for any offset/count cast (AGENTS.md §4).
A malformed document (missing $defs, missing kind, unknown kind
string, dangling $ref, empty mapping, non-struct/union/enum at the
top level, a field-name union without a fields array, etc.) surfaces
as AlkTypeError::Schema with a path-annotated message.
What the parser does not do
- No meta-schema validation.
BastDoc::newparses structurally (every reachable def parses to a typedBastDef) but does not run the BAST meta-schema. Consumers that want full structural validation can run the meta-schema viajsonschemadirectly (BAST_META_SCHEMAis re-exported from the crate root). The parser's structural checks catch the cases that matter for layout/materialize/validate; the meta-schema is the authoritative well-formedness check. - No eager full-document parse. Only the root definition and the
definitions it (transitively) references are parsed eagerly; orphan
$defsentries are not checked. Lazy$refresolution reaches the rest at access time. - No annotation interpretation. The parser records
endian/align/encoding/maxLengthonBastField/BastStruct; the layout engines and validator interpret them (ADR-003 semantics).
Schema Annotations
Annotation semantics carry forward unchanged from ADR-003; only their
location moved from v0.1.0's custom-keyword objects to BAST
type-level properties. The concrete BAST shapes are in
bast-format.md:
- Endianness — struct/union-level
endianwith field-level override. - Alignment — struct/field-level
align(aligned mode only). - Variable-length encoding —
field-level
encodingandmaxLength. - Union discriminators —
discriminatorobject on the union def (byteorfield).
The maxLength keyword is not a BAST invention — it is the standard
JSON Schema maxLength, repurposed as a byte-length cap. In aligned
mode it reserves a fixed-size slot; in packed mode it is a validation
constraint only. See bast-format.md §Variable-Length
Encoding and
ADR-003.
What Was Removed
The v0.1.0 custom-keyword accessor layer was removed in step 8 of the
BAST pivot. The schema module retains only the foundational types
(AlkTypeKind, Endian, VariableEncoding, shared constants); the
BAST parser is the typed surface every engine module walks. Removed:
get_alktype_kind/get_alktype_kind_enum/get_alktype_kind_loose/get_alktype_kind_loose_enum— superseded by the parser'skind-string dispatch.normalize_refs/inline_union_variant_refs(+ recursive helpers) — BAST refs are always#/$defs/<name>; one hash lookup, variant refs resolve lazily.parse_encoding/parse_align/parse_max_length/parse_endian—bast.rshas its own BAST-property-form copies (internal to the parser).parse_discriminator+DiscriminatorKind— replaced bybast::BastDiscriminator; the builder has its ownDiscriminatorenum.resolve_ref/resolve_ref_or_inline— replaced byBastDoc::lookup_def/resolve_typeref.FromStrimpl,as_str,Endian::from_schema,ALKTYPE_PREFIX,BYTE_DISCRIMINATOR_TYPES, and their unit tests.
See ADR-BAST §"What is removed" and
bast-format.md §What is removed.
Design Decisions
| Decision | ADR | Summary |
|---|---|---|
BAST format, meta-schema, $defs/$ref/kind vocabulary |
ADR-BAST | Supersedes ADR-001's format-specific content; records D-BAST-001..009 |
| Schema annotations | ADR-003 | Annotation semantics (carry forward unchanged; only location moves) |
| Int64/Uint64 kinds | ADR-005 | 64-bit integers as first-class kinds (required by SFTP offsets and metatensor data_offsets) |
| Purpose and scope | ADR-001 | Why jsonschema not a custom engine; "schema is the format" principle (format-specific content superseded by ADR-BAST) |
Open Questions
See open-questions.md for full details.
- OQ-001 (deferred(scope)): Arrays of variable-length-element
structs — BAST arrays require
countin v1 (D-BAST-004), aligning with this deferral.
References
bast-format.md— the normative BAST format specification (meta-schema, TypeRef, examples, validation model)- ADR-BAST — the BAST format decision
- ADR-003 — annotation semantics
- BAST pivot research record — motivation, POC scope and result, decisions D-BAST-001..009
- validation.md — the BAST-native validator and the
validate_jsonJSON-Schema path src/bast.rs— the parser implementationsrc/schema.rs— theAlkTypeKindenum and foundational annotation types