Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30b01c1696 | ||
|
|
f1f8508177 | ||
|
|
2719629621 | ||
|
|
f371fe4e94 |
No files matched your search
+315
-5
@@ -613,6 +613,100 @@ over unchanged.
|
||||
- BAST-native validator — a recursive walker over the BAST type tree that checks value-domain constraints on a materialized `Value`. Replaces the 19 custom keyword validators for the `validate_bytes` path. Enforces: integer ranges, float finiteness, string/bytes `maxLength`, RFC 3339 timestamp shape, enum index bounds, union variant dispatch (recursing into variants). See [The Validator Split](#the-validator-split).
|
||||
- BAST meta-schema validation at compile time (optional but recommended — the engine can skip it and trust the caller, or validate as a guard)
|
||||
|
||||
## Semver and ADR Impact
|
||||
|
||||
This section is the scope-creep guardrail for the public API during
|
||||
implementation. The crate is on crates.io at 0.1.0 with zero real
|
||||
consumers, so a breaking bump is free — but the contract still needs to
|
||||
be explicit so the implementation doesn't drift. Per AGENTS.md, the
|
||||
0.1.0 public surface is the items re-exported from `src/lib.rs`.
|
||||
|
||||
### Public API delta
|
||||
|
||||
| Public item (from `lib.rs` re-exports) | Class | Change |
|
||||
|---|---|---|
|
||||
| `AlkTypeKind` (enum + variants + methods) | **Additive** | Unchanged. 19 variants, same methods. New `from_str()`/`to_str()` mapping for lowercase BAST kind strings (`"uint32"` ↔ `AlkTypeKind::Uint32`) — additive methods. |
|
||||
| `Endian`, `VariableEncoding`, `DiscriminatorKind` | **Unchanged** | — |
|
||||
| `AlkTypeEngine::compile` | **Breaking** | Signature: `compile(schema: &mut Value, mode)` → `compile(bast_doc: &Value, root_name: &str, mode)`. Adds required `root_name` param (D-BAST-001); drops `&mut` (BAST needs no in-place `normalize_refs`); input is a BAST document, not a custom-keyword JSON Schema. |
|
||||
| `AlkTypeEngine::validate_json` | **Breaking (behavioral)** | Signature unchanged `(instance: &Value) -> Result<...>`, but the validator it runs is now a standard `jsonschema::Validator` from a consumer-provided JSON Schema supplied at `compile`, not a custom-keyword validator built from the alktype schema. The compiled engine must carry a separate JSON-Schema validator (or `validate_json` takes the JSON Schema at call time — to be decided in step 6). Either way the *contract* of what schema validates the instance changes. |
|
||||
| `AlkTypeEngine::validate_bytes` | **Unchanged (contract)** | Same signature. Internally the validation step switches from `jsonschema::Validator` to the BAST-native validator. Error type unchanged (D-BAST-009). |
|
||||
| `AlkTypeEngine::is_valid_json` | **Breaking (behavioral)** | Same caveat as `validate_json` — validates against the consumer JSON Schema, not the alktype schema. |
|
||||
| `AlkTypeEngine` accessors (`endian`, `mode`, `offset_map`, `layout_builder`, `sequential_reader`, `read_field`, `write_field`, etc.) | **Unchanged** | Layout-layer accessors are format-agnostic. |
|
||||
| `LayoutMode`, `OffsetMap`, `ByteRange` | **Unchanged** | — |
|
||||
| `LayoutBuilder`, `PackedLayout`, `FieldPosition` | **Unchanged** | — |
|
||||
| `SequentialReader`, `FieldValue` | **Unchanged** | — |
|
||||
| `UnionDispatch` | **Unchanged** | — |
|
||||
| `data_access::*` functions | **Unchanged** | — |
|
||||
| `AlkTypeError` (all 4 variants) | **Unchanged** | D-BAST-009 keeps `Validation(jsonschema::ValidationError<'static>)`. |
|
||||
| `Schema` builder (`struct_`, `object`, `field`, `build`, all setters) | **Breaking (output format)** | Public method signatures unchanged. `build()` output changes from custom-keyword JSON to BAST JSON (for `struct_`) / standard JSON Schema (for `object`). Callers that introspect the built `Value` break; callers that pass it straight to `compile` are source-compatible once `compile` takes BAST. |
|
||||
| `Definitions` builder (`new`, `define`, `define_value`, `build`, `merge_into`) | **Breaking (output format)** | Same as `Schema` — signatures unchanged, `build()`/`merge_into()` output shape changes to BAST `$defs`. |
|
||||
| `Discriminator` builder enum | **Unchanged** | — |
|
||||
| `build_validator` (from `validation`) | **Breaking (signature or removal)** | Currently `build_validator(schema: &Value) -> Result<jsonschema::Validator, AlkTypeError>` builds a custom-keyword validator. Under the pivot it either (a) is removed (consumers call `jsonschema` directly for standard JSON Schema) or (b) is repurposed to build a standard `jsonschema::Validator` from a consumer-provided standard JSON Schema (no custom keywords). Decision belongs to step 6. Either way the current signature's contract breaks. |
|
||||
| `get_alktype_kind`, `get_alktype_kind_enum`, `get_alktype_kind_loose`, `get_alktype_kind_loose_enum`, `normalize_refs`, `inline_union_variant_refs`, `resolve_ref`, `resolve_ref_or_inline`, `parse_align`, `parse_discriminator`, `parse_encoding`, `parse_endian`, `parse_max_length` | **Breaking (removal or rework)** | All currently re-exported from `lib.rs`. `normalize_refs` and `inline_union_variant_refs` are removed (BAST needs neither). The `get_alktype_kind*` family is removed (replaced by direct `kind` parsing). The `parse_*` and `resolve_*` functions are reworked to read BAST properties instead of keyword-value objects, or removed if subsumed by the BAST parser. **Open: which of these stay public vs become internal.** Current leaning — drop all from `lib.rs` re-exports (they're engine-internal accessors, not consumer API); the BAST parser exposes a new typed surface instead. |
|
||||
|
||||
**Net breaking surface:** `compile`, `validate_json`/`is_valid_json`
|
||||
(contract), `Schema::build`/`Definitions::build` (output format),
|
||||
`build_validator` (signature/removal), and the ~13 `schema::*` helper
|
||||
re-exports. **Net additive:** BAST parser, BAST-native validator,
|
||||
`AlkTypeKind::from_str`/`to_str`. **Net unchanged:** the entire layout
|
||||
+ data-access + materialize + tunion layer, `AlkTypeError`, the
|
||||
`Discriminator` builder, `AlkTypeKind` variants.
|
||||
|
||||
### ADR impact
|
||||
|
||||
| ADR | Action | Reason |
|
||||
|---|---|---|
|
||||
| [ADR-001](../architecture/decisions/001-alktype-purpose-scope-jsonschema-engine.md) (purpose, scope, "schema is the format") | **Supersede** | The "schema is the format" principle is retained and strengthened (BAST *is* the format), but the concrete format changes from custom-keyword JSON Schema to BAST. A new ADR (ADR-BAST or renumbered) records the BAST format as the realization of the principle. ADR-001's Status → Superseded by ADR-BAST. |
|
||||
| [ADR-002](../architecture/decisions/002-two-layout-modes-packed-vs-aligned.md) (two layout modes) | **Unchanged** | Layout modes are format-agnostic. No content change; maybe a one-line note that the input format changed but the modes didn't. |
|
||||
| [ADR-003](../architecture/decisions/003-schema-annotations.md) (endianness, alignment, encoding, discriminators) | **Amend** | Annotation *semantics* carry forward unchanged; annotation *location* moves from custom-keyword objects to BAST type-level properties. Amend the "where annotations live" sections, keep the semantics. |
|
||||
| [ADR-004](../architecture/decisions/004-error-handling-validation-strategy.md) (`AlkTypeError`, load-time build, access-time check) | **Amend** | Error enum shape unchanged (D-BAST-009). The "validation strategy" section updates: bytes path uses BAST-native validator, JSON path uses standard `jsonschema`. The load-time/access-time split is retained. |
|
||||
| [ADR-005](../architecture/decisions/005-int64-uint64-first-class-kinds.md) (Int64/Uint64, JSON precision caveat) | **Unchanged** | Kinds carry forward; JSON precision caveat is unchanged. |
|
||||
| [ADR-006](../architecture/decisions/006-reject-non-final-inline-length-prefixed-in-aligned-mode.md) (reject non-final inline length-prefixed in aligned mode) | **Unchanged** | Layout rule, format-agnostic. |
|
||||
| [ADR-007](../architecture/decisions/007-packed-mode-read-factory.md) (packed-mode read factory, `sequential_reader()` returns owned reader) | **Unchanged** | Reader factory semantics are format-agnostic. |
|
||||
| [ADR-008](../architecture/decisions/008-reject-tunion-in-aligned-mode.md) (reject TUnion in aligned mode for v1) | **Unchanged** | Layout rule, format-agnostic. |
|
||||
| [ADR-009](../architecture/decisions/009-builder-api.md) (builder API) | **Amend** | Public method surface unchanged; `build()` output format changes (BAST for `struct_`, standard JSON Schema for `object`). Amend the "output format" section; keep the method catalog. |
|
||||
| [ADR-010](../architecture/decisions/010-generalized-validation-validate-bytes.md) (`validate_bytes` — materialize then validate) | **Amend** | The two-step concept (materialize → validate) is retained. The validation step's *implementation* changes from `jsonschema` custom keywords to the BAST-native validator. Amend the "validation step" section; add a pointer to D-BAST-006/D-BAST-009 and the Validator Split section. |
|
||||
|
||||
**New ADRs to write (post-implementation, grounded in shipped code):**
|
||||
- **ADR-BAST** — the BAST format, meta-schema, and `$defs`/`$ref`/
|
||||
`kind` vocabulary. Supersedes ADR-001's format-specific content.
|
||||
- **ADR-VAL-SPLIT** (or fold into ADR-004's amend) — the two-validator
|
||||
model: BAST-native for `validate_bytes`, standard `jsonschema` for
|
||||
`validate_json`. Records D-BAST-006, D-BAST-007, D-BAST-009.
|
||||
|
||||
**Descriptive docs to sync (post-implementation):**
|
||||
- `docs/architecture/schema-layer.md` — rewrite for the BAST parser
|
||||
(replaces the custom-keyword accessor walk-through).
|
||||
- `docs/architecture/validation.md` — rewrite for the validator split.
|
||||
- `docs/architecture/builder.md` — update the `build()` output examples
|
||||
to BAST JSON.
|
||||
- `src/lib.rs` module doc comment — update the "Takes a JSON Schema
|
||||
with `AlkType:*` custom keywords" preamble to BAST.
|
||||
|
||||
**Stale TODOs to remove:** any TODO referencing custom-keyword
|
||||
normalization, `inline_union_variant_refs`, or the rejected
|
||||
bare-name-ref design — align with the ADRs as AGENTS.md §"Architecture
|
||||
Context" requires.
|
||||
|
||||
### Open decisions deferred to implementation steps
|
||||
|
||||
These are small enough to decide when the step is reached, but are
|
||||
flagged here so they don't become drive-by semver changes:
|
||||
|
||||
1. **`validate_json` JSON Schema source** (step 6): does the consumer
|
||||
pass the JSON Schema to `compile` (engine carries a second
|
||||
validator) or to `validate_json` at call time? The former preserves
|
||||
the current single-call ergonomics; the latter is more flexible. Not
|
||||
semver-relevant either way if `validate_json`'s signature can absorb
|
||||
a new param or stay as-is — needs the call-site analysis.
|
||||
2. **`build_validator` fate** (step 6): removed vs repurposed. If
|
||||
repurposed, its signature stays but its contract (no custom
|
||||
keywords) changes — a behavioral break, not a type break.
|
||||
3. **`schema::*` helper re-exports** (step 3): drop from `lib.rs`
|
||||
(engine-internal) vs keep public for consumers that walk schemas.
|
||||
Leaning: drop — they're accessors for the old format, and the BAST
|
||||
parser exposes a cleaner typed surface. Confirmed during step 3.
|
||||
|
||||
## The Validator Split
|
||||
|
||||
A key architectural clarification: BAST separates two concerns that the
|
||||
@@ -935,15 +1029,24 @@ through JSON Schema trees do not.
|
||||
|
||||
1. Define the BAST meta-schema (the JSON Schema that validates BAST
|
||||
documents)
|
||||
2. **Run the BAST-native validator POC** — implement the validator,
|
||||
2. ~~**Run the BAST-native validator POC** — implement the validator,
|
||||
wire it into `validate_bytes`, confirm the existing test suite passes.
|
||||
This de-risks the validation model before the full pivot.
|
||||
This de-risks the validation model before the full pivot.~~
|
||||
**Done.** See [POC Result — BAST-native validator](#poc-result--bast-native-validator).
|
||||
The hypothesis is confirmed; the production refactor (step 5 below)
|
||||
is now a straightforward port. POC code lives on branch
|
||||
`bast-validator-poc` (commit `f371fe4`) as a self-contained
|
||||
`src/bast_poc.rs` reference — deliberately not merged to main (it is
|
||||
throwaway scaffolding that gets replaced by the production module in
|
||||
step 5).
|
||||
3. Implement BAST document parsing in the engine (replace custom keyword
|
||||
detection with `kind` field parsing)
|
||||
4. Update `AlkTypeEngine::compile()` to accept a BAST document + root
|
||||
type name
|
||||
5. Implement the BAST-native validator (production version, informed
|
||||
by the POC)
|
||||
by the POC). Constructs `AlkTypeError::Validation` via
|
||||
`jsonschema::ValidationError::custom` per [D-BAST-009](#d-bast-009-alktypeerrorvalidation-payload-shape)
|
||||
— the `Validation` variant's payload type is unchanged.
|
||||
6. Update `validate_json()` to accept a consumer-provided JSON Schema
|
||||
and build a standard `jsonschema::Validator` (no custom keywords)
|
||||
7. Update the builder API to produce BAST JSON (public methods unchanged)
|
||||
@@ -974,12 +1077,19 @@ engine's byte-identity is already proven (alknet-typedef-poc,
|
||||
alktype-builder-poc) and the layout code is unchanged, so there is
|
||||
nothing empirical to de-risk there.
|
||||
|
||||
One targeted POC **is** needed to de-risk the validation model. The
|
||||
risk is specific and falsifiable: can a BAST-native validator — a
|
||||
One targeted POC **was** needed to de-risk the validation model. The
|
||||
risk was specific and falsifiable: can a BAST-native validator — a
|
||||
recursive walker over the BAST type tree — fully replace the 19 custom
|
||||
keyword validators on the `validate_bytes` path, including the OQ-008
|
||||
union variant dispatch, without regression?
|
||||
|
||||
**The POC has been run and succeeded.** The remainder of this section
|
||||
records the original scope and success criteria (kept for the record);
|
||||
the outcome is in [POC Result — BAST-native validator](#poc-result--bast-native-validator).
|
||||
The POC code is on branch `bast-validator-poc` (commit `f371fe4`), not
|
||||
merged to main — it is reference scaffolding superseded by Phase 1
|
||||
step 5.
|
||||
|
||||
### POC: BAST-native validator for `validate_bytes`
|
||||
|
||||
**Hypothesis:** A recursive walker over the BAST type tree can enforce
|
||||
@@ -1140,6 +1250,57 @@ Future crates (alktty, tunnels, sftp, git) will use BAST for their
|
||||
binary formats. The codegen feature (future) will generate
|
||||
readers/writers from BAST documents for these crates.
|
||||
|
||||
### D-BAST-009: `AlkTypeError::Validation` payload shape
|
||||
|
||||
**Status: decided.** Keep `Validation(jsonschema::ValidationError<'static>)`.
|
||||
|
||||
`AlkTypeError::Validation` currently wraps
|
||||
`jsonschema::ValidationError<'static>`. Under the BAST pivot the
|
||||
`validate_bytes` path no longer uses `jsonschema` at all (confirmed by
|
||||
the [POC](#poc-result--bast-native-validator) — observation 1), so the
|
||||
error payload on that path is constructed via
|
||||
`jsonschema::ValidationError::custom` purely to keep the variant's type
|
||||
unchanged. The two options were:
|
||||
|
||||
1. **Keep `Validation(jsonschema::ValidationError<'static>)`.** Simplest —
|
||||
`ValidationError::custom` is public and `'static`, so the bytes path
|
||||
can construct it without a real `jsonschema` validator. Cost: the
|
||||
error type retains its `jsonschema` dependency even though the bytes
|
||||
path no longer drives it. `validate_json` still uses `jsonschema`, so
|
||||
the dependency isn't removable either way — but the error type
|
||||
carries `jsonschema` only for one of its two callers.
|
||||
2. **Introduce `Validation(String)` (or a small structured payload).**
|
||||
Drops the `jsonschema` type from the public error enum. This is a
|
||||
**semver-relevant public-API change** (the `Validation` variant's
|
||||
payload type changes), so per AGENTS.md it requires an explicit
|
||||
decision, not a drive-by. Benefit: the error type is
|
||||
`jsonschema`-free, which matters if a future `no_std`/minimal build
|
||||
wants to drop `jsonschema` from the bytes-only path (relates to
|
||||
OQ-002).
|
||||
|
||||
**Rationale for option 1:** The deciding factor is consumer ergonomics
|
||||
on the *combined* path. Consumers like alkcall use both `validate_json`
|
||||
(channel 0, JSON-RPC) and `validate_bytes` (binary channels) and handle
|
||||
`AlkTypeError::Validation` in one place. A single uniform payload type
|
||||
means one match arm covers both sources — no `Validation(jsonschema_err)
|
||||
vs Validation(string)` branching downstream. Option 2 would force
|
||||
`validate_json` to flatten its structured errors (instance path, schema
|
||||
path, keyword) to a `String` via `Display` just to match a bytes-path
|
||||
shape — the more information-rich path loses data to accommodate the
|
||||
less rich one. That is the wrong direction.
|
||||
|
||||
The `no_std`/minimal-build angle (OQ-002) that option 2 was meant to
|
||||
enable is moot in practice: `validate_json` requires `jsonschema`
|
||||
regardless, so a bytes-only `no_std` build already has to give up
|
||||
`validate_json` as a separate, larger decision. Dropping the type from
|
||||
one error variant does not unlock that build — the dependency is load-
|
||||
bearing on the other validation path. The right place to revisit this is
|
||||
when/if OQ-002 is actually pursued, not preemptively.
|
||||
|
||||
The POC already used option 1 (via `ValidationError::custom`); the
|
||||
production refactor (Phase 1 step 5) follows the same construction
|
||||
pattern. No semver-relevant change to the `Validation` variant.
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
@@ -1165,3 +1326,152 @@ readers/writers from BAST documents for these crates.
|
||||
- `/workspace/@alkimiadev/typebox-rs/src/codegen/` — Rust/TypeScript codegen from schemas (reference architecture)
|
||||
- `/workspace/alknet-typedef-poc/tests/sftp_roundtrip_test.rs` — SFTP POC proving byte-identical output (to be replicated with BAST)
|
||||
- `/workspace/@alkdev/alkcall/src/channels/wire.rs` — hand-rolled chunk header (target for BAST replacement)
|
||||
|
||||
## POC Result — BAST-native validator
|
||||
|
||||
**Status: succeeded.** The POC is on branch `bast-validator-poc` in
|
||||
`src/bast_poc.rs` (20 tests, all passing; full crate suite — 416 tests —
|
||||
green; `cargo clippy --all-targets -- -D warnings` clean;
|
||||
`cargo build --target wasm32-unknown-unknown --release` clean).
|
||||
|
||||
The POC implements the `validate_bytes` validation model from
|
||||
[D-BAST-006](#d-bast-006-validate_bytes-validation-model) as a
|
||||
self-contained module that does **not** touch the production schema /
|
||||
materializer / validator paths. It reuses only `data_access` (read
|
||||
primitives), `AlkTypeError` (error type), and `Endian`. The BAST
|
||||
document parser, a packed-mode materializer, and the BAST-native
|
||||
validator are all implemented from scratch — that is the point: prove
|
||||
the model works end-to-end before refactoring the production code.
|
||||
|
||||
### What the POC proves
|
||||
|
||||
The hypothesis from the [POC section](#poc-bast-native-validator-for-validate_bytes)
|
||||
is confirmed: a recursive walker over the BAST type tree fully
|
||||
replaces the 19 custom keyword validators on the `validate_bytes` path,
|
||||
including the OQ-008 union variant dispatch, and fixes the
|
||||
enum-membership dead constraint — all without `jsonschema` custom
|
||||
keywords and without an external JSON Schema.
|
||||
|
||||
The hard cases that were the actual de-risking targets all pass:
|
||||
|
||||
- **Union byte-offset discriminator + `maxLength` inside a variant
|
||||
(OQ-008).** `union_byte_disc_max_length_inside_variant_enforced`
|
||||
materializes a union with two `$ref` variants, dispatches on a
|
||||
byte-offset `uint8` discriminator, and enforces `maxLength` on a
|
||||
`bytes` field inside the selected variant. The validator reads
|
||||
`__discriminator`, looks up the variant's BAST definition, and
|
||||
recurses — same behavior as the current `UnionValidator`'s
|
||||
per-variant sub-validators, but with no `jsonschema` involvement.
|
||||
- **Union field-name discriminator + `maxLength` inside a variant.**
|
||||
`union_field_disc_max_length_inside_variant_enforced` covers the
|
||||
typedef.ts-style discriminator (a length-prefixed string field
|
||||
selects the variant). Same recursion model.
|
||||
- **Enum index-bounds fix.** `enum_index_out_of_bounds_rejected`
|
||||
exercises the constraint that is **broken in the current engine**
|
||||
(the built-in `enum` keyword checks string membership; the
|
||||
materializer emits `Value::Number(index)`, which never matches — a
|
||||
dead constraint). The BAST-native validator checks the materialized
|
||||
index against the `values` array bounds (0..len-1), which is the
|
||||
correct validation for a binary enum encoded as an index. Net
|
||||
improvement, not a regression.
|
||||
- **Nested struct wrapping a union wrapping a struct.**
|
||||
`nested_struct_with_union_variant` confirms the recursion composes
|
||||
through multiple type layers.
|
||||
- **Arrays of fixed-size structs with `count`.**
|
||||
`array_of_structs_with_count` covers the `Vector3`-style array
|
||||
(D-BAST-004).
|
||||
- **Records (count-prefixed string-keyed maps).**
|
||||
`record_of_uint32` covers the `TRecord` shape.
|
||||
- **Untrusted schema input.** `malformed_document_produces_schema_error_not_panic`
|
||||
confirms a malformed BAST document surfaces as `AlkTypeError::Schema`,
|
||||
not a panic (AGENTS.md §3).
|
||||
- **Basic cases** (chunk header, int8/uint32 ranges, string/bytes
|
||||
`maxLength`, timestamp, bool, short buffer) all pass — if a couple of
|
||||
basic examples work, all of them do, since the validator is a flat
|
||||
per-kind dispatch with no per-kind special-casing beyond the range
|
||||
bounds.
|
||||
|
||||
### How the validator works
|
||||
|
||||
The validator is a single recursive function
|
||||
(`validate_typeref`) that dispatches on the BAST `kind`. Each arm
|
||||
checks the value-domain constraint for that kind and, for composites,
|
||||
recurses into the child type definitions. The materializer (also
|
||||
implemented in the POC) guarantees structural correctness — bounds,
|
||||
UTF-8, bool byte, discriminator lookup, all fields present — so the
|
||||
validator only enforces what the materializer cannot:
|
||||
|
||||
| Constraint | Validator arm |
|
||||
|------------|---------------|
|
||||
| Integer range (Int8..Uint64) | `validate_int` / `validate_uint` with `as_i64`/`as_u64` + range check |
|
||||
| Int64/Uint64 (full range) | `validate_int64` / `validate_uint64` (JSON precision caveat per ADR-005) |
|
||||
| Float finiteness (Float32/64) | `validate_float` with `as_f64().is_finite()` |
|
||||
| String `maxLength` (byte length) | `check_string` reads the field-level `maxLength` annotation |
|
||||
| Bytes `maxLength` (array length) | `check_bytes` accepts both `Value::String` and `Value::Array` forms |
|
||||
| RFC 3339 timestamp shape | `validate_timestamp` reuses the same non-strict check as the current engine |
|
||||
| Enum index bounds | `validate_enum` checks `idx < values.len()` — the dead-constraint fix |
|
||||
| Union variant dispatch | `validate_union` reads `__discriminator`, looks up the variant, recurses via `validate_typeref` |
|
||||
| Struct fields | `validate_struct` walks `fields`, requires each declared field present, recurses |
|
||||
| Array count | `validate_array` checks `arr.len() == count` and recurses per element |
|
||||
| Record values | `validate_record` recurses into each value's `values` type |
|
||||
| Boolean | `validate_bool` (materializer already rejects non-0/1 bytes) |
|
||||
|
||||
### Observations for the production implementation
|
||||
|
||||
1. **No `jsonschema` dependency for `validate_bytes`.** The validator
|
||||
only needs `serde_json` (for `Value`) and the BAST document. The
|
||||
`jsonschema` crate is still a direct dependency for `validate_json`
|
||||
and for validating BAST documents against the BAST meta-schema, but
|
||||
the `validate_bytes` path no longer touches it. This is a small wasm
|
||||
binary-size win in addition to the architecture simplification.
|
||||
|
||||
2. **`$ref` resolution is a single hash lookup.** The POC's
|
||||
`resolve_ref_or_inline` handles only `#/$defs/Name` pointers — the
|
||||
only form BAST allows. The current engine's `normalize_refs` /
|
||||
`inline_union_variant_refs` / `resolve_ref_or_inline` machinery for
|
||||
bare-name refs and inlined union variants is no longer needed: BAST
|
||||
`$ref`s are always full JSON Pointers, and union variant refs are
|
||||
resolved lazily by the validator (the materializer already does this
|
||||
for the read path). The `inline_union_variant_refs` compile step can
|
||||
be removed entirely.
|
||||
|
||||
3. **The validator is ~250 lines.** The 19 custom keyword validators
|
||||
(`src/validation.rs`) plus the macro definitions are ~500 lines and
|
||||
require the `jsonschema::Keyword` trait plumbing (factory closures,
|
||||
`Box<dyn Keyword>`, sub-validator construction at factory time). The
|
||||
BAST-native validator is a flat match — no factories, no trait
|
||||
objects, no sub-validator pre-computation. The recursion is direct.
|
||||
|
||||
4. **The `AlkTypeError::Validation` variant still wraps
|
||||
`jsonschema::ValidationError<'static>`.** The POC uses
|
||||
`jsonschema::ValidationError::custom` to construct these so the
|
||||
error type is unchanged. This is now the decided shape for the
|
||||
production refactor — see [D-BAST-009](#d-bast-009-alktypeerrorvalidation-payload-shape).
|
||||
The rationale is consumer ergonomics: a single uniform payload type
|
||||
means one match arm covers both `validate_json` and `validate_bytes`
|
||||
errors downstream, and `validate_json`'s structured errors are worth
|
||||
preserving rather than flattening to a `String`.
|
||||
|
||||
5. **The materializer and validator share the BAST-walking code
|
||||
structure.** Both walk the same `kind`/`fields`/`mapping` tree. The
|
||||
production refactor could share a typed BAST tree (a small
|
||||
`BastNode` enum) between them so the walk is parsed once. The POC
|
||||
parses lazily from the raw JSON in both passes to keep the model
|
||||
honest; a typed tree is a straightforward follow-on optimization, not
|
||||
a risk.
|
||||
|
||||
### Verdict
|
||||
|
||||
The "how do we reproduce the same behavior?" question is answered:
|
||||
walk the BAST tree the same way the materializer does, checking the
|
||||
same value-domain constraints the custom keyword validators check
|
||||
today. The model is a strict simplification — fewer moving parts, no
|
||||
`jsonschema` integration on the bytes path, no compile-time
|
||||
`inline_union_variant_refs` step, no factory closures or trait
|
||||
objects, and the enum dead-constraint is fixed as a side effect.
|
||||
|
||||
The POC does not wire into `AlkTypeEngine::validate_bytes` — that is
|
||||
the production refactor (Phase 1 step 5 in the Migration Path), which
|
||||
replaces `validation::build_validator` usage on the bytes path with
|
||||
the BAST-native validator. The POC's job was to de-risk the model
|
||||
before that refactor; that job is done.
|
||||
+1454
File diff suppressed because it is too large.
Load diff
@@ -28,6 +28,7 @@
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
pub mod bast_poc;
|
||||
pub mod builder;
|
||||
pub mod data_access;
|
||||
pub mod engine;
|
||||
|
||||
Reference in new issue
Block a user