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.
SequentialReader now walks Arc<ReadPlan> instead of re-parsing the
BAST typed tree per field (the 400x read-path gap, review #004 H1);
materialize_packed walks the same plan, unifying the two packed
read-side consumers on one compiled form.
- SequentialReader::new(Arc<ReadPlan>) -> Self, infallible: the
fallible BastDoc parse moved to ReadPlan::compile (phase 1). The
reader holds the plan Arc + cursor state only; schema() returns the
Arc<Value> retained on the plan (review #005 H2 — no
self-referential struct); new plan() accessor exposes the shared
plan.
- ReadPlan carries schema: Arc<Value> (set at compile; sub-plans hold
a Null placeholder — only the root plan is handed out).
- materialize_packed(&ReadPlan, &[u8]): plan-walking packed
materializer. The aligned path keeps walking BastDoc with the
retained dummy_field_for/ty_source/materialize_typeref_packed
helpers (phase 5 Scope Boundary: aligned structure walk is the
permanent 0.3.0 design).
- Engine: Layout::Packed stores Arc<ReadPlan> alongside the builder;
sequential_reader() is an Arc::clone (was a full-document Value
clone); packed validate_bytes calls materialize_packed(&self.plan).
- Stride (deferred decision 4): FieldValue::Array now reports the
true stride for fixed-size struct/nested-array elements (0.2.0
returned 0); doc comment documents the behavioral change; no
existing test asserted the 0, so none needed changing.
- Two parity subtleties found and preserved:
(a) materialize_plan_composite unwraps the plan's anonymous
single-field wrapper for primitive array elements/record values
— without it, materialized records nest each leaf under a
synthetic object (caught by the record parity test);
(b) field-disc unions keep 0.2.0's materialized key order
(__discriminator first), observable under preserve_order.
Both are now covered by plan-phase tests or construction.
Bench (alktty wire_vs_bast, 1024 chunks/stream): packed read
2.27 us/chunk (review #004) -> 98 ns/chunk p64 / 100 ns/chunk p4k
(~23x; the 400x gap closes to ~17x vs hand-rolled 5.6 ns/chunk).
Residual gap is the per-field String allocation mandated by the
unchanged (String, FieldValue) read_next signature (2 allocs/chunk)
plus data_access bounds checks. sequential_reader() construction:
15.7 ns (was a whole-document clone).
Verification: 465 tests pass unchanged (the existing reader/
materialize/engine suites drive the rewrite through the public API —
only constructor call sites moved to ReadPlan::compile); clippy
-D warnings clean; cargo doc zero warnings; wasm32 release build
green.
The compiled value-domain validation form: replaces the interpretive
BastDoc walk in validate_bytes with a compile-once-walk-many
constraint tree built at engine-compile time. This was the design
session + implementation ADR-012 §3 delegated; the shape decisions
are recorded in new ADR-012 §3a.
- New src/validation_plan.rs: ValidationPlan + ValidNode/ValidField/
ValidVariant (Debug+Clone+PartialEq+Eq+Hash+Send+Sync),
compile(&BastDoc) with eager $ref resolution, and a per-buffer walk
with deferred error-path rendering (zero happy-path allocation,
byte-identical error messages vs the 0.2.0 walker).
fingerprint() via DefaultHasher, same as the phase-6 pattern.
- Compile-time graph safety: definition-level cycle set + depth cap
(128) reject cyclic $ref graphs with AlkTypeError::Schema. The
interpretive walker resolved refs lazily with no guard (stack-
overflow hazard); diamond (shared) refs still compile.
- bast_validation.rs: interpretive walker retired (deleted);
validate_value survives as a one-shot wrapper (compile + validate)
for callers holding a doc without an engine.
- engine: Arc<ValidationPlan> built at compile in BOTH modes; the
plan compile runs before the layout build and doubles as the
engine's cyclic-ref gate (LayoutBuilder/OffsetMap struct recursion
has no cycle guard; a cyclic doc previously overflowed there).
validate_bytes walks the plan; new accessor validation_plan().
validate_bytes signature unchanged.
- lib.rs: pub mod validation_plan + re-exports (ValidationPlan,
ValidNode, ValidField, ValidVariant).
Verification: cargo test --release (355 pass, incl. parity suite,
fingerprint contract, cycle/depth rejection, Send+Sync + thread-share
assertions); clippy --all-targets -D warnings clean; cargo doc
zero warnings; wasm32-unknown-unknown release build green.
Co-authored-by: opencode <noreply@alk.dev>
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).
- N1: number_from_f64 now returns AlkTypeError::Access for non-finite
floats instead of silently materializing Value::Null. A NaN/Inf in the
buffer surfaces as a clear access error rather than a misleading
'expected a number' validation error.
- N3: drop the dead Value::String arm from check_bytes; the materializer
only ever emits bytes as an array of u8. Update the validation-model
docs to match.
- N4: fix stale ADR references in doc comments (ADR-096 -> ADR-002,
ADR-097 -> ADR-003, ADR-098 -> ADR-004, ADR-101 -> ADR-007).
N2 (BastType::alk_kind returning Struct for ) left as documented;
every current caller resolves the ref first, so forcing Option through
the call sites is churn without benefit.
Verification: cargo test --release (411 pass), cargo clippy --all-targets
-- -D warnings (clean), cargo doc --no-deps (clean).
Resolves review #003 findings M1, M2, L1, and L2.
- offset-indirect (L1 + M2 arm): rework read_*_indirect to same-buffer
absolute offsets (safetensors-style, per the metatensor model) and add
write_*_indirect. Wire the encoding dispatch into engine.read_field/
write_field and the aligned materializer. Previously the encoding was
laid out but never read back.
- field-level endian override (M1): thread field.effective_endian through
sequential_reader, engine.read_field/write_field, and
materialize_struct_aligned. Previously a per-field endian override was
silently ignored, misreading multi-byte values.
- aligned-mode materialization (M2): materialize fixed-size arrays via
their vals[i] offset-map entries and maxLength reservations as
zero-padded fixed-size slices (trailing NULs trimmed). Previously both
read garbage or errored.
- dead endian param (L2): drop the ignored endian argument from
materialize_packed/materialize_aligned; endianness is read from the
root struct.
Verification: cargo test --release (404 pass), cargo clippy --all-targets
-- -D warnings (clean), cargo build --target wasm32-unknown-unknown
--release (clean), cargo llvm-cov --release (89.60% lines).
Replace the jsonschema custom-keyword validator on the bytes path with
a recursive walker over the BAST typed tree. The materializer already
guarantees structural correctness; the validator enforces only the
value-domain constraints expressed in the BAST document.
- New `src/bast_validation.rs`: `validate_value` dispatches on
`BastType`, resolving `$ref`s lazily via `BastDoc::resolve_typeref`.
Constraint arms: integer ranges (Int8..Uint64), float finiteness,
string/bytes `maxLength` (from `BastField::max_length`), RFC 3339
timestamp shape, enum index bounds, union variant dispatch (recurses
into the variant, recovering OQ-008 per-variant constraints), struct
field presence, array count, record values.
- `engine::validate_bytes` now calls `bast_validation::validate_value`
instead of `self.validator.validate`. The `validator` field is still
built and used by `validate_json`/`is_valid_json` (step 6 reworks
those).
- Enum index bounds check (`idx < values.len()`) fixes the v0.1.0 dead
constraint: the built-in `enum` keyword checked string membership, but
the materializer emits a numeric index that never matched.
- Errors constructed via `jsonschema::ValidationError::custom` so
`AlkTypeError::Validation` keeps its payload type uniform with the
`validate_json` path (D-BAST-009).
- `lib.rs`: add `pub mod bast_validation;` (engine-internal, not
re-exported in the `pub use` block) and update the module doc.
Verification:
- cargo test --release: 446 pass (369 lib + 77 integration)
- cargo clippy --all-targets -- -D warnings: clean
- cargo build --target wasm32-unknown-unknown --release: clean
- cargo doc --no-deps: clean