- Parse gate in BastField::parse: maxLength on any kind other than
string/bytes is a clean Schema error (records, arrays, inline
structs, refs, union shared fields all covered; the choke point
needs no ref-following since $defs entries are struct/union/enum)
- Meta-schema FieldDef: if kind in {string, bytes} else maxLength
forbidden — the published alk.dev/bast/v1 contract matches the
parser (N2 dual-layer pattern)
- M5's compute-side record maxLength arm became unreachable and was
deleted (offset-indirect arm stays); the two superseded M5
maxLength tests rewritten as the n3_* parse-rejection family
- ADR-006 remedy message tailored per kind: for records both
annotated remedies are dead ends, so the error text points at the
last-position fix only
- Docs aligned: bast-format.md (FieldDef meta-schema + FieldDef/
Variable-Length Encoding prose), layout-engine.md (Strategy 2 +
ADR-006 paragraph), schema-layer.md, ADR-003 §2/§3a amended,
builder .max_length() doc
- Review #006: N3 resolved (all findings now closed), M5 update
note, test-count bookkeeping note (in-session probes vs static
counts), status lines flipped to fully resolved
Verified: 547 tests green + 2 ignored doctests in BOTH release and
default profiles (a stale debug artifact from an earlier session
masked one H3 roundtrip test in debug; clean rebuild passes both),
clippy -D warnings clean, cargo doc --no-deps zero warnings, wasm
build green.
align: 2^62 compiled and reported total_size = 2^63 — meaningless
layout output the consumer may act on, and the reachable path to the
MAX_ARRAY_BYTES cap used exactly this knob.
- MAX_ALIGN = 4096 (page granularity) in schema.rs, documented with the
probe arithmetic
- parse_align returns Result and rejects over-cap values with a clean
Schema error naming path/value/maximum — a silent clamp was rejected
(it would change layout semantics without telling the consumer);
both call sites thread the path, so standalone BastDoc::new (which
never runs the meta-schema) is covered
- Meta-schema: "maximum": 4096 on StructDef.align and FieldDef.align —
the published alk.dev/bast/v1/schema contract now matches the parser
- H1's byte-cap test retuned to align 4096 x count 2^16 = 2^28 > 2^26
(the byte cap stays reachable under the new align cap)
Tests: 3 new (struct align above cap, field align above cap, align at
cap accepted). 511 tests green, clippy -D warnings clean, wasm32 build
green, cargo doc zero warnings.
Decision (recorded as an ADR-011 addendum): the packed-mode wire layout
for a field-name-discriminator TUnion is shared-then-variant — the
union's declared `fields` (disc + shared fields) first, then the
variant's own fields. Reader and materializer already implemented this;
LayoutBuilder was corrected from variant-only layout.
Enforcement in BastUnion::parse (the choke point every consumer
inherits — union roots at BastDoc::new, referenced unions at
resolve_ref):
- discriminator field must be declared in `fields`
- `fields` must not contain duplicate names
- variants must not re-declare shared fields (checked inline and
through $ref resolution — parse chain now threads the doc root)
- the discriminator field must be the FIRST entry in `fields` (the
reader reads the disc at the union start; a later position made it
dispatch on the wrong bytes — H3 item 2, probe-verified)
Schemas relying on the old variant-only builder convention (variants
re-declaring shared fields) are rejected with a clean Schema error
naming the convention. Breaking for 0.2.0-era re-declaring schemas;
announced with 0.3.x.
- L5: FieldValue::Union::variant_start doc now states per-kind
semantics (byte-disc: union_start + disc.offset + disc.size;
field-disc: after the shared walk).
- L6: roundtrip test added (poc_roundtrip.rs) — LayoutBuilder write →
SequentialReader read → materialize_packed → validate_bytes over a
field-disc union with a second shared field and non-redeclaring
variant; pins event.type@0/seq@1/handle@5, total 10.
- ADR-011: Status-block addendum recording the convention decision,
the no-re-declare rule, and the breaking-constraint note.
- Review #006 updated: H3/L5/L6 resolution blocks, resolution log,
recommended order.
Verified: 488 tests green (410+17+34+15+12, 2 pre-existing ignored),
clippy -D warnings clean, wasm32 build green, cargo doc zero warnings.
- Replace the three Vec::with_capacity(count) sites in materialize.rs
with Vec::new() — validate_bytes on an adversarial count no longer
OOM-aborts the process (AGENTS.md §3).
- New compile-time caps in schema.rs: MAX_ARRAY_ELEMENTS (2^16,
enforced at BastArray::parse — the choke point every consumer
inherits, bounds the walkers' per-element entry loops) and
MAX_ARRAY_BYTES (2^26, enforced per walker against the mode-specific
stride: compile_array, walk_array, compute_array_field).
- L1: fixed_composite_size/fixed_plan_size now return
Result<Option<usize>>; unwrap_or_default() gone, overflow is a clean
Schema error instead of silent stride-0.
- Zero-progress guard: stride-0 arrays whose elements consume 0 bytes
(legal empty-struct elements) now error in plan_walk_variable_array_
size and materialize_array_packed instead of looping count times.
- Tests: 8 new (parse/build/compile rejections, cap boundary,
short-buffer clean error) + array_count_large_u64_parses_on_64bit
rewritten to assert the new cap rejection. In-tree tests assert only
the safe (compile-time) half per review #006's Methodology warning.
- Review #006 updated: H1/L1 resolution blocks, new finding N2
(unbounded align annotations, found while re-deriving the cap
arithmetic), resolution log, recommended order.
Verified: 482 tests green (405+17+34+14+12, 2 pre-existing ignored),
clippy -D warnings clean, wasm32-unknown-unknown build green.
Every Bast* type drops <'a>: &'a str -> String, &'a Value -> Value
(deferred decision 1: plain String/Value — the tree is built once;
Arc<str> name-sharing needs a bench justification that doesn't exist).
BastDoc::new(&Value, &str) still takes references in and clones into
owned storage; the doc gains Clone. resolve_ref/resolve_typeref/
resolve_typeref_as_def return owned types.
- Engine ownership flip: AlkTypeEngine holds the owned BastDoc
(replacing bast_doc: Value + root_name: String; root_name()
delegates to the doc), killing its three per-call BastDoc::new
re-parses (aligned validate_bytes, read_field, write_field — the
review #004 M1 pattern removed by construction; phase 5 retires the
lookup_leaf_field walk itself). New public accessor root_name()
(additive). Engine Send + Sync with the owned doc, asserted in the
existing thread-share test.
- Bonus cleanup: materialize_typeref_packed's dead _field param
dropped (phase 2 left it dangling). Under ownership, keeping it
would force a deep Value clone per array element / record value /
union variant via dummy_field_for. The param, dummy_field_for, and
ty_source are gone; no behavior change (the arg was already
ignored). BastField::synthetic keeps an owned-signature
#[allow(dead_code)] definition (no remaining callers today).
- Consumers adapted: OffsetMap::compute(&BastDoc),
materialize_aligned(&BastDoc, ...) (no lifetime), BuildCtx/
ComputeCtx hold &'d BastDoc, tunion/discriminator name borrows,
lib.rs module doc. LayoutBuilder's doc_value re-parse cache is
unchanged pending phase 4.
Verification: 465 tests pass with zero test-logic changes (bast.rs
suites exercise every parser path through the public API); clippy
-D warnings clean; cargo doc zero warnings; wasm32 release build
green.
The Timestamp kind was a residual from an early research reference. It
was byte-identical to String everywhere (length-prefixed UTF-8) and its
only distinguishing behavior was a hand-rolled non-strict RFC 3339 check
that the docs admitted was incomplete (Feb 31 passes, seconds range
unchecked, no leap seconds). JSON-level timestamp validation is
jsonschema's job (format: date-time on the validate_json path), not
alktype's.
Removes the AlkTypeKind::Timestamp variant, its to_bast_str/from_bast_str
mapping, the builder's Schema::timestamp() constructor, the
validate_timestamp/is_rfc3339_timestamp validator arms, and the
materializer/reader/engine timestamp arms. Updates the meta-schema
primitive enum (14 -> 13), the spec docs (bast-format.md, schema-layer.md,
builder.md, validation.md, overview.md, data-access.md, README.md), and
the kind-count references (19 -> 18).
Verification: cargo test --release (407 pass), cargo clippy --all-targets
-- -D warnings (clean), cargo doc --no-deps (clean), cargo build --target
wasm32-unknown-unknown --release (clean).
Resolves review #003 finding M3.
- Add bast_meta::validate_bast_doc and call it from AlkTypeEngine::compile
before parsing. Malformed annotations (unknown endian/encoding strings,
non-integer align/maxLength, missing required properties) now surface as
AlkTypeError::Schema instead of being silently tolerated by the parser.
- Align the meta-schema TypeRef with the parser: allow inline struct/union/
enum as TypeRefs (the parser and builder already accepted them; the
meta-schema and spec did not). Update bast-format.md TypeRef table and
the BastType doc comment to the seven-form vocabulary.
Verification: cargo test --release (410 pass), cargo clippy --all-targets
-- -D warnings (clean), cargo doc --no-deps (clean), cargo build --target
wasm32-unknown-unknown --release (clean).
Step 4 of the BAST pivot: the layout engines, materializer, tunion
dispatch, and engine now consume the BAST typed tree (BastDoc/
BastStruct/BastField/BastType/...) instead of walking raw JSON with
get_alktype_kind*.
Breaking changes (per the pivot plan's semver contract):
- AlkTypeEngine::compile signature:
compile(schema: &mut Value, mode)
-> compile(bast_doc: &Value, root_name: &str, mode)
Drops &mut (BAST needs no in-place normalize_refs); adds required
root_name (D-BAST-001); input is a BAST document, not a custom-keyword
JSON Schema.
- OffsetMap::compute, LayoutBuilder::new, SequentialReader::new now take
a BAST document (&Value) + root_name (or &BastDoc) instead of a
v0.1.0 schema.
- tunion::read_byte_discriminator / read_field_discriminator /
resolve_variant / discriminator_size now take &BastUnion instead of
&Value.
- materialize::materialize_packed / materialize_aligned now take
&BastDoc instead of &Value.
Key design points:
- The engine stores a clone of the BAST Value + root_name so
sequential_reader() and read_field() can re-parse the typed tree on
demand without lifetime entanglement with the caller's Value.
- resolution is a single hash lookup via BastDoc::resolve_typeref;
no normalize_refs, no inline_union_variant_refs.
- bast.rs gains BastField::synthetic() (pub(crate)) for constructing
synthetic fields wrapping TypeRefs (array elements, record values,
union variants — these aren't fields and carry no field annotations).
- The v0.1.0 schema.rs helpers and validation.rs custom-keyword
validators remain defined (step 8 removes them). build_validator still
runs on the BAST doc — with no AlkType:* keywords present, the custom
factories don't trigger and jsonschema performs structural validation
only. The validate_bytes value-constraint enforcement (maxLength, enum
bounds) is step 5's concern (the BAST-native validator).
Tests:
- All engine, layout, materialize, tunion, and integration tests
converted to BAST format (kind/fields vocabulary, /
composition). Expected validation outcomes for the layout path are
identical; the maxLength/enum-bounds validate_bytes tests are step 5's
regression target.
- builder.rs::builder_chunk_header_compiles_in_packed_mode uses a
hand-written BAST doc (the builder still emits v0.1.0 format; step 7
converts it).
Verification:
- cargo test --release: 425 pass (348 lib + 77 integration)
- cargo clippy --all-targets -- -D warnings: clean
- cargo build --target wasm32-unknown-unknown --release: clean
- cargo doc --no-deps: clean
- New src/bast.rs: typed surface over a BAST document — BastDoc,
BastDef, BastDefKind, BastStruct, BastField, BastUnion,
BastDiscriminator, BastEnum, BastType, BastRef, BastArray,
BastRecord. Borrows from the source Value (no clone of the tree).
- $ref resolution is a single hash lookup against $defs
(#/$defs/<name> only); union variant refs resolved lazily via
BastDoc::resolve_typeref / resolve_ref — replaces
normalize_refs + inline_union_variant_refs (those stay for now;
step 8 removes them).
- Untrusted-input safe: every walk returns AlkTypeError::Schema on
a malformed document, never panic/unwrap (AGENTS.md §3).
Overflow-safe usize parsing via try_from (AGENTS.md §4).
- D-BAST-005 enforced: fields array only valid with field-name
discriminators; required for them.
- Additive only: new pub mod bast + re-exports in lib.rs. No
existing re-exports removed (those go in step 8). 47 new tests.
Verification:
- cargo test --release: 465 pass (379 lib + 86 integration)
- cargo clippy --all-targets -- -D warnings: clean
- cargo doc --no-deps: clean
- cargo build --target wasm32-unknown-unknown --release: clean