Review #008 (docs/reviews/008-pre-publish-review.md) audits the two post-#007 unreviewed commits (dea96f0bench port,d4635d2perf) before the first crates.io publish of 0.3.0. - F1: the int_keys integer dispatch accepted non-canonical mapping keys ("01", "+1" parse as u64 1) — the reader dispatched disc 1 while the materializer, validation plan, and tunion rejected the same buffer. compile_int_keys now builds the table only when every key is canonical (v.to_string() == key); otherwise the string fallback applies (agreement restored, perf kept for canonical mappings). Tests: r8_non_canonical_mapping_key_disables_int_dispatch, r8_canonical_mapping_keys_keep_int_dispatch. - F2:d4635d2reintroduced Vec::with_capacity(count) on both array materializers. Bounded per array by MAX_ARRAY_ELEMENTS but nesting compounds: probe (counting allocator) measured ~477 MB simultaneous allocation from a ~1 KB schema + empty buffer (100-level stride-0 chain, all legal under the caps). H1's layer-1 rule restored: Vec::new() + push. Bench unchanged (packet read 220µs vs 246µs baseline). Test: r8_deeply_nested_stride0_array_rejects_before_bulk_prealloc. - N3a disposition (review #007's deferred item): BastField::synthetic (pub(crate), zero callers, #[allow(dead_code)]) deleted; the seven source() accessors are public API and stay (semver decision — removal needs an explicit ask); resolve_typeref_as_def's inline struct/union/enum arms probe-verified reachable (inline struct union variants are legal) — kept. - CHANGELOG: 0.3.0 entry (compiled forms, breaking surface, hardening fixes, coverage). README: ReadPlan/ValidationPlan roles, union conventions, untrusted-schema bounds. Verification: 569 tests green (491 lib + 17 + 34 + 15 + 12, + 2 ignored doctests), clippy -D warnings clean, cargo doc 0 warnings, wasm32 build green, cargo publish --dry-run clean.
12 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.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.