- VariableEncoding gains MaxLengthReserved (public enum, semver-major
for 0.x; breaks exhaustive matches) — W3-3 root fix
- read_field/write_field correctly treat aligned maxLength reservations
as raw zero-padded/NUL-trimmed windows (W3-3, a0dd3d2)
- plan_read_array bounds check for truncated fixed-stride arrays (W2-1)
- data_access::read_reservation{,_string}/write_reservation public API
- 0.4.0 changelog entry; fuzz Cargo.lock version sync
Verification: cargo test --release 573 passed/0 failed; clippy
--all-targets -D warnings clean; wasm32-unknown-unknown release build
clean; fuzz corpus replay 30/30; cargo doc --no-deps clean;
cargo publish --dry-run --allow-dirty (67 files, 1.3MiB) verified
14 KiB
Changelog
All notable changes to this crate are documented here. The format is based on Keep a Changelog, and this crate adheres to Semantic Versioning.
[0.4.0] - 2026-09-30
The fuzzing release. The fuzz/ workspace (docs/plans/fuzzing.md) put five libFuzzer targets on the engine — bast_compile, data_access, read_opseq, layout_build, validate_pair — with release-budget campaigns across all five. Two genuine engine bugs found and fixed same-day, plus one upstream pin (docs/plans/fuzzing.md §6, §5 campaign numbers).
Breaking changes
VariableEncodinggainsMaxLengthReserved(finding W3-3): the aligned-modemaxLengthreservation (ADR-003 strategy 2, theVARCHAR(N)pattern) is now recorded as its own encoding variant instead of masquerading asLengthPrefixed. Code matching onVariableEncodingexhaustively must add an arm; in the document form the strategy is still expressed viamaxLength, never as anencodingvalue.read_field/write_fieldfix for alignedmaxLengthreservations (finding W3-3, commita0dd3d2): an aligned String/Bytes leaf with a declaredmaxLengthis now read as a raw zero-padded, NUL-trimmed window and written zero-padded — previously the first four raw bytes of the reservation window were misparsed as a u32 length prefix, breaking the validate_bytes ⇒ read_field agreement for every aligned schema declaringmaxLength.plan_read_arraybounds check (finding W2-1, wave 2): a fixed-stride array whose declared window (count × stride) extends past the buffer now returns anAccesserror naming the array field instead of reporting success and deferring the failure to the next field read (or masking it entirely when the array was last).
Additions
data_access::read_reservation/read_reservation_string/write_reservation— the single source of truth for themaxLengthreservation leaf semantics, shared with the aligned materializer.fuzz/subtree — five libFuzzer targets, 257 committed seeds, the corpus-replay gate (cargo test --manifest-path fuzz/shared/Cargo.toml, AGENTS.md verification checklist), the detached campaign runner; nightly confined tofuzz/,fuzz/excluded from the package. All targets, seeds, and the replay gate are stable-toolchain safe.
0.3.0 - 2026-09-07
The compiled-forms release. The packed read path — the hot path for
stream parsing — is driven by a compile-once ReadPlan instead of a
per-call walk of the BAST typed tree; byte validation runs a compiled
ValidationPlan; plans and offset maps fingerprint to a stable hash
(ADR-011/ADR-012). Reads of SFTP-shaped packet streams went from
~189× hand-rolled Rust to ~74× (~2.4× faster), fixed-stride chunk
reads from ~18× to ~11×, and SequentialReader::read_next_borrowed
makes the per-field hot loop allocation-free.
Breaking changes
Bast*types are owned.BastDoc/BastStruct/BastField/… no longer borrow from the sourceserde_json::Value; all v0.2.0 lifetimes are gone.BastDoc::newparses the root eagerly;$refs resolve lazily.OffsetMap::get/PackedLayout::getreturn&OffsetEntry(wasOption<OffsetEntry>by value), backed by an O(log n)BTreeMappath→index (first-occurrence-wins for duplicate names).SequentialReader::newtakes the compiled plan; construct viaAlkTypeEngine::sequential_reader()(packed mode only).materialize_packed/materialize_alignedtake the compiled plan /(&BastDoc, &OffsetMap)pair respectively.- Field-name-discriminator union wire convention (ADR-011
addendum): the builder lays out the union's declared
fields(shared) first, then the variant's own fields. Variants must not re-declare the discriminator or any shared field, and the discriminator field must be the first entry infields— all enforced at parse with cleanSchemaerrors. Schemas relying on 0.2.0's variant-only layout are rejected (they produced reader↔builder-disagreeing bytes). maxLengthis string/bytes-only — rejected at parse on every other kind (it was silently unenforced there).- Aligned-mode
Recordfields rejectoffset-indirect(the materializer always walks the inline count-prefixed form — the annotated shape was never readable). - Schema input bounds (untrusted-schema hardening, AGENTS.md §3):
array
count≤ 2^16 andcount × stride≤ 2^26 bytes;align≤ 4096;maxLength≤ 2^26; cyclic$refgraphs and >128-deep nesting are rejected by every public walker (OffsetMap::compute,LayoutBuilder::new,materialize_alignedincluded), not just the engine.
Additions
ReadPlan(ADR-011) — the compiled packed-read plan, re-exported withCompositePlan/FieldPlan/ReadKind/DiscriminatorPlan.ReadPlan::compileis untrusted-input-safe standalone (depth cap + cycle set).fixed_size()exposes the compile-time-known byte size for fixed structs.ValidationPlan(ADR-012 §3) — the compiledvalidate_byteswalker, withValidNode/ValidVariantsub-types.fingerprint()onReadPlan/OffsetMap/ValidationPlan+Hash/Eqderives on the plan types (ADR-012 §1/§4) — plan identity for cache-keying across processes.OffsetMapLeafMeta— each entry records whether it is fixed/length-prefixed/offset-indirect soread_field/write_fielddispatch without re-walking the schema;OffsetEntrytype re-exported.SequentialReader::read_next_borrowed— zero-allocation variant ofread_next(field name borrowed from the plan).AlkTypeEngine::validate_bytesnow runs the compiledValidationPlan(was an interpretive BAST walk in 0.2.0).
Fixes (post-release-commit hardening — reviews #006, #007, #008)
All found and fixed before the first crates.io publish of 0.3.0, so no published version ever exhibited them.
- Untrusted-input crashes removed. A huge declared array count
OOM-aborted the process (
Vec::with_capacity(count)before reading a byte) — now compile-capped and walked with push-only growth. Cyclic$refgraphs stack-overflowed the three standalone layout walkers — now guarded by a shared reference-graph check. Deeply nested stride-0 arrays briefly allowed ~477 MB of simultaneous allocation from a ~1 KB schema — restored to incremental growth. - Cross-consumer divergences closed. Builder, reader,
materializer, tunion, and the validation plan now agree on
field-disc union layout (shared-then-variant), on the discriminator
field's position (must be first), and on union mapping-key matching
(numeric fast-path dispatch only for canonical keys like
"2";"01"/"+1"fall back to the string comparison all consumers share). The legacy BAST walker's field-disc union arm walks shared fields before the variant (it previously materialized variant fields from shared fields' bytes). - Silently-corrupt layouts rejected. Aligned record fields with
maxLength/offset-indirect; non-final inline length-prefixed fields (records included — the ADR-006 check now sees them); aligned-modemaxLength/offset-indirecton records; unions in aligned mode (pre-existing, now tested). - Coverage: 90.67% lines / 86.32% functions at review #007's audit, 91.66% after its fixes; every uncovered region outside test modules read and classified in-tree (docs/reviews/007).
Non-breaking improvements
- Engine compile is one-shot and allocation-tidy; plans are
Send + Sync(statically asserted) and fingerprintable. - Zero-progress array-element guard on all three array walkers (a zero-size element makes the declared count unbounded on the wire).
- WASM-clean unchanged: two dependencies (
jsonschemadefault-features off,serde_jsonwithpreserve_order), noasync, nounsafe, no feature flags. - Benches (
benches/wire_vs_bast.rs): read/write chunk streams, an SFTP-shaped union packet stream, andvalidate_bytesper buffer — the numbers quoted above and in ADR-007/ADR-011.
0.2.0 - 2026-08-17
A breaking release that replaces the v0.1.0 AlkType:* custom-keyword
JSON Schema format with BAST (Binary Abstract Syntax Tree) — a JSON
document that describes binary layouts using a kind-based vocabulary
with $defs/$ref for composition. BAST is itself a valid JSON Schema
instance (it has a meta-schema), making it self-validating,
editor-friendly, and trivially consumable from any language with a JSON
parser. The engine works the same way as before: compile a document
once into an AlkTypeEngine, then read/write fields at computed offsets
and validate bytes/JSON. The pivot was made now because v0.1.0 has no
real consumers (≈15 crates.io downloads, mostly bots/scanners), so the
custom-keyword wart could be removed cleanly.
Breaking changes
- Schema format. The v0.1.0
AlkType:*custom-keyword JSON Schema format ({ "AlkType:Struct": true, "fields": [...] }) is removed. Schemas are now BAST documents:{ "$defs": { "<TypeName>": { "kind": "struct", "fields": [...] } } }. Thekind-based vocabulary covers 18 binary kinds (integers, floats, bytes, string, struct, union, enum, array, etc.). AlkTypeEngine::compilesignature. Now takes(bast_doc: &Value, root_name: &str, mode: LayoutMode, json_schema: Option<&Value>). The root type name is a required parameter — it selects which$defsentry is the top-level type (previously the root was implicit from the single top-level schema object).- Builder API output.
Definitions/Schema/Discriminatornow produce BAST JSON viaDefinitions::build_doc(name, schema). The builder method names are unchanged; only the emitted JSON shape changed.Schema::struct_()produces a BAST struct;Schema::object()produces a standard JSON Schema (for thevalidate_jsonpath). - Validation split. v0.1.0 used a single
jsonschemavalidator with 19 customAlkType:*keywords for both bytes and JSON validation. 0.2.0 splits this into two independent paths:validate_bytesuses a new BAST-native validator (bast_validation) — a recursive walker over the BAST type tree.validate_json/is_valid_jsonuse a standardjsonschema::Validatorbuilt from a consumer-provided JSON Schema (passed tocompileas thejson_schemaparameter). No custom keywords; BAST is not involved — BAST describes bytes, not JSON shape. Both paths returnAlkTypeError::Validationwith a uniformjsonschema::ValidationError<'static>payload.
- Removed. The v0.1.0 custom-keyword accessor layer
(
AlkTypeKind::FromStr,parse_*,resolve_ref*,DiscriminatorKind) is removed. The BAST parser (bastmodule) exposes a cleaner typed surface (BastDoc/BastDef/BastStruct/BastField/BastType/etc.) that borrows from the sourceserde_json::Valuewithout cloning field data. - Public module surface. New public modules:
bast,bast_meta,bast_validation,builder,materialize. Theschemamodule is retained but now holds onlyEndian/AlkTypeKind/VariableEncoding(the binary-kind vocabulary); the v0.1.0 custom-keyword machinery is gone.
Additions
- BAST meta-schema. Embedded in the crate as
BAST_META_SCHEMA(re-exported from the crate root) and published athttps://alk.dev/bast/v1/schema. BAST documents are validated against it at compile time (AlkTypeEngine::compilecallsvalidate_bast_docbefore parsing). materializemodule. Materializes aserde_json::Valuetree from a binary buffer by walking the BAST typed tree. Used byAlkTypeEngine::validate_bytes(ADR-010).- Builder for JSON Schemas.
Schema::object()produces a standard JSON Schema object (for thevalidate_jsonpath), complementingSchema::struct_()which produces a BAST struct (for the bytes path). One builder, two output shapes — the method name selects which.
Bug fixes vs v0.1.0
- Enum index bounds are now checked. The v0.1.0 validator had a
dead constraint: enum variant indices were never bounds-checked
against
values.len(). The BAST-native validator enforces it (validate_enumchecksidx < values.len()). - Offset-indirect, field-level endian, and aligned materialization bugs found during review #003 are fixed.
Non-breaking improvements
$refis restricted to#/$defs/<name>— one hash lookup, nonormalize_refspass (the v0.1.0 engine needed one).- Schemas remain untrusted input: every engine path that walks a BAST
document returns
Erron a malformed document, neverpanic!/unreachable!. Overflow-safe arithmetic (checked_add,usize::try_from) on all offset/count casts. - Still two dependencies (
jsonschemawithdefault-features = false,serde_jsonwithpreserve_order), noasync, nounsafe, no platform deps, no feature flags. Compiles towasm32-unknown-unknown.
Upgrade notes
There is no migration path from v0.1.0 AlkType:* schemas — the format
is incompatible. Rewrite schemas as BAST documents (the builder API
produces them; see the README usage example) and update compile calls
to pass the root type name and the optional JSON Schema. The
read/write/validate API surface (read_field, write_field,
sequential_reader, validate_bytes, validate_json,
is_valid_json) is unchanged.
0.1.0 - 2025-11-10
Initial crates.io release. Custom-keyword JSON Schema format
(AlkType:*), single jsonschema validator for both bytes and JSON,
AlkTypeEngine with packed/aligned layout modes, builder API producing
serde_json::Value.