Author SHA1 Message Date
glm-5.2 30b01c1696 Sync BAST pivot doc with main (semver/ADR impact mapping)
Brings the 'Semver and ADR Impact' section from main onto this branch
so the two doc copies don't drift. No code change.
2026-08-15 10:29:27 +00:00
glm-5.2 f1f8508177 Sync BAST pivot doc with main (D-BAST-009 resolution)
Brings the D-BAST-009 decision and the step-5/POC-observation-4 updates
from main onto this branch so the two doc copies don't drift. No code
change.
2026-08-15 10:13:54 +00:00
glm-5.2 2719629621 Mark BAST validator POC done; track OQ-BAST-001 error-payload decision
Phase 1 step 2 and the POC Scope section now reflect that the POC has
run and succeeded, with a pointer to the POC Result section. The POC
code stays on this branch (commit f371fe4) as reference scaffolding and
is deliberately not merged to main — it is superseded by Phase 1 step 5.

Elevates the deferred AlkTypeError::Validation payload-shape question
to OQ-BAST-001 in the Decisions section. The POC kept the existing
jsonschema::ValidationError<'static> shape; the production refactor
must decide explicitly between keeping it (simplest, dependency
stays) or introducing a Validation(String) shape (drops jsonschema
from the error type, but is a semver-relevant public-API change).
2026-08-15 09:38:36 +00:00
glm-5.2 f371fe4e94 Add BAST-native validator POC for validate_bytes
Implements the D-BAST-006 validation model as a self-contained module
(`src/bast_poc.rs`) that does not touch the production schema /
materializer / validator paths. Reuses only `data_access`,
`AlkTypeError`, and `Endian` — the BAST parser, packed-mode
materializer, and native validator are all from scratch, which is the
point: prove the model works end-to-end before refactoring.

The POC confirms the hypothesis from the pivot doc: a recursive walker
over the BAST type tree fully replaces the 19 custom keyword
validators on the `validate_bytes` path, recovers the OQ-008 union
variant dispatch, and fixes the enum-membership dead constraint on the
bytes path — all without `jsonschema` custom keywords and without an
external JSON Schema.

Coverage (20 tests, all passing):
- basic: chunk header, int8/uint32 ranges, string/bytes maxLength,
  timestamp, bool, short buffer
- hard: union byte-offset disc + maxLength inside variant (OQ-008),
  union field-name disc + maxLength inside variant, enum index-bounds
  fix, nested struct→union→struct, array of structs with count,
  record, untrusted-schema error-not-panic

Verification:
- cargo test --release (416 tests: 330 lib + 86 integration)
- cargo clippy --all-targets -- -D warnings
- cargo build --target wasm32-unknown-unknown --release
- cargo doc --no-deps

Findings and verdict recorded in docs/research/bast-pivot.md
§"POC Result — BAST-native validator".
2026-08-15 09:29:14 +00:00
3 changed files with 1770 additions and 5 deletions

No files matched your search

+315 -5
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -28,6 +28,7 @@
#[macro_use]
mod macros;
pub mod bast_poc;
pub mod builder;
pub mod data_access;
pub mod engine;