Step 9 (convert tests to BAST format) was a no-op: steps 4-8 converted
the tests as they went. The only remaining reference in
src/tests was the intentional rejection test at
src/schema.rs:462 (asserting the old keyword form is rejected). Full
suite passes: 389 tests (312 lib + 77 integration).
Step 10 (sync architecture docs and ADRs):
Descriptive docs rewritten/updated for BAST:
- schema-layer.md: rewritten for the BAST parser (BastDoc/BastDef/
BastType typed tree, AlkTypeKind enum with to_bast_str/from_bast_str,
what was removed). Points at bast-format.md for the normative format.
- validation.md: rewritten for the two-validator model
(bast_validation for validate_bytes, standard jsonschema for
validate_json). Documents the repurposed build_validator, the
AlkTypeError::Validation uniform payload (D-BAST-009), and what is
removed.
- builder.md: updated all output examples to BAST JSON
(struct_() -> { kind: struct, fields: [...] }; object() -> standard
JSON Schema). Documents build_doc, count(), and the field-name union
fields requirement (D-BAST-005).
- overview.md: updated for BAST (what/why, schema-is-the-format table,
dependencies, architecture pointers, design decisions table).
- README.md (architecture index): updated document table, ADR table
(new ADR-BAST + ADR-VAL-SPLIT, superseded ADR-001), OQ table
(OQ-007/OQ-008 resolutions updated for BAST-native validator), and
key design principles (#1, #2, #7, #10 reworded for BAST).
- data-access.md: updated tunion function signatures to BastUnion and
the variant resolution to return BastType (resolve_typeref for refs).
- layout-engine.md: updated construct signatures
(LayoutBuilder::new(bast_doc, root_name), OffsetMap::compute(&doc),
SequentialReader::new(bast_doc, root_name)), the recursive-walk
description (BAST typed tree), and composite-kind headings
(TStruct/TUnion/TArray -> struct/union/array). Added D-BAST-004
note on array count requirement.
New ADRs:
- ADR-BAST (bast-bast-format.md): the BAST format, meta-schema,
//kind vocabulary, design principles, what is removed, the
enum index bounds bug fix. Supersedes ADR-001's format-specific
content; records D-BAST-001..009.
- ADR-VAL-SPLIT (val-split-two-validator-model.md): the two-validator
model (BAST-native for validate_bytes, standard jsonschema for
validate_json), the repurposed build_validator, the uniform
AlkTypeError::Validation payload. Refines ADR-004's validation
strategy and ADR-010's validation step; records D-BAST-006/007/009.
Amended ADRs (supersession/amendment notes added; original decision
text preserved as historical record):
- ADR-001: format-specific content superseded by ADR-BAST;
purpose/scope and schema-is-the-format principle retained.
- ADR-002: unchanged under the pivot; one-line note that the input
format changed but the modes didn't.
- ADR-003: annotation semantics retained; annotation location moved
to BAST type-level properties (amended by ADR-BAST).
- ADR-004: AlkTypeError enum retained (D-BAST-009); validation
strategy section refined by ADR-VAL-SPLIT.
- ADR-009: builder API surface retained; build() output format
amended to BAST / standard JSON Schema by ADR-BAST (D-BAST-008).
- ADR-010: validate_bytes two-step concept retained; validation step
amended to the BAST-native validator by ADR-VAL-SPLIT.
Other:
- Cargo.toml description: JSON Schema with AlkType:* custom keywords
-> BAST document.
- bast-pivot.md research record: status draft -> implemented, with a
pointer to the ADRs that superseded its decisions.
- bast-implementation.md plan: status draft -> complete, with a note
that step 9 was a no-op and step 10 is this commit.
- open-questions.md: OQ-006/OQ-007/OQ-008 resolutions updated for the
BAST-native validator.
- questions/008-unionvalidator-variant-dispatch.md: added a
post-BAST-pivot note pointing to the current bast_validation
implementation; v0.1.0 resolution text preserved as historical
record.
Verification:
- cargo test --release: 389 pass (312 lib + 77 integration)
- cargo clippy --all-targets -- -D warnings: clean
- cargo doc --no-deps: clean
- cross-reference check: every relative link in the new/updated docs
resolves (verified by script).
29 KiB
status, created, last_updated
| status | created | last_updated |
|---|---|---|
| implemented | 2026-08-14 | 2026-08-15 |
BAST Pivot — Research Record
Status: implemented. The BAST pivot landed in steps 1–10 of the
implementation plan (commits
66ab9d7 → 54fd112 on origin/main). The decisions D-BAST-001..009
are recorded in two new ADRs —
ADR-BAST (the format)
and ADR-VAL-SPLIT
(the two-validator model) — which supersede the format-specific content
of ADR-001
and refine the validation strategy of
ADR-004
and ADR-010.
The normative format spec is
docs/architecture/bast-format.md;
the parser is documented in
docs/architecture/schema-layer.md.
What follows is the original research record — the why and what was proved, preserved as the historical grounding for the decisions.
Replace alktype's custom JSON Schema keywords (AlkType:Uint32,
AlkType:Struct, etc.) with a standalone JSON format — BAST (Binary
Abstract Syntax Tree) — that describes binary data layouts using a
kind-based vocabulary with $defs/$ref for composition. BAST is
itself a valid JSON Schema instance (it has a meta-schema), making it
self-validating, editor-friendly, and trivially consumable from any
language with a JSON parser.
The engine's core logic (layout computation, data access, union
dispatch, two layout modes) is unchanged. Only the schema-walking
accessor layer changes: instead of detecting AlkType:* keywords
scattered through a JSON Schema tree, the walkers read kind/fields/
annotation properties from a purpose-built format.
The builder API's public surface stays the same; only the JSON output format changes internally.
Document role. This is the research record: motivation, POC scope and result, decisions, risks, references. The normative format specification lives in
docs/architecture/bast-format.md. The execution plan — ordered implementation steps, the public-API semver contract, and the ADR-sync checklist — lives indocs/plans/bast-implementation.md. Those documents supersede the format-spec, what-changes, and migration-path sections that previously lived here; this record keeps the why and what was proved, not the how to implement.
Motivation
Current state
alktype v0.1.0 embeds binary layout information inside standard JSON Schema documents via custom keywords:
{
"AlkType:Struct": true,
"type": "object",
"properties": {
"channel_id": { "AlkType:Uint32": true, "type": "integer" },
"length": { "AlkType:Uint32": true, "type": "integer" }
},
"endian": "big"
}
This works for the Rust engine — it walks the tree, detects keywords, computes offsets. But it creates friction for everything outside Rust:
-
Cross-language consumption. A Python, Go, or TypeScript consumer that wants to parse an alktype schema must re-implement custom keyword detection. The format is not self-describing — you need to know that
AlkType:Uint32means "4-byte unsigned integer" and that it can appear as eithertrueor{ "encoding": "..." }. -
Code generation. Generating Rust/TypeScript/Python readers and writers from a schema requires walking an arbitrary JSON Schema tree looking for custom keywords. A
kind-based format with known keys makes this a straightforward structural walk. -
Tooling. Editors, linters, and schema validators don't understand
AlkType:*keywords. A BAST document with a published meta-schema gets autocomplete, validation, and documentation in any JSON Schema- aware editor for free. -
Two concerns in one document. The current format conflates binary layout (what the engine needs) with JSON validation (what jsonschema needs). A
type: "object"withpropertiesandrequiredis a JSON validation concern;AlkType:Uint32is a binary layout concern. They live in the same JSON object but serve different masters.
The downstream pain is real
The alkcall agent's review identified that the channels 8-byte chunk header is hand-rolled with manual bit shifts — alktype's binary layout capability is unused because the custom-keyword format is awkward to integrate for a simple 2-field struct. alktty plans to hand-roll its 5-byte TTY chunk format for the same reason. SFTP's 29 packet types were proven byte-identical with alktype in the POC, but the production path requires defining 29 schemas in the custom-keyword format.
All three cases are the same pattern: a small binary struct that needs a schema-driven reader/writer. BAST makes this trivial — a 10-line JSON file replaces hand-rolled bit shifts.
Timing
v0.1.0 was published but has zero real consumers (only bots/scanners have downloaded it). A breaking change now is free. Waiting until adoption creates migration cost.
POC Scope
The layout swap needs no POC — it is a backend swap (custom keywords →
kind-based format) on top of a proven layout engine. The layout
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 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 outcome is recorded in
POC Result below. The POC code is
on branch bast-validator-poc (commit f371fe4), not merged to main —
it is reference scaffolding superseded by the production module in
implementation step 5.
POC: BAST-native validator for validate_bytes
Hypothesis: A recursive walker over the BAST type tree can enforce
all value-domain constraints that the 19 custom keyword validators
currently enforce, recovering the OQ-008 union variant dispatch
behavior, and fixing the enum-membership dead constraint on the bytes
path — all without jsonschema custom keywords and without requiring
the consumer to provide an external JSON Schema.
Scope:
- Implement the BAST-native validator as a new module
(
src/bast_validation.rsor similar) - The validator walks a materialized
Valuetree against the BAST type definitions, checking:- Integer ranges (Int8..Uint64)
- Float finiteness (Float32/64)
- String
maxLength(UTF-8 byte length) - Bytes
maxLength(byte length) - RFC 3339 timestamp shape (non-strict, matching current behavior)
- Enum index bounds (0..values.len()-1 — fixes the dead constraint)
- Union variant dispatch (read
__discriminator, look up variant BAST definition, recurse) - Boolean validity (materializer already checks, but the validator should confirm)
- Wire it into
validate_bytes()as the validation step (replacing thejsonschema::Validatorcall) - Run the existing test suite — the tests encode all current expected validation behavior. If they pass, the POC succeeds.
Success criteria:
- All existing
validate_bytestests pass without modification to their assertions (test inputs will change to BAST format, but the expected validation outcomes must be identical) - The union variant dispatch tests (OQ-008) pass —
maxLengthon aBytesfield inside a union variant is enforced - The enum index-bounds validation works (new behavior — currently broken, so this is a fix, not a regression)
Failure path: If the POC reveals that the BAST-native validator cannot cleanly express some constraint (e.g., a constraint that relies on JSON Schema's structural keywords in a way that's hard to reimplement), the fallback is the "structural-only + external JSON Schema" model from the original Gap 1 — but this is unlikely given that the materializer already guarantees structure, leaving only value-domain checks.
Out of scope for this POC:
validate_json— this path uses a standardjsonschema::Validatorfrom a consumer-provided JSON Schema, not the BAST-native validator. No POC needed; it's a standardjsonschemausage.- BAST document parsing / meta-schema validation — the parser is straightforward JSON walking; no empirical risk.
- Layout computation — unchanged, already proven.
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 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
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 +
maxLengthinside a variant (OQ-008).union_byte_disc_max_length_inside_variant_enforcedmaterializes a union with two$refvariants, dispatches on a byte-offsetuint8discriminator, and enforcesmaxLengthon abytesfield inside the selected variant. The validator reads__discriminator, looks up the variant's BAST definition, and recurses — same behavior as the currentUnionValidator's per-variant sub-validators, but with nojsonschemainvolvement. - Union field-name discriminator +
maxLengthinside a variant.union_field_disc_max_length_inside_variant_enforcedcovers 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_rejectedexercises the constraint that is broken in the current engine (the built-inenumkeyword checks string membership; the materializer emitsValue::Number(index), which never matches — a dead constraint). The BAST-native validator checks the materialized index against thevaluesarray 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_variantconfirms the recursion composes through multiple type layers. - Arrays of fixed-size structs with
count.array_of_structs_with_countcovers theVector3-style array (D-BAST-004). - Records (count-prefixed string-keyed maps).
record_of_uint32covers theTRecordshape. - Untrusted schema input.
malformed_document_produces_schema_error_not_panicconfirms a malformed BAST document surfaces asAlkTypeError::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. The full constraint table is in
bast-format.md §Validation Model.
Observations for the production implementation
-
No
jsonschemadependency forvalidate_bytes. The validator only needsserde_json(forValue) and the BAST document. Thejsonschemacrate is still a direct dependency forvalidate_jsonand for validating BAST documents against the BAST meta-schema, but thevalidate_bytespath no longer touches it. This is a small wasm binary-size win in addition to the architecture simplification. -
$refresolution is a single hash lookup. The POC'sresolve_ref_or_inlinehandles only#/$defs/Namepointers — the only form BAST allows. The current engine'snormalize_refs/inline_union_variant_refs/resolve_ref_or_inlinemachinery for bare-name refs and inlined union variants is no longer needed: BAST$refs are always full JSON Pointers, and union variant refs are resolved lazily by the validator (the materializer already does this for the read path). Theinline_union_variant_refscompile step can be removed entirely. -
The validator is ~250 lines. The 19 custom keyword validators (
src/validation.rs) plus the macro definitions are ~500 lines and require thejsonschema::Keywordtrait 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. -
The
AlkTypeError::Validationvariant still wrapsjsonschema::ValidationError<'static>. The POC usesjsonschema::ValidationError::customto construct these so the error type is unchanged. This is now the decided shape for the production refactor — see D-BAST-009. The rationale is consumer ergonomics: a single uniform payload type means one match arm covers bothvalidate_jsonandvalidate_byteserrors downstream, andvalidate_json's structured errors are worth preserving rather than flattening to aString. -
The materializer and validator share the BAST-walking code structure. Both walk the same
kind/fields/mappingtree. The production refactor could share a typed BAST tree (a smallBastNodeenum) 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 (implementation step 5),
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.
Decisions
The following were open questions in earlier drafts. Each is now
resolved. They are recorded here as decisions, not re-litigated. The
normative format specification that realizes these decisions is in
bast-format.md; the semver
classification of each is in the
implementation plan's Semver Contract.
D-BAST-001: Root type selection
Decision: Explicit. The root type name is a required parameter to
compile(): AlkTypeEngine::compile(bast_doc, "ChunkHeader", Packed).
This is unambiguous and matches how consumers think about it ("compile
the ChunkHeader schema"). Convention (first entry in $defs) is fragile
and depends on JSON key order; a $root marker is redundant with an
explicit parameter.
D-BAST-002: Primitive type string set
Decision: Lowercase ("uint32", "int8", "float64", "bool",
"string", "bytes", "timestamp"). Matches JSON Schema's own
convention ("string", "integer", "boolean"), is easier to type,
and is the convention in the TypeBox research examples. The
AlkTypeKind enum variants remain PascalCase in Rust — the mapping is
a simple from_str() impl.
D-BAST-003: Top-level $defs requirement
Decision: Always $defs. Every BAST document has the same
top-level shape: { "$defs": { ... } }. Single-type documents are a
special case with one entry. The $defs block is the namespace; the
root type name (D-BAST-001) selects the entry point. A bare struct at
the top level would be a special case with different parsing logic and
no home for additional definitions.
D-BAST-004: Arrays of variable-length elements (deferred)
Decision: Arrays of variable-length elements are not supported
in v1. The meta-schema requires count on all array types, making
arrays fixed-size only. This matches the engine's current behavior (it
rejects arrays of variable-length elements) and aligns with OQ-001
(deferred, blocked on a concrete consumer needing interleaved
variable-stride arrays).
Variable-length collections are still available via record (a
count-prefixed string-keyed map), which the engine supports. If a
consumer needs a variable-length array of fixed-size elements, they
can use a record with integer-stringified keys as a workaround, or
wait for OQ-001 to be addressed.
D-BAST-005: Field-name discriminator unions
Decision: Supported. The meta-schema includes an optional fields
array on UnionDef. When discriminator.kind == "field", the
fields array provides the union's field definitions (including the
discriminator field). When discriminator.kind == "byte", fields is
absent — the union's layout is purely the variant layout. This
preserves a feature the engine already supports. The meta-schema is in
bast-format.md §The Meta-Schema.
D-BAST-006: validate_bytes validation model
Decision: BAST-native validator. The validate_bytes path uses a
recursive walker over the BAST type tree to check value-domain
constraints on the materialized Value — no external JSON Schema
needed. This recovers the OQ-008 union variant dispatch behavior (the
validator recurses into the variant's BAST definition) and fixes the
enum-membership dead constraint (the validator checks the materialized
index against the values array bounds). See bast-format.md §Validation
Model and the POC.
An optional external JSON Schema can be layered on top for constraints BAST doesn't express (cross-field consistency, regex patterns on string content). This is additive, not load-bearing.
D-BAST-007: validate_json validation model
Decision: Standard JSON Schema validator. validate_json on
AlkTypeEngine validates a consumer-provided JSON Value against a
jsonschema::Validator compiled from a standard JSON Schema document
the consumer provides at compile time. No custom keywords. The BAST
document is not involved in this path — BAST describes bytes, not JSON
shape. This preserves the validate_json / validate_bytes symmetry
from ADR-010, but the two paths now use different validators (standard
jsonschema for JSON, BAST-native for bytes), reflecting their
different inputs and guarantees.
The JSON Schema may be authored separately or derived from BAST via
future codegen. For alkcall's channel 0 (JSON-RPC), the JSON Schema is
the OperationSpec schema, authored independently of any BAST
document.
D-BAST-008: Builder API — two output formats
Decision: One builder, two build methods. The construction API is
the same (field names, types, annotations); only the output format
differs. Schema::struct_().field(...).build() → BAST JSON (binary
layout). Schema::object().field(...).build() → standard JSON Schema
(JSON validation). The builder already distinguishes AlkType kinds from
JSON Schema types via naming conventions (string() vs string_()).
Both output formats live in the same crate. This is the point of alktype: one small wasm-compatible codebase that handles both binary layout and JSON validation for protocol crates. alkcall uses both — channel 0 is JSON (standard JSON Schema), binary channels use BAST. 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 — 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:
- Keep
Validation(jsonschema::ValidationError<'static>). Simplest —ValidationError::customis public and'static, so the bytes path can construct it without a realjsonschemavalidator. Cost: the error type retains itsjsonschemadependency even though the bytes path no longer drives it.validate_jsonstill usesjsonschema, so the dependency isn't removable either way — but the error type carriesjsonschemaonly for one of its two callers. - Introduce
Validation(String)(or a small structured payload). Drops thejsonschematype from the public error enum. This is a semver-relevant public-API change (theValidationvariant's payload type changes), so per AGENTS.md it requires an explicit decision, not a drive-by. Benefit: the error type isjsonschema-free, which matters if a futureno_std/minimal build wants to dropjsonschemafrom 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 (implementation step 5)
follows the same construction pattern. No semver-relevant change to the
Validation variant.
Risks and Mitigations
| Risk | Mitigation |
|---|---|
| BAST format doesn't cover all 19 type kinds | The format is designed to cover all 19. The meta-schema is the spec — if a kind can't be expressed, the meta-schema is wrong. |
$ref resolution complexity moves from engine to schema authoring |
BAST $ref values are always full JSON Pointers (#/$defs/Name). No normalization, no bare names. Resolution is a single hash lookup. |
| BAST-native validator misses a constraint the custom keywords enforced | The POC runs the existing test suite, which encodes all current expected validation behavior. If a constraint is missed, a test fails before the pivot lands. |
| Losing OQ-008 union variant dispatch | The BAST-native validator recurses into the variant's BAST definition on __discriminator lookup — same behavior, no custom keywords. Covered by the POC. |
| Enum membership broken on bytes path | Already broken today (dead constraint). The BAST-native validator fixes it by checking the materialized index against the values array bounds. Net improvement. |
validate_json loses custom keyword validation |
validate_json uses a standard jsonschema::Validator from a consumer-provided JSON Schema. Consumers that relied on custom keywords for JSON validation need to provide equivalent standard JSON Schema keywords. No real consumers exist yet. |
| Builder API output format change breaks consumers | No real consumers exist yet (v0.1.0 has zero adoption). The builder's public methods are unchanged; only the JSON output format changes. |
| Meta-schema maintenance burden | The meta-schema is small and changes rarely. It's embedded in the crate and published at a stable URL. |
Future Directions
These are enabled by BAST but out of scope for the pivot itself. They are mentioned to show that BAST makes them possible, not to commit to a specific implementation timeline.
Codegen
BAST enables code generation that the custom-keyword format makes
awkward. A codegen module (feature-gated behind codegen) would walk
$defs entries, inspect kind values, and emit Rust/TypeScript/Python
readers and writers via Handlebars templates. The typebox-rs codegen/
module (/workspace/@alkimiadev/typebox-rs) is the reference
architecture: SchemaRegistry for named types, RustGenerator/
TypeScriptGenerator wrapping Handlebars, schema_to_rust_type()/
schema_to_ts_type() mapping functions. alktype's codegen would follow
the same pattern but walk BAST kind values. The handlebars-rs
dependency is WASM-compatible. The pivot changes the schema format;
codegen builds on top of the new format.
ABI Adapter
A BAST document describes the binary interface of a protocol — it is essentially an ABI specification in JSON. This enables version negotiation (two peers exchange BAST documents to agree on a protocol version; the engine detects mismatches and either rejects or adapts), schema migration (a consumer with schema v1 can read data written by schema v2 if the changes are compatible), and WASM interop (a WASM component can export its BAST schema as part of its WIT interface).
References
docs/architecture/bast-format.md— the normative BAST format spec (meta-schema, TypeRef, examples, validation model)docs/plans/bast-implementation.md— the execution plan (ordered steps, semver contract, ADR-sync checklist)- ADR-001 — current "schema is the format" principle (to be superseded by ADR-BAST post-implementation)
- ADR-003 — annotation semantics (carry forward to BAST unchanged)
- ADR-009 — builder API (public surface unchanged, output format changes)
- ADR-010 —
validate_bytes(unchanged in concept) /workspace/research/typebox_research/ujsx/jpath.gen.ts— TypeBoxType.Modulepattern (the$defs/$refmodel BAST follows)/workspace/research/typebox_research/ujsx/mdast.gen.ts— TypeBox cross-module references and composite types/workspace/research/typebox_research/codegen/ts-to-module.ts— TypeScript-to-TypeBox codegen (reference for future BAST codegen)/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)