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).
27 KiB
status, last_updated
| status | last_updated |
|---|---|
| accepted | 2026-08-15 |
alktype — Builder API
The builder layer: a fluent Rust API for constructing BAST documents
(binary-layout schemas) and standard JSON Schemas (JSON-validation
schemas) at runtime, producing serde_json::Value. Decided in
ADR-009; resolves
OQ-003. The
two-output-format split is D-BAST-008, recorded in
ADR-BAST.
What
The builder module provides a single Schema builder type and a
Definitions helper for named $defs. The builder's .build() method
returns a serde_json::Value — one of two forms depending on the
constructor used (D-BAST-008):
- BAST JSON (binary layout) —
Schema::struct_().field(...).build()produces a BAST TypeDef ({ "kind": "struct", "fields": [...] }). Primitive constructors produce bare TypeRef strings ("uint32"). Feed toAlkTypeEngine::compile(the binary-layout path) →validate_bytes. - Standard JSON Schema (JSON validation) —
Schema::object().field(...)produces{ "type": "object", "properties": {...}, "required": [...] }. No BASTkind, no custom keywords — a plain JSON Schema. Feed to a standardjsonschema::Validator(orAlkTypeEngine::compilewith a JSON Schema for thevalidate_jsonpath, D-BAST-007).
The builder covers:
- All 18 BAST kinds (binary-layout schemas) — see
schema-layer.md for the kinds and
bast-format.mdfor the format. - All standard JSON Schema keywords needed for operation payload
schemas:
type,properties,required,items,enum,format,additionalProperties,minimum,maximum,minItems,maxItems,minLength,maxLength,$ref,$defs. - All ADR-003 schema annotations:
endian,align,encoding(length-prefixed / offset-indirect),maxLength-as-reservation, and TUniondiscriminator(byte-offset and field-name).
Why
alkcall (the merged alknet-call + alknet-channels extraction) is
alktype's first consumer and needs to build schemas at runtime from
Rust code, for two roles:
- Binary layout schemas (channels' 8-byte chunk header, future
binary call frames) — BAST documents, fed to
AlkTypeEngine::compile(packed mode, big-endian) →validate_bytes. - JSON payload schemas (call's
OperationSpec.input_schema/output_schema/error_schemas) — plain JSON Schema, no BASTkind, validated via the standardjsonschemavalidator (AlkTypeEngine::compilewith a JSON Schema →validate_json).
A single builder serving both roles means alkcall imports one module
for schema construction. See ADR-009
for the decision rationale (why Value not a typed Schema enum, why
both BAST and standard JSON Schema in one builder) and
ADR-BAST for the two-output-format
decision (D-BAST-008).
Architecture
Output type: serde_json::Value
The builder returns serde_json::Value. There is no typed Schema
enum. This matches ADR-001's "schemas are JSON" principle and avoids
duplicating the JSON form that AlkTypeEngine::compile,
jsonschema::Validator, and OperationSpec all consume.
Module placement
src/builder.rs, re-exported from the crate root. The builder is a
peer of bast.rs (which parses BAST documents) and engine.rs (which
compiles them). The builder constructs; it does not parse or compile.
// src/lib.rs (additions)
pub mod builder;
pub use builder::{Schema, Definitions, Discriminator};
Field order is explicit
BAST struct fields are an ordered array (BAST design principle #4 —
see bast-format.md). The builder's
struct_()/union_() accumulates fields in call order and emits them
as the fields array on .build(). Field order in the builder is the
field order in the binary layout. This is critical for packed mode
(ADR-002) where field order determines offsets.
(serde_json's preserve_order feature remains a dependency, but
layout correctness no longer depends on it — the fields array makes
order explicit. preserve_order is still load-bearing for the
mapping object's iteration order and for Definitions' $defs
block, which the parser walks in document order.)
Public API
Schema builder
Schema is the single entry point. Constructors for each BAST kind and
each standard JSON Schema type; setters for annotations and
constraints; .build() produces Value.
BAST kind constructors
One constructor per AlkTypeKind variant (see schema-layer.md
§"The 18 BAST Kinds"):
impl Schema {
// Fixed-size integer kinds
pub fn int8() -> Self;
pub fn int16() -> Self;
pub fn int32() -> Self;
pub fn int64() -> Self;
pub fn uint8() -> Self;
pub fn uint16() -> Self;
pub fn uint32() -> Self;
pub fn uint64() -> Self;
// Float kinds
pub fn float32() -> Self;
pub fn float64() -> Self;
// Other fixed-size
pub fn boolean() -> Self;
pub fn enum_of(values: &[&str]) -> Self; // u32 index; values in declaration order
// Variable-length kinds
pub fn string() -> Self;
pub fn bytes() -> Self;
// Composite kinds
pub fn struct_() -> Self; // fields added via .field()
pub fn union_(disc: Discriminator) -> Self; // variants via .mapping()
pub fn array_of(element: Schema) -> Self; // .count() required for valid BAST (D-BAST-004)
pub fn record_of(value: Schema) -> Self;
}
Primitive constructors produce the bare BAST TypeRef string on
.build(). For example, Schema::uint32().build() produces "uint32".
Composite constructors produce the BAST object form.
enum_of produces a BAST enum TypeDef ({ "kind": "enum", "values": [...] }); declaration order is the index order — see
schema-layer.md §"The 18 BAST Kinds"):
Schema::enum_of(&["read", "write", "execute"]).build()
// -> { "kind": "enum", "values": ["read", "write", "execute"] }
array_of and record_of take the element/value schema as a
nested Schema. array_of requires .count(N) for valid BAST
(D-BAST-004 — arrays of variable-length elements without a count are
deferred, aligning with OQ-001):
Schema::array_of(Schema::uint32()).count(3).build()
// -> { "kind": "array", "element": "uint32", "count": 3 }
Schema::record_of(Schema::float32()).build()
// -> { "kind": "record", "values": "float32" }
Standard JSON Schema type constructors
For plain JSON Schema (no BAST kind) — call's input_schema /
output_schema / error_schemas:
impl Schema {
pub fn object() -> Self; // type: "object" — fields via .field()
pub fn array() -> Self; // type: "array" — items via .items()
pub fn string_() -> Self; // type: "string"
pub fn integer() -> Self; // type: "integer"
pub fn number() -> Self; // type: "number"
pub fn boolean_()-> Self; // type: "boolean"
pub fn null() -> Self; // type: "null"
pub fn any() -> Self; // {} (no type constraint)
}
The _ suffix disambiguates standard JSON Schema types from BAST kinds
(string is the BAST primitive; string_ is the standard JSON Schema
type — string() would produce "string" as a BAST TypeRef,
string_() produces { "type": "string" } as a standard JSON Schema).
This is deliberate: the two are distinct schema forms and the builder
makes the distinction visible at the call site.
Annotation setters
Annotation setters mirror ADR-003 (semantics unchanged; location moved
to BAST type-level properties under the pivot — see
ADR-BAST). Each setter is named after
the annotation it produces; calling the setter sets the corresponding
JSON key. Setters return Self for chaining.
impl Schema {
/// Struct/union-level endianness (ADR-003 §1). Default little.
pub fn endian(mut self, endian: Endian) -> Self;
/// Struct or field alignment (ADR-003 §2). Struct-level sets the
/// default; field-level overrides.
pub fn align(mut self, align: usize) -> Self;
/// Variable-length encoding strategy (ADR-003 §3).
/// Default length-prefixed.
pub fn encoding(mut self, encoding: VariableEncoding) -> Self;
/// maxLength — standard JSON Schema keyword. In aligned mode with
/// a variable-length type, reserves this many bytes (strategy 2).
/// In packed mode, validation constraint only.
pub fn max_length(mut self, max: usize) -> Self;
/// Array count (D-BAST-004 — required for valid BAST arrays in v1).
pub fn count(mut self, count: usize) -> Self;
}
Endian and VariableEncoding are re-exported from schema.rs (no
new types — the builder uses the existing enums). When applied to a
struct, endian/align are struct-level; when the Schema is used as
a .field() argument, the builder extracts endian/align/encoding/
maxLength and places them on the field object (BAST field-level
properties). The setters produce the exact BAST JSON shapes from
bast-format.md:
Schema::struct_()
.endian(Endian::Big)
.field("channel_id", Schema::uint32())
.field("length", Schema::uint32())
.build()
// -> {
// "kind": "struct",
// "endian": "big",
// "fields": [
// { "name": "channel_id", "kind": "uint32" },
// { "name": "length", "kind": "uint32" }
// ]
// }
Composite builders
struct_(), union_(), array_of(), record_of() are the composite
constructors. struct_() and union_() need additional setters to
populate their children:
impl Schema {
/// Add a field to a struct (or a field-name-discriminator union).
/// Field order is load-bearing for binary layouts (packed mode
/// field order = byte order — the `fields` array is ordered).
/// The field's schema is built from the passed `Schema`.
pub fn field(mut self, name: &str, field: Schema) -> Self;
/// Mark fields as required (standard JSON Schema `required` keyword).
/// Only meaningful for `object()` (standard JSON Schema) — BAST
/// structs require all declared fields present (the validator
/// enforces this). Can be called multiple times; required names
/// accumulate.
pub fn required(mut self, names: &[&str]) -> Self;
/// Set the items schema for a standard `array` type.
pub fn items(mut self, item: Schema) -> Self;
/// Set the additionalProperties schema for a standard `object` type.
pub fn additional_properties(mut self, props: Schema) -> Self;
/// Add a variant to a union. `disc_value` is the stringified
/// discriminator value (mapping key). The variant schema is built
/// from the passed `Schema`.
pub fn mapping(mut self, disc_value: &str, variant: Schema) -> Self;
}
field appends a { "name": ..., "kind": <field.build()>, ... }
entry to the struct/union's fields array, extracting field-level
annotations (endian, align, encoding, maxLength) from the
passed Schema. Repeated calls append in order. Field order in the
built Value is the call order.
required sets the standard JSON Schema "required" array. The
builder does not check that the named fields exist (that's a
compile-time check at AlkTypeEngine::compile, per ADR-004's
load-time validation strategy — see ADR-009
§"What the builder is not"). Calling required multiple times
accumulates names:
Schema::object()
.field("path", Schema::string_().max_length(4096))
.field("offset", Schema::integer().minimum(0))
.field("length", Schema::integer().minimum(0))
.required(["path"])
.required(["offset", "length"])
.build()
// -> {
// "type": "object",
// "properties": { "path": {...}, "offset": {...}, "length": {...} },
// "required": ["path", "offset", "length"]
// }
Constraint setters (standard JSON Schema)
For operation payload schemas (call's input_schema etc.):
impl Schema {
/// `minimum` (inclusive lower bound for numbers/integers).
pub fn minimum(mut self, min: f64) -> Self;
/// `maximum` (inclusive upper bound for numbers/integers).
pub fn maximum(mut self, max: f64) -> Self;
/// `minLength` (minimum string length).
pub fn min_length(mut self, min: usize) -> Self;
/// `minItems` (minimum array length).
pub fn min_items(mut self, min: usize) -> Self;
/// `maxItems` (maximum array length).
pub fn max_items(mut self, max: usize) -> Self;
/// `format` (e.g. "date-time", "uri", "email").
pub fn format(mut self, fmt: &str) -> Self;
/// `title` (human-readable description).
pub fn title(mut self, t: &str) -> Self;
/// `description` (human-readable description).
pub fn description(mut self, d: &str) -> Self;
}
These set the corresponding standard JSON Schema keywords. They apply
to standard JSON Schema type schemas (e.g.,
Schema::string_().max_length(4096) sets maxLength, which on the
validate_json path is a JSON-Schema validation constraint). On a BAST
schema, max_length also serves as the aligned-mode fixed-size
reservation (ADR-003 §3) and the packed-mode validation constraint
(enforced by the BAST-native validator — see
validation.md).
.build()
impl Schema {
/// Produce the final `serde_json::Value`.
pub fn build(self) -> Value;
}
Consumes the builder and returns the assembled Value. The builder is
not Clone (to discourage partial builds); each Schema is consumed
once. To reuse a sub-schema, build it once and pass the Value to a
Schema::from_value constructor (below).
Schema::from_value — adopt an existing Value
impl Schema {
/// Adopt an existing JSON Schema `Value` as a `Schema`, for
/// composition with builder-constructed schemas. Does not validate
/// the schema; just wraps it.
pub fn from_value(value: Value) -> Self;
}
For consumers that have some schemas as JSON (e.g., loaded from a
TypeBox-produced file) and others built via the fluent API, and want
to compose them. from_value wraps the Value so it can be passed to
.field() / .items() / .mapping() like any other Schema.
Discriminator for union_()
union_() takes a Discriminator describing the union's dispatch
mechanism. This mirrors bast::BastDiscriminator (the parser's typed
view) but with a builder-friendly shape:
pub enum Discriminator {
/// Byte-offset discriminator (ADR-003 §4 Kind A).
/// `offset` is the byte position; `disc_type` is the BAST kind
/// of the discriminator (Uint8/Uint16/Uint32).
Byte {
offset: usize,
disc_type: AlkTypeKind, // restricted to Uint8/Uint16/Uint32
},
/// Field-name discriminator (ADR-003 §4 Kind B).
/// `name` is the field holding the discriminator value. The
/// discriminator field and any shared fields are declared via
/// `.field()` on the union builder.
Field {
name: String,
},
}
Byte-offset example (channels-style protocol dispatch):
let packet = Schema::union_(Discriminator::Byte {
offset: 0,
disc_type: AlkTypeKind::Uint8,
})
.mapping("5", Schema::ref_def("Read"))
.mapping("6", Schema::ref_def("Write"))
.mapping("101", Schema::ref_def("Status"))
.build();
// -> {
// "kind": "union",
// "discriminator": { "kind": "byte", "offset": 0, "type": "uint8" },
// "mapping": {
// "5": { "$ref": "#/$defs/Read" },
// "6": { "$ref": "#/$defs/Write" },
// "101": { "$ref": "#/$defs/Status" }
// }
// }
Field-name example (typedef.ts pattern):
let event = Schema::union_(Discriminator::Field { name: "type" })
.field("type", Schema::string())
.mapping("read", Schema::ref_def("Read"))
.mapping("write", Schema::ref_def("Write"))
.build();
// -> {
// "kind": "union",
// "discriminator": { "kind": "field", "name": "type" },
// "fields": [ { "name": "type", "kind": "string" } ],
// "mapping": { "read": {...}, "write": {...} }
// }
(Field-name-discriminator unions require a fields array declaring the
discriminator field — D-BAST-005. The builder emits fields only when
the discriminator is Field and at least one field was added.)
Definitions — named $defs for cross-reference
Definitions is a helper for building named $defs that schemas can
$ref by name, and for assembling a complete BAST document. This is
the ergonomics win for alkcall's OperationSpec, where
input/output/error schemas reference shared definitions (e.g.,
FileNotFound, RateLimited).
pub struct Definitions { /* ... */ }
impl Definitions {
pub fn new() -> Self;
/// Define a named schema. Returns a `Schema` that produces
/// `{"$ref": "#/$defs/<name>"}` — the JSON Pointer form BAST
/// requires (no `normalize_refs` step; refs are always full
/// pointers).
pub fn define(&mut self, name: &str, schema: Schema) -> Schema;
/// Like `define`, but the schema is an existing `Value` (adopted
/// via `Schema::from_value`).
pub fn define_value(&mut self, name: &str, value: Value) -> Schema;
/// Produce the `{"$defs": { ... }}` object.
pub fn build(self) -> Value;
/// Build a complete BAST document with `root_name` as the root
/// type. The root schema is inserted into `$defs` alongside any
/// previously defined entries. The resulting `Value` is ready for
/// `AlkTypeEngine::compile(&doc, root_name, mode, ...)`.
pub fn build_doc(self, root_name: &str, root: Schema) -> Value;
/// Merge the `$defs` into a top-level schema `Value`. If `top`
/// already has a `$defs` object, the definitions are merged into
/// it; otherwise a `$defs` key is inserted. For BAST documents,
/// prefer `build_doc` — it places the root type inside `$defs`
/// (where BAST requires it).
pub fn merge_into(self, top: &mut Value);
}
Usage (complete BAST document):
let mut defs = Definitions::new();
defs.define("Init", Schema::struct_().field("version", Schema::uint32()));
defs.define("Read", Schema::struct_()
.field("handle", Schema::bytes())
.field("offset", Schema::uint64())
.field("len", Schema::uint32()));
let doc = defs.build_doc("Packet", Schema::struct_()
.field("payload", Schema::union_(Discriminator::Byte {
offset: 0,
disc_type: AlkTypeKind::Uint8,
})
.mapping("1", Schema::ref_def("Init"))
.mapping("5", Schema::ref_def("Read"))));
// -> {
// "$defs": {
// "Init": { "kind": "struct", "fields": [ { "name": "version", "kind": "uint32" } ] },
// "Read": { "kind": "struct", "fields": [ ... ] },
// "Packet": { "kind": "struct", "fields": [
// { "name": "payload", "kind": {
// "kind": "union",
// "discriminator": { "kind": "byte", "offset": 0, "type": "uint8" },
// "mapping": { "1": { "$ref": "#/$defs/Init" }, "5": { "$ref": "#/$defs/Read" } }
// } }
// ] }
// }
// }
//
// Feed to AlkTypeEngine::compile(&doc, "Packet", LayoutMode::Packed, None)
// then validate incoming frames via engine.validate_bytes(&frame).
define returns a Schema (the $ref to the definition), so it can
be passed directly to .field() / .mapping() / .items() without a
separate ref_def call:
let op = Schema::object()
.field("error", defs.define("FileNotFound", /* ... */))
.build();
Schema::ref_def — reference a definition by name
impl Schema {
/// Produce a `{"$ref": "#/$defs/<name>"}` schema. The definition
/// must exist in the `$defs` of the top-level schema at compile
/// time. The builder does not check this.
pub fn ref_def(name: &str) -> Self;
}
For cases where the Definitions::define return value isn't handy
(e.g., referencing a definition defined elsewhere). Produces the same
{"$ref": "#/$defs/<name>"} form.
Usage Examples
Example 1: channels' 8-byte chunk header (binary layout, BAST)
use alktype::{Schema, Endian, Definitions};
let chunk_header = Schema::struct_()
.endian(Endian::Big)
.field("channel_id", Schema::uint32())
.field("length", Schema::uint32());
// Build a complete BAST document (single-type — one $defs entry).
let doc = Definitions::new().build_doc("ChunkHeader", chunk_header);
// -> {
// "$defs": {
// "ChunkHeader": {
// "kind": "struct",
// "endian": "big",
// "fields": [
// { "name": "channel_id", "kind": "uint32" },
// { "name": "length", "kind": "uint32" }
// ]
// }
// }
// }
//
// Feed to AlkTypeEngine::compile(&doc, "ChunkHeader", LayoutMode::Packed, None)
// then validate incoming frames via engine.validate_bytes(&frame).
Example 2: call's OperationSpec input schema (JSON payload, standard JSON Schema)
use alktype::Schema;
let read_file_input = Schema::object()
.field("path", Schema::string_().max_length(4096))
.field("offset", Schema::integer().minimum(0))
.field("length", Schema::integer().minimum(0))
.required(["path"])
.build();
// -> {
// "type": "object",
// "properties": {
// "path": { "type": "string", "maxLength": 4096 },
// "offset": { "type": "integer", "minimum": 0 },
// "length": { "type": "integer", "minimum": 0 }
// },
// "required": ["path"]
// }
//
// Stored in OperationSpec.input_schema; validated via the standard
// jsonschema validator (AlkTypeEngine::compile with Some(&read_file_input)
// for the validate_json path, or serde_json::from_slice then
// validate_json for wire frames).
Example 3: SFTP Packet union (binary layout, byte discriminator)
The SFTP wire shape is [type:u8][payload-struct] — a struct with a
union payload field. The engine requires a struct at the root
(OffsetMap::compute / SequentialReader::new both enforce this; a
Union is a field type within a struct, not a top-level schema). The
builder constructs the union wrapped in a struct, and $defs are
placed inside the document via build_doc so $refs resolve:
use alktype::{Definitions, Discriminator, AlkTypeKind, Schema};
let mut defs = Definitions::new();
defs.define("Init", Schema::struct_().field("version", Schema::uint32()));
defs.define("Open", Schema::struct_().field("path", Schema::string()).field("flags", Schema::uint32()));
defs.define("Read", Schema::struct_().field("handle", Schema::bytes()).field("offset", Schema::uint64()).field("len", Schema::uint32()));
defs.define("Write", Schema::struct_().field("handle", Schema::bytes()).field("offset", Schema::uint64()).field("data", Schema::bytes()));
defs.define("Status", Schema::struct_().field("code", Schema::uint32()).field("message", Schema::string()));
// A "Packet" is a struct with one field — the union. This mirrors
// SFTP's wire shape: [type:u8][payload-struct].
let doc = defs.build_doc("Packet", Schema::struct_()
.field("payload", Schema::union_(Discriminator::Byte {
offset: 0,
disc_type: AlkTypeKind::Uint8,
})
.mapping("1", Schema::ref_def("Init"))
.mapping("3", Schema::ref_def("Open"))
.mapping("5", Schema::ref_def("Read"))
.mapping("6", Schema::ref_def("Write"))
.mapping("101", Schema::ref_def("Status"))));
// Feed to AlkTypeEngine::compile(&doc, "Packet", LayoutMode::Packed, None)
// then validate incoming frames via engine.validate_bytes(&frame).
Example 4: OperationSpec error schemas (named $defs, standard JSON Schema)
use alktype::{Definitions, Schema};
let mut defs = Definitions::new();
let file_not_found = defs.define("FileNotFound",
Schema::object()
.field("path", Schema::string_())
.field("errno", Schema::integer())
.required(["path", "errno"])
);
let rate_limited = defs.define("RateLimited",
Schema::object()
.field("retry_after_ms", Schema::integer().minimum(0))
.required(["retry_after_ms"])
);
// An operation's error schemas reference these definitions
let op_errors = vec![
ErrorDefinition {
code: "FILE_NOT_FOUND".to_string(),
description: "File not found".to_string(),
schema: Schema::ref_def("FileNotFound").build(),
http_status: Some(404),
},
ErrorDefinition {
code: "RATE_LIMITED".to_string(),
description: "Rate limited".to_string(),
schema: Schema::ref_def("RateLimited").build(),
http_status: Some(429),
},
];
// `$defs` is built once and stored alongside the OperationSpec.
// (For the validate_json path, compile with Some(&defs.build()) as the
// json_schema argument — but typically OperationSpec schemas are
// validated directly via jsonschema, not via AlkTypeEngine.)
Design Decisions
| Decision | ADR | Summary |
|---|---|---|
| Builder API for schema construction | ADR-009 | Fluent Rust API producing serde_json::Value; covers BAST kinds + standard JSON Schema; resolves OQ-003 |
| BAST format + two output formats | ADR-BAST | struct_() → BAST, object() → standard JSON Schema (D-BAST-008) |
| Schema annotations | ADR-003 | The annotation semantics the builder's setters produce (location moved to BAST type-level properties) |
| Load-time validation strategy | ADR-004 | The builder does not pre-validate; compile-time is the validation point |
Open Questions
None specific to the builder. OQ-003 (the original "should we build a
builder API") is resolved by this spec / ADR-009. OQ-004
(Discriminator::Field name type — &str or String) is resolved:
String, for ownership simplicity (the builder consumes Self on
setters; &str would require a lifetime parameter on Discriminator
and transitively on Schema::union_). See
open-questions.md.
References
- ADR-009 — the decision this spec implements
- ADR-BAST — the BAST format and the two-output-format decision (D-BAST-008)
- ADR-001 — scope boundaries this module extends; "schemas are JSON" principle
- ADR-003 — the annotation semantics the builder's setters produce
bast-format.md— the normative BAST format specification (the output format forstruct_())- schema-layer.md — the BAST kinds and parser
- validation.md — the validation layer that consumes
builder output (via
AlkTypeEngine::compile) @alkdev/alknet: docs/architecture/crates/call/operation-registry.md—OperationSpec(the alkcall consumer for the JSON-payload role)@alkdev/alknet: docs/architecture/crates/channels/channels-wire.md— the 8-byte chunk header (the alkcall consumer for the binary-layout role)