Files
glm-5.3-flash b10e980346 Release 0.4.0: fuzzing wave findings, MaxLengthReserved encoding, reservation read/write API
- 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
2026-09-30 12:51:56 +00:00

14 KiB
Raw Permalink Blame History

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

  • VariableEncoding gains MaxLengthReserved (finding W3-3): the aligned-mode maxLength reservation (ADR-003 strategy 2, the VARCHAR(N) pattern) is now recorded as its own encoding variant instead of masquerading as LengthPrefixed. Code matching on VariableEncoding exhaustively must add an arm; in the document form the strategy is still expressed via maxLength, never as an encoding value.
  • read_field/write_field fix for aligned maxLength reservations (finding W3-3, commit a0dd3d2): an aligned String/Bytes leaf with a declared maxLength is 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 declaring maxLength.
  • plan_read_array bounds check (finding W2-1, wave 2): a fixed-stride array whose declared window (count × stride) extends past the buffer now returns an Access error 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 the maxLength reservation 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 to fuzz/, 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 source serde_json::Value; all v0.2.0 lifetimes are gone. BastDoc::new parses the root eagerly; $refs resolve lazily.
  • OffsetMap::get / PackedLayout::get return &OffsetEntry (was Option<OffsetEntry> by value), backed by an O(log n) BTreeMap path→index (first-occurrence-wins for duplicate names).
  • SequentialReader::new takes the compiled plan; construct via AlkTypeEngine::sequential_reader() (packed mode only).
  • materialize_packed / materialize_aligned take 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 in fields — all enforced at parse with clean Schema errors. Schemas relying on 0.2.0's variant-only layout are rejected (they produced reader↔builder-disagreeing bytes).
  • maxLength is string/bytes-only — rejected at parse on every other kind (it was silently unenforced there).
  • Aligned-mode Record fields reject offset-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 and count × stride ≤ 2^26 bytes; align ≤ 4096; maxLength ≤ 2^26; cyclic $ref graphs and >128-deep nesting are rejected by every public walker (OffsetMap::compute, LayoutBuilder::new, materialize_aligned included), not just the engine.

Additions

  • ReadPlan (ADR-011) — the compiled packed-read plan, re-exported with CompositePlan/FieldPlan/ReadKind/DiscriminatorPlan. ReadPlan::compile is 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 compiled validate_bytes walker, with ValidNode/ValidVariant sub-types.
  • fingerprint() on ReadPlan/OffsetMap/ValidationPlan + Hash/Eq derives on the plan types (ADR-012 §1/§4) — plan identity for cache-keying across processes.
  • OffsetMap LeafMeta — each entry records whether it is fixed/length-prefixed/offset-indirect so read_field/write_field dispatch without re-walking the schema; OffsetEntry type re-exported.
  • SequentialReader::read_next_borrowed — zero-allocation variant of read_next (field name borrowed from the plan).
  • AlkTypeEngine::validate_bytes now runs the compiled ValidationPlan (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 $ref graphs 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-mode maxLength/offset-indirect on 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 (jsonschema default-features off, serde_json with preserve_order), no async, no unsafe, no feature flags.
  • Benches (benches/wire_vs_bast.rs): read/write chunk streams, an SFTP-shaped union packet stream, and validate_bytes per 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": [...] } } }. The kind-based vocabulary covers 18 binary kinds (integers, floats, bytes, string, struct, union, enum, array, etc.).
  • AlkTypeEngine::compile signature. 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 $defs entry is the top-level type (previously the root was implicit from the single top-level schema object).
  • Builder API output. Definitions/Schema/Discriminator now produce BAST JSON via Definitions::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 the validate_json path).
  • Validation split. v0.1.0 used a single jsonschema validator with 19 custom AlkType:* keywords for both bytes and JSON validation. 0.2.0 splits this into two independent paths:
    • validate_bytes uses a new BAST-native validator (bast_validation) — a recursive walker over the BAST type tree.
    • validate_json / is_valid_json use a standard jsonschema::Validator built from a consumer-provided JSON Schema (passed to compile as the json_schema parameter). No custom keywords; BAST is not involved — BAST describes bytes, not JSON shape. Both paths return AlkTypeError::Validation with a uniform jsonschema::ValidationError<'static> payload.
  • Removed. The v0.1.0 custom-keyword accessor layer (AlkTypeKind::FromStr, parse_*, resolve_ref*, DiscriminatorKind) is removed. The BAST parser (bast module) exposes a cleaner typed surface (BastDoc/BastDef/BastStruct/ BastField/BastType/etc.) that borrows from the source serde_json::Value without cloning field data.
  • Public module surface. New public modules: bast, bast_meta, bast_validation, builder, materialize. The schema module is retained but now holds only Endian/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 at https://alk.dev/bast/v1/schema. BAST documents are validated against it at compile time (AlkTypeEngine::compile calls validate_bast_doc before parsing).
  • materialize module. Materializes a serde_json::Value tree from a binary buffer by walking the BAST typed tree. Used by AlkTypeEngine::validate_bytes (ADR-010).
  • Builder for JSON Schemas. Schema::object() produces a standard JSON Schema object (for the validate_json path), complementing Schema::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_enum checks idx < values.len()).
  • Offset-indirect, field-level endian, and aligned materialization bugs found during review #003 are fixed.

Non-breaking improvements

  • $ref is restricted to #/$defs/<name> — one hash lookup, no normalize_refs pass (the v0.1.0 engine needed one).
  • Schemas remain untrusted input: every engine path that walks a BAST document returns Err on a malformed document, never panic!/ unreachable!. Overflow-safe arithmetic (checked_add, usize::try_from) on all offset/count casts.
  • Still two dependencies (jsonschema with default-features = false, serde_json with preserve_order), no async, no unsafe, no platform deps, no feature flags. Compiles to wasm32-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.