9949f914df4bcfecb52e0f92159d41ec011ee976
31
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9949f914df |
Release v0.3.0: compiled forms — ReadPlan, owned BastDoc, LeafMeta, ValidationPlan, fingerprinting
Public API bump 0.2.0 -> 0.3.0 (the 030-compiled-forms plan is now fully implemented; all eight phases landed). - Cargo.toml: version 0.3.0. lib.rs re-exports complete (ReadPlan + sub-types, LeafMeta, OffsetEntry, ValidationPlan + sub-types). - ADR-007 "Cost" rewritten to the Arc<ReadPlan> cost (15.7 ns) with the 0.2.0 "re-parse on demand" framing as a historical note (review #004 L2, the last loose end from that review). - ADR-011/012 status blocks flipped to implemented; architecture README ADR table rows updated; layout-engine.md rewritten for the 0.3.0 surface (engine-factory reader construction, OffsetMap OffsetEntry/LeafMeta/fingerprint section, owned BastDoc compute signature); SequentialReader module doc points at the engine factory. Reviews #004 and #005 flipped to closed. - Bench re-run (alktty wire_vs_bast, 0.3.0 tree): read p64 98 ns/chunk (parity with phase 2; hand-rolled 5.7 us/stream), layout_build 180 ns (was ~1.2 us — the phase-4 owned-doc cache removed the per-build re-parse, ~7x), sequential_reader_new 15.7 ns, write p64 -3%, engine_compile unchanged (meta-schema validation dominates). No dedicated validate_bytes-stream bench: the phase-7 spot check (~0.2 us plan-validate vs ~0.6 us compile-per-call) stands; a dedicated bench is a follow-up if alkcall profiling motivates it. - Downstream: alktty compiles against the path dep unchanged; alkcall has no dependency yet. Verification (full block, all green): 474 tests; clippy -D warnings clean; cargo doc zero warnings; wasm32 release build green; cargo publish --dry-run clean at 0.3.0. |
||
|
|
537a2170fb |
Fingerprint ReadPlan/OffsetMap: Hash + Eq + fingerprint() (ADR-012 §1/§4, plan phase 6)
- #[derive(Hash, Eq)] on ReadPlan, FieldPlan, CompositePlan, ReadKind, DiscriminatorPlan (schema: Arc<Value> hashes via serde_json Value Hash + Eq under preserve_order), and on OffsetMap (+ Clone; LeafMeta/OffsetEntry/ByteRange payload already Hash from phase 5 / this phase). - fingerprint() -> u64 on both via std DefaultHasher (deferred decision 3 resolved: no new dep, not hot, cross-version stability a non-goal per ADR-012). - Contract tests both sides: equal schemas -> equal PartialEq + fingerprint; field-kind / field-order / endianness changes each break equality and fingerprint; different root names over the same document fingerprint differently (ReadPlan). - ValidationPlan already carries its own Hash/Eq/fingerprint + contract test (phase 7 landed early). Verification: 474 tests pass (9 new fingerprint contract tests); clippy -D warnings clean; cargo doc zero warnings; wasm32 release build green. |
||
|
|
255c8c493e |
OffsetMap carries LeafMeta; read/write_field dispatch on it (ADR-012 §2b, plan phase 5)
Prerequisite (review #005 M2): Hash added to Endian/VariableEncoding derives (additive; fieldless Eq enums), and to ByteRange. - New public types LeafMeta { kind, encoding, endian } (Copy + Eq + Hash) and OffsetEntry { range, meta } (start()/end() accessors), re-exported from lib.rs. Deferred decision 2 resolved: struct — get(path) -> Option<&OffsetEntry>, iter() -> (&str, &OffsetEntry). Storage: Vec<(String, OffsetEntry)>. - LeafMeta computed at compute time with effective endian threaded through the aligned walk (container default -> field override, propagated into nested-struct probes and array elements via the referring field, matching the aligned materializer). - engine read_field/write_field dispatch on the entry's LeafMeta: the per-access BastDoc re-parse + lookup_leaf_field walk + LeafFieldInfo are gone — the last two review #004 M1 sites. - Parity note: lookup_leaf_field computed nested-struct defaults from the nested struct's own endian annotation; the map now agrees with the aligned materializer and packed ReadPlan (referring-field propagation). The old divergence (nested struct declaring endian under a field that also declares one) is closed; no test pinned it. - Behavior change: read_field on a map-absent path (whole-struct field) errors Offset ("field not found") instead of Access ("composite types"); the composite-path test accepted either. - materialize_aligned's four offset_map.get call sites updated to .range.start. alktty/alkcall untouched (bench never uses OffsetMap::get; alkcall has no dependency yet). Verification: 465 tests pass (offset_map tests updated to the OffsetEntry shape with per-kind LeafMeta expectations; engine test for the old lookup walk rewritten to assert map entries carry the LeafMeta); clippy -D warnings clean; cargo doc zero warnings; wasm32 release build green. |
||
|
|
b7c7dbe2a1 |
LayoutBuilder caches the owned BastDoc (ADR-012 §2a, plan phase 4)
The builder stores doc: BastDoc + endian (the doc_value: Value + root_name: String cache is gone); new parses the typed tree once and build walks &self.doc — the per-build BastDoc::new re-parse (layout_builder.rs M1) is retired. - build's root-is-struct re-check replaces its unreachable!() with a clean Schema error (AGENTS.md §3 never-panic; invariant unchanged — new already rejects non-struct roots). - Boxing fallout: the builder now holds the full owned tree, so Layout::Packed boxes it (Box<LayoutBuilder>) to keep the engine's Layout enum variant sizes balanced (clippy large_enum_variant). layout_builder() still returns Option<&LayoutBuilder> via auto-deref; public API unchanged. Verification: 465 tests pass unchanged (layout_builder.rs suites drive new/build through the public API); clippy -D warnings clean; wasm32 release build green. |
||
|
|
c583762352 |
Make BastDoc owned: drop Bast* lifetimes (ADR-012 §2a, plan phase 3)
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. |
||
|
|
e5f1b9d825 |
Wire packed read path through ReadPlan (ADR-011 steps 2-4, plan phase 2)
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. |
||
|
|
ff85258d03 |
Implement ReadPlan type + compile (ADR-011 step 1, plan phase 1)
Pure addition: the packed read-side compiled form (src/read_plan.rs) and lib.rs wiring (module + re-exports of ReadPlan, FieldPlan, CompositePlan, ReadKind, DiscriminatorPlan). No existing engine code touched — phases 2-5 wire the plan into the reader/materializer/engine. - Refined union shape (ADR-011 as refined by review #005): CompositePlan::Union { disc, shared, variants } with shared: Option<Box<ReadPlan>> for field-disc unions and variants: Vec<(String, CompositePlan)> — no VariantPlan/VariantKind, nested-union variants work by ordinary CompositePlan recursion (restores the 0.2.0 capability the POC rejected). - by_name is BTreeMap (ADR-012 §1 Hash-derive prerequisite). - True array strides (deferred decision 4): fixed struct/nested-array elements compute their real stride via fixed_composite_size; variable-length elements stay 0. 0.2.0 returned 0 for fixed struct arrays; that behavioral change rides the 0.3.0 bump (phase 2 will surface it through SequentialReader). - Endianness: effective endian baked at every node. Parity lock: the plan propagates the referring field's effective endian into nested structs/unions — what the 0.2.0 packed reader/materializer actually do — and ignores nested containers' own endian annotations (the POC baked s.endian() there; latent divergence, never exercised by its equivalence tests). Nested-annotation tests lock this in. - Untrusted input: compile carries its own depth cap (128) + definition-level cycle set (mirrors ValidationPlan::compile), so standalone compile is safe on adversarial docs: cyclic refs, deep chains, dangling refs, non-struct roots, and non-struct/union variants all surface as AlkTypeError::Schema, never a panic. Overflow-safe stride arithmetic (checked_mul). Verification: 388 tests pass (355 existing + 33 new: every BastType arm coverage, field-disc shared/nested-union compile shape, stride computation, endian parity, cycle/depth/malformed rejection, Send + Sync static-bound assertion); clippy -D warnings clean; cargo doc zero warnings; wasm32-unknown-unknown release build green. Next: phase 2 (SequentialReader + materialize_packed consume the plan). |
||
|
|
e4636e6a44 |
Implement ValidationPlan (ADR-012 §3, plan phase 7)
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> |
||
|
|
82fec45bc6 |
Sync docs to 18 BAST kinds (Timestamp removal fallout)
- README: 19 -> 18 kinds, drop the timestamp row from the kinds table, drop 'timestamp shape' from the validate_bytes constraint list, remove the residual 'upcoming alkcall crate' sentence from the crate independence section (alkcall exists now), fix two '19 kinds' refs in the documentation pointer list and schema-layer link. - src/data_access.rs: module doc comment still said 'all 19 AlkType kinds' -> 18. - bast-format.md (normative): 19 -> 18 AlkTypeKind enum variants. - layout-engine.md: cross-reference to 'the 19 AlkType kinds' -> 18. - ADR-BAST (bast-bast-format.md): the Decision section claimed the post- pivot enum has '19 unchanged' variants; now 18, with the wording adjusted so it no longer says 'unchanged' across the pivot. - ADR-VAL-SPLIT: drop 'timestamp shape' from the value-domain constraint list and the validator-arm table row (validate_timestamp no longer exists). Left as historically accurate (describe the v0.1.0 pre-pivot state): ADR-003/004/006 Context mentions of AlkType:Timestamp, ADR-005 'engine now has 19', and the '19 jsonschema::Keyword factories' references in the What-is-removed sections of ADR-BAST and ADR-VAL-SPLIT. 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). |
||
|
|
510553d800 |
Remove the Timestamp kind
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). |
||
|
|
62ed009281 |
Resolve review #003 nits N1, N3, N4
- 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). |
||
|
|
230345a867 |
Validate BAST documents against the meta-schema at compile time
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). |
||
|
|
e5c7cc1ca2 |
Fix offset-indirect, field-level endian, and aligned materialization
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). |
||
|
|
54fd112fde |
Remove v0.1.0 custom-keyword machinery (step 8)
The BAST parser (step 3) and BAST-native validator (step 5) replaced the v0.1.0 custom-keyword accessor layer; step 7 moved the builder to BAST output. This step removes the now-dead code: Removed from src/schema.rs: - get_alktype_kind / get_alktype_kind_enum / get_alktype_kind_loose / get_alktype_kind_loose_enum (replaced by the BAST parser's kind dispatch) - normalize_refs / inline_union_variant_refs + helpers (BAST refs are always #/$defs/<name>; resolution is a single hash lookup, variant refs resolve lazily) - parse_encoding / parse_align / parse_max_length / parse_endian (bast.rs has its own BAST-property-form copies) - parse_discriminator + DiscriminatorKind (replaced by bast::BastDiscriminator; builder has its own Discriminator enum) - resolve_ref / resolve_ref_or_inline (replaced by BastDoc::lookup_def / resolve_typeref) - FromStr impl, as_str, Endian::from_schema, ALKTYPE_PREFIX, BYTE_DISCRIMINATOR_TYPES, and the associated unit tests Kept: AlkTypeKind enum + methods (type_size, natural_alignment, is_fixed_size, needs_endian, is_composite, is_variable_length, to_bast_str, from_bast_str), Display (now backed by to_bast_str), Endian, VariableEncoding, U32_SIZE, DISCRIMINATOR_PATH. src/lib.rs: dropped the 13 schema::* helper re-exports and DiscriminatorKind from the public surface; kept Endian, AlkTypeKind, VariableEncoding. Doc/comment updates: bast.rs, builder.rs, engine.rs, error.rs — removed references to the deleted functions and the AlkType:* keyword form. The jsonschema crate remains a dependency (validate_json path + BAST meta-schema validation); build_validator was already repurposed in step 6 (no custom keywords). Verification: - cargo test --release: 389 pass (312 lib + 77 integration) - cargo clippy --all-targets -- -D warnings: clean - cargo doc --no-deps: clean - cargo build --target wasm32-unknown-unknown --release: clean |
||
|
|
45f3336201 |
Builder API produces BAST JSON (step 7)
- Schema internals: flat Map<String, Value> → Repr enum distinguishing
BAST primitives (bare TypeRef strings), BAST composites (struct/
union/enum/array/record objects), $ref, standard JSON Schema
objects, and raw adopted values. Annotations (endian/align/encoding/
maxLength) stored on the Schema and placed correctly by build()
(struct-level) or field() (field-level).
- Primitive constructors (uint32, string, bytes, etc.) now produce
bare BAST kind strings ("uint32") instead of {"AlkType:Uint32":true}.
- struct_().field(...) produces {"kind":"struct","fields":[{name,kind,...annos}]}
with an ordered fields array (BAST design principle #4 — no reliance
on preserve_order for field order).
- union_() emits {"kind":"union","discriminator":{...},"mapping":{...}}
with BAST kind strings in the discriminator ("uint8" not
"AlkType:Uint8"). Field-name discriminator unions emit the fields
array; byte-offset unions omit it.
- enum_of() produces {"kind":"enum","values":[...]}.
- array_of(element).count(n) produces {"kind":"array","element":...,"count":n}.
.count() is an additive method (D-BAST-004 requires count; the
array_of signature is unchanged per the semver contract).
- record_of(values) produces {"kind":"record","values":...}.
- object()/string_()/etc. unchanged — standard JSON Schema output.
- encoding() now stores the value on the Schema; field() extracts it
as a field-level property. No more keyword-object duality.
- max_length() on standard types emits the standard keyword; on BAST
types it is extracted by field() as a field-level constraint.
- Definitions::build_doc(root_name, root) — new additive method
producing a complete BAST document with the root type inside $defs
(where BAST requires it). The old build()/merge_into() remain for
backward compatibility.
- All builder tests updated to expect BAST output shapes. New tests:
field annotations, field-name union with fields, array with count,
ref element, encoding emission, Definitions::build_doc round-trip
through compile(), SFTP-style union compile, array/record compile.
- Module doc comments updated (builder.rs, lib.rs).
Verification:
- cargo test --release: 427 pass (350 lib + 77 integration)
- cargo clippy --all-targets -- -D warnings: clean
- cargo doc --no-deps: clean (no warnings)
- cargo build --target wasm32-unknown-unknown --release: clean
|
||
|
|
ba7f8e1bad |
validate_json against consumer-provided JSON Schema (step 6)
- AlkTypeEngine::compile gains a 4th param json_schema: Option<&Value>. When Some, a standard jsonschema::Validator is built from the consumer-provided JSON Schema and stored for the JSON-validation path. When None, validate_json returns AlkTypeError::Schema and is_valid_json returns false (D-BAST-007). - validate_json / is_valid_json signatures unchanged (per semver contract). Behavior: they now validate against the consumer JSON Schema, not a custom-keyword validator built from the alktype schema. The BAST document is not involved in this path. - build_validator repurposed (deferred decision #2): same signature, now builds a standard jsonschema::Validator with no custom keywords. Behavioral break, not a type break. Re-export kept. - Removed the 19 jsonschema::Keyword implementations and the 4 define_*_validator! macros (dead on the bytes path since step 5, now dead on the JSON path too). The is_rfc3339_timestamp helper lives on in bast_validation.rs (already copied there in step 5). - All compile call sites updated to pass None for json_schema (the layout/read/write/validate_bytes tests don't need JSON validation). - New tests: validate_json accepts/rejects against consumer JSON Schema, returns Schema error when no JSON Schema supplied, is_valid_json false when no schema, independence from BAST doc, malformed JSON Schema build error, nested object JSON Schema. Verification: - cargo test --release: 409 pass (332 lib + 77 integration) - cargo clippy --all-targets -- -D warnings: clean - cargo build --target wasm32-unknown-unknown --release: clean - cargo doc --no-deps: clean |
||
|
|
f853dafaf1 |
Add BAST-native validator for validate_bytes (step 5)
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 |
||
|
|
04573e1d86 |
Wire compile() to BAST document + root name (step 4)
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
|
||
|
|
f2f9c0326c |
Add BAST document parser (typed tree, step 3)
- 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 |
||
|
|
29134789a9 |
Embed BAST v1 meta-schema as BAST_META_SCHEMA
Step 2 of the BAST pivot. Adds the normative BAST meta-schema (Draft 2020-12 JSON Schema) as a public serde_json::Value, embedded at compile time and available for validating BAST document well-formedness. Copied verbatim from docs/architecture/bast-format.md §The Meta-Schema. - New src/bast_meta.rs: BAST_META_SCHEMA static, built lazily via LazyLock (the json! macro allocates, so it can't be a const; parsed once, reused as &'static Value thereafter). - src/lib.rs: pub mod bast_meta + re-export BAST_META_SCHEMA. Additive public surface. The meta-schema validates structure (correct $defs shape, known kind strings, required properties, no additional properties, $ref restricted to #/$defs/<name>). Value-domain constraints (maxLength, enum index bounds) are enforced by the BAST-native validator (step 5), not this meta-schema. Verification: - cargo test --release: 418 tests pass (332 lib + 86 integration); 16 new unit tests exercise the meta-schema against valid and invalid BAST documents (missing $defs, unknown kind, additional properties, byte-discriminator union, enum, array+count, record, $ref, malformed ref, empty enum). - cargo clippy --all-targets -- -D warnings: clean. - cargo build --target wasm32-unknown-unknown --release: clean (LazyLock + serde_json::json! macro are wasm-safe). |
||
|
|
66ab9d7d93 |
Add AlkTypeKind::to_bast_str/from_bast_str for BAST kind strings
Step 1 of the BAST pivot. Adds the lowercase-string mapping
("uint32" <-> AlkTypeKind::Uint32) that the BAST parser and
validator dispatch on (D-BAST-002).
- to_bast_str(self) -> &'static str: returns the lowercase BAST
string for all 19 variants (14 primitives + struct/union/array/
record/enum). Boolean -> "bool", distinct from the PascalCase
variant name.
- from_bast_str(s) -> Result<AlkTypeKind, AlkTypeError>: inverse of
to_bast_str; returns AlkTypeError::Schema for unknown strings.
These are additive inherent methods on the already-re-exported enum.
The existing FromStr impl (parsing the v0.1.0 "AlkType:Uint32"
keyword form) is unchanged and removed in step 8. The two surfaces
are deliberately distinct: from_bast_str rejects "AlkType:Uint32"
and FromStr rejects "uint32".
Verification:
- cargo test --release: 402 tests pass (316 lib + 86 integration);
6 new unit tests cover both directions, round-trip, and the
from_bast_str/from_str distinctness.
- cargo clippy --all-targets -- -D warnings: clean.
|
||
|
|
fb3a27f974 |
Pre-publish docs sweep: README, AGENTS.md, licenses, inline doc fixes
- Add README.md reflecting the v0.1.0 state: 19 AlkType kinds, two layout modes, builder + AlkTypeEngine usage example (verified to compile and run), validation entry points, crate independence, untrusted-schemas guarantee, docs pointers. Mirrors the alkvault README structure. - Add AGENTS.md with alktype-specific git workflow, project conventions (no comments, AlkTypeError, untrusted schemas, overflow safety, no async, no feature flags, wasm-clean, preserve_order load-bearing, no unsafe), verification commands, and ADR/OQ index. Blocks auto-commit on semver-relevant public API changes per the crates.io 0.1.0 contract. - Add LICENSE-MIT and LICENSE-APACHE (dual MIT/Apache-2.0, matching alkvault and the Cargo.toml license field). - Cargo.toml: add readme, keywords, categories, rust-version = "1.85". - Fix broken intra-doc link in builder.rs: DiscriminatorKind -> crate::schema::DiscriminatorKind (cargo doc now warning-free). - N1 (review #002): document is_rfc3339_timestamp as non-strict in the function doc comment. Lists the specific gaps (day-of-month per month, seconds range, leap seconds) and points consumers needing strict validation to chrono/time. - N2 (review #002): document the FieldValue::Bytes-for-Record API asymmetry in the FieldValue enum doc and on read_record_value. - .opencode/agents/implementation-specialist.md: point to AGENTS.md for full convention details (matches the alkvault pattern). - review #002: mark N1/N2 resolved; all 7 findings now closed. Verification: - cargo test --release: 396 tests pass (310 crate + 86 integration) - cargo clippy --all-targets -- -D warnings: clean - cargo doc --no-deps: clean (no broken intra-doc link warnings) - cargo build --target wasm32-unknown-unknown --release: clean - cargo publish --dry-run --allow-dirty: clean |
||
|
|
5f88bca0d8 |
Fix L2: replace unreachable! with Err for untrusted-schema safety
The immediate downstream consumer (alkcall) accepts schemas from
arbitrary internet peers in its hub/spoke topology. A panic is the
wrong failure mode for a malicious or unsupported schema - an Err
the caller can handle is correct.
Three sites converted:
- offset_map.rs: _ => Err(Offset { unsupported AlkType kind for
aligned offset computation })
- layout_builder.rs: _ => Err(Offset { unsupported AlkType kind
for packed layout computation })
- materialize.rs: _ => Err(Schema { internal: union discriminator
type N is not a supported byte discriminator })
The materialize.rs site uses Schema (not Access) because a wrong
disc_type is a schema-authoring bug (parse_discriminator should have
caught it), not a buffer-access error. The sibling sites in
sequential_reader.rs and tunion.rs already returned Err(Schema) -
only materialize.rs was the holdout.
Note: the k if k.is_fixed_size() guard in offset_map and
layout_builder means the compiler cannot enforce exhaustiveness at
compile time. Converting _ from unreachable! to Err is the runtime
mitigation. A future refactor could list all fixed-size kinds
explicitly to restore compile-time checking. Deferred to a separate
cleanup pass.
Verified: no unreachable! remains in production code (the one
remaining hit at offset_map.rs:688 is inside a #[test] fn).
cargo test --release: 396 tests pass, 0 failures
cargo clippy --all-targets -- -D warnings: clean
cargo build --target wasm32-unknown-unknown --release: clean (prior)
|
||
|
|
a975befdd1 |
Fix M1, M2, L1, L3 from code review #002
Four of seven review findings resolved. 5 new tests (391 -> 396 crate tests; 438 -> 443 total). cargo test, clippy, wasm32 all green. M2 (data_access.rs): write_bytes now validates data_len fits in u32 before the length-prefix cast. A >4GiB blob returns Access error instead of silently writing a truncated length prefix (silent data corruption on read-back). M1 (builder.rs): Definitions::merge_into rewritten to access self.defs directly instead of round-tripping through self.build() with a double- cloned/unwrap_or_default chain that could silently drop definitions on a shape mismatch. 4 new tests: insert-when-absent, merge-into- existing, overwrite-duplicate-keys, no-op-on-non-object-top. L1 (materialize.rs): byte-offset discriminator arm of materialize_union_packed now uses checked_add for offset+disc_offset and disc_abs_offset+disc_size, returning Access error on overflow. Mirrors the existing sequential_reader.rs::read_union_value pattern. L3 (error.rs): AlkTypeError::source() now returns Some(inner) for the Validation variant (jsonschema::ValidationError implements std::error::Error). Existing source_returns_none_for_all_variants test split into source_returns_none_for_schema_offset_access and source_returns_some_for_validation_variant. Deferred: L2 (unreachable! -> Err, defense-in-depth), N1 (non-strict RFC 3339 validator, docs-only), N2 (FieldValue::Bytes for Record, API asymmetry). Review doc updated with resolution section. |
||
|
|
c0217d91a8 |
Resolve v0.1.0 open questions and fix production-readiness issues
POC: /workspace/alktype-builder-poc/ (18/18 tests pass, findings in
docs/research/alktype-builder-poc/findings.md). Round 2 adds the SFTP
Packet validate_bytes tests (7 new: valid Init/Read/Write/Status,
short buffer, unknown discriminator, over-maxLength Bytes).
Open questions resolved (OQ-004 through OQ-008):
- OQ-004: Discriminator::Field name is String (already implemented;
docs updated to mark resolved)
- OQ-005: Both union discriminator kinds return the same shape:
{__discriminator, ...variant-fields}. Field-name path also had a
real offset bug (returned start, not end) - fixed.
- OQ-006: builder.md Example 3 now wraps the Union in a
Schema::struct_().field("payload", ...) and merges $defs via
Definitions::merge_into (matches the engine's AlkType:Struct-at-root
constraint and the SFTP wire shape)
- OQ-007: Bytes materialization is array-of-u8 (Value::Array of
Value::Number, one entry per byte 0..=255). BytesValidator accepts
both Value::String (validate_json) and Value::Array (validate_bytes).
maxLength = max byte count. Replaces the lossy from_utf8_lossy path
that corrupted non-UTF-8 bytes and broke maxLength semantics.
- OQ-008 (new): UnionValidator now dispatches to variant schemas via
sub-validators built at factory time. AlkTypeEngine::compile calls
schema::inline_union_variant_refs before build_validator to inline
$refs in union mapping entries (necessary because union_factory
receives the union node, but $defs live at the schema root).
Production-readiness fixes in src/ (no stubs/hedges in a published crate):
- materialize.rs: Record stub -> full count-prefixed key/value pair
implementation per schema-layer.md TRecord
- materialize.rs: root_of() was broken (returned the current node, not
the schema root) -> root schema threaded through every recursive call
so resolve_ref_or_inline can resolve $refs for nested composites
- builder.rs: LengthPrefixed encoding setter was a no-op when the
keyword was already in object form -> complete the branch (updates
the encoding entry in place for both LengthPrefixed and OffsetIndirect)
- builder.rs, engine.rs: POC-referencing comments cleaned up; the
round-trip test's or_else fallback (papering over write_field being
aligned-only) replaced with direct byte writes
Documentation:
- builder.md: Example 3 updated; Discriminator::Field spec shows String;
Open Questions section updated (OQ-004 resolved)
- validation.md: AlkType:Bytes and AlkType:Union validator descriptions
updated for array-of-u8 form and variant dispatch
- open-questions.md: OQ-004/005/006/007/008 marked resolved; new
Validation theme entries
- questions/004-008: individual OQ files updated with resolutions
- findings.md: round 2 results documented
Verification:
- cargo test: 369 -> 391 tests pass (22 new: 14 materialize, 5
inline_union_variant_refs, 3 validation/builder)
- cargo clippy --all-targets: clean
- POC: 11 -> 18 tests (7 new SFTP Packet tests); all pass
|
||
|
|
5588278451 |
Implement builder (ADR-009) and validate_bytes (ADR-010) for v0.1.0
POC: /workspace/alktype-builder-poc/ (11/11 tests pass, findings in docs/research/alktype-builder-poc/findings.md). The POC code lives outside the repo per the internal dev convention. Implementation: - src/builder.rs: Schema, Definitions, Discriminator types. Constructors for all 19 AlkType kinds + standard JSON Schema types (object/array/ string/integer/number/boolean/null/any). Setters for ADR-003 annotations (endian/align/encoding/max_length), composite builders (field/required/ items/mapping), and standard JSON Schema constraints (minimum/maximum/ minLength/minItems/maxItems/format/title/description). 16 unit tests. - src/materialize.rs: materialize_packed and materialize_aligned functions that walk a schema + buffer to produce a serde_json::Value tree. Recurses into Struct, Array, Union (byte-offset discriminator). Record is stubbed (deferred for the POC scope). - src/engine.rs: AlkTypeEngine::validate_bytes(&[u8]) added (ADR-010). Dispatches on layout mode, materializes Value, then validates against the existing jsonschema validator. 7 unit tests. - src/lib.rs: pub mod builder, pub mod materialize; re-exports Schema, Definitions, Discriminator. Verification: - cargo test: 346 -> 369 tests pass (23 new: 16 builder, 7 validate_bytes) - cargo clippy --all-targets -- -D warnings: clean - POC (11 tests): builder round-trip + validate_bytes (packed + aligned) + validate_json for call payloads; all pass Findings: - Top-level schema must be AlkType:Struct (existing constraint); unions are field types within a struct. Builder spec Example 3 needs a doc fix. - Builder field order preserved (preserve_order feature, load-bearing for packed mode). - validate_bytes correctly distinguishes Access (read phase) from Validation (validate phase) errors, with field paths. - validate_json path unchanged for call payloads. Open questions surfaced (OQ-005, OQ-006, OQ-007) tracked in findings.md; to resolve before the SFTP Packet POC round. |
||
|
|
57d8ed25ba |
Add tests for coverage gaps S1, S2, S3, S5, S6, S7 (285→346 tests, 88.9%→91.9% lines)
- S1 (error.rs): 5 tests for Display impl on all 4 AlkTypeError variants + Error::source(). error.rs 0%→100%. - S2 (validation.rs, macros.rs): 28 tests for validate() (Result-returning) method on every validator + factory rejection arms for all 12 keywords. validation.rs 85%→97.5% lines / 100% fns; macros.rs 75.7%→93.1% / 100% fns. - S3 (engine.rs): 3 tests for read_field on nested struct leaf fields, Bytes, and Timestamp. engine.rs fns 95%→95.3%. - S5 (layout_builder.rs): 2 tests for union 'variant must be Struct' error (byte + field discriminator). - S6 (schema.rs): 6 tests for as_str() round-trip (all 19 kinds), Display, needs_endian(), is_composite(), get_alktype_kind_enum(). schema.rs 91.2%→97.5% lines / 94.3% fns. - S7 (offset_map.rs): 1 test for nested-struct-without-properties schema error. Updated docs/reviews/001-coverage-analysis.md with resolution section and partial-resolved status. Remaining: S4 (sequential_reader error paths, medium effort) and S8 (overflow guards, recommended to skip for v1). |
||
|
|
6d6142978a |
Clean up rebranding drift in docs, agent configs, and source comments
Fix stale references left over from the alknet-typedef → alktype migration: - .opencode/agents/: replace @alkdev/alknet constraints (tokio, crypto, feature flags, anyhow/thiserror) with alktype-accurate ones (sync, AlkTypeError, WASM-clean); fix @alkimiadev → @alkdev org name; remove nonexistent AGENTS.md ref; replace alknet-http/alknet-agent spec examples - docs/sdd_process.md: fix wrong package name (@alkdev/storage → @alkdev/alktype) - docs/architecture/: rewrite dangling /workspace/ and docs/research/ paths as @alkdev/alknet: cross-repo references with explanatory notes; fix @alkimiadev → @alkdev; fix 'not yet used by any alknet crate' stale context - src/ + tests/: correct '17 AlkType kinds' → '19' in doc comments (enum has 19 variants; pre-existing count error); fix dangling /workspace/ path in poc_roundtrip.rs |
||
|
|
5e268e8f47 |
Rebrand TypeDef to AlkType in code, keyword strings, and docs
Rename all 19 JSON Schema custom keyword strings from "TypeDef:*"
to "AlkType:*" (e.g., "TypeDef:Struct" -> "AlkType:Struct")
across source, tests, and docs. This is a breaking change to the
schema format itself — existing schemas using the old keywords
must be updated.
Rename the Rust identifiers:
- TypedefEngine -> AlkTypeEngine
- TypedefError -> AlkTypeError
- TypeDefKind -> AlkTypeKind
- TYPEDEF_PREFIX -> ALKTYPE_PREFIX
- get_typedef_kind{,_loose,_loose_enum,_enum} ->
get_alktype_kind{,_loose,_loose_enum,_enum}
Update error message strings ("unknown TypeDef kind" ->
"unknown AlkType kind"), 11 test function names containing
typedef_kind/to_typedef_error, and doc-comment prose ("TypeDef
kind" -> "AlkType kind", "typedef engine" -> "alktype
engine", "typedef schema" -> "alktype schema"). Fix the broken
docs/architecture/crates/typedef/ path references in source doc
comments to point at docs/architecture/ directly. Rebrand the
typedef:annotation test fixture and the "not-a-typedef" test
string to their alktype equivalents.
Update ~20 generic "typedef" prose references in the architecture
docs ("typedef is the binary struct engine", "use typedef",
"typedef limitation", "replaced by typedef", etc.) to alktype.
Rename TypedefEngine in the ADR-007 code example to AlkTypeEngine.
Preserve as provenance per the prior prose-rebrand decision:
typedef.ts references (external TypeBox source file),
docs/research/alknet-typedef/findings.md research citations,
/workspace/alknet-typedef-poc/ POC path, and the
"alknet-typedef:" research section headers in findings.
Build, 295 tests, and clippy all pass clean.
|
||
|
|
1cfb3638d1 |
Rebrand alknet-typedef to alktype in docs, crate name, and lib name
Renumber ADRs 095-102 to 001-008 and OQs 069-071 to 001-003, and update all cross-references (titles, body prose, file-path links, tables) across the 5 spec docs, README, open-questions index, and all 11 ADR/OQ files. Inline the ADR-009 door-type definition from the parent alknet project (broken cross-project reference). Rebrand prose: alknet-typedef -> alktype in headings, body text, dependency diagrams, and "additions" notes. Disambiguate the prior failed attempt at /workspace/@alkimiadev/alktype/ as "the @alkimiadev/alktype prototype" to distinguish it from this crate. Historical research citations (docs/research/*, /workspace/alknet-typedef-poc/) are kept as-is for provenance. Rename the crate in Cargo.toml ([package].name, [lib].name) and update the 11 use alknet_typedef::* imports across the 4 test files. Rebrand the crate-level doc comment in src/lib.rs. The TypeDef:* keyword strings, TypedefError/TypedefEngine identifiers, and other code-level references are unchanged — those are a separate code rebrand pass. Build, 295 tests, and clippy all pass clean. |
||
|
|
2c4a4994dc |
Port alknet-typedef crate from alknet
Copy the binary struct engine (src/, tests/) verbatim from alknet/crates/alknet-typedef and create a standalone Cargo.toml (workspace-inherited fields inlined). Port the architecture docs (specs, ADRs 095-102, OQs 069-071) from alknet's nested multi-crate layout to a flat single-crate layout, fixing relative link paths. Build, 295 tests, and clippy all pass clean. |