refactor: drop alktype dependency; add BAST doc for chunk header

- Remove alktype from Cargo.toml (its only usage was a thin wrapper
  over jsonschema::options().build())
- Replace alktype::validation::build_validator with direct jsonschema
  in dispatch.rs
- Add docs/architecture/chunk-header.bast.json — the chunk header's
  BAST (Binary Abstract Syntax Tree) machine-readable wire spec
- Embed as channels::wire::CHUNK_HEADER_BAST via include_str! so
  downstream Rust crates can consume it without a file lookup
- Add test asserting the embedded BAST doc is valid JSON and matches
  the wire format
- Update AGENTS.md §10 and implementation-specialist.md: BAST docs
  are the contract; trivial/hot-path formats stay hand-rolled,
  complex formats use the alktype engine or codegen

Verification:
- cargo test: 543 passed, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean
- BAST doc compiles + round-trips against alktype v0.2.0 engine
This commit is contained in:
glm-5.2 committed 2026-08-17 06:08:13 +00:00
1 parent 08e7df2aa0
commit 44d4b496e8
10 files changed
+95 -42

No files matched your search

+10 -3
View File
@@ -241,9 +241,16 @@ Read `AGENTS.md` at project root for full details. Key rules:
`AuthToken`, `Capabilities`, `OwnershipProvider`, `HandlerError`, `AuthToken`, `Capabilities`, `OwnershipProvider`, `HandlerError`,
`StreamError` live in this crate. Do not add a separate `alkcore` dependency. `StreamError` live in this crate. Do not add a separate `alkcore` dependency.
Keep them lean (no TLS, no transport coupling, no endpoint/accept-loop). Keep them lean (no TLS, no transport coupling, no endpoint/accept-loop).
10. **`alktype` dependency** — use `alktype` for binary layout (channels chunk 10. **BAST documents for wire formats** — every binary wire format carries a
header, future binary payload schemas) and JSON payload schema validation BAST (Binary Abstract Syntax Tree) document as its machine-readable spec
(`OperationSpec`'s `input_schema`/`output_schema`). Do not roll your own. (e.g. the channels chunk header's `docs/architecture/chunk-header.bast.json`,
embedded as `CHUNK_HEADER_BAST`). BAST is plain JSON — no dependency
required to author or consume it. The `alktype` crate compiles BAST into
readers/writers/validators; future codegen derives language-specific
implementations. Trivial or hot-path formats (chunk header, tty framing)
stay hand-rolled with the BAST doc as the contract; complex formats (sftp)
use the alktype engine or codegen. Do not roll your own offset map or
validator for complex formats.
11. **Feature flags** — transports may be feature-gated if the need arises. The 11. **Feature flags** — transports may be feature-gated if the need arises. The
base crate should compile lean (no `quinn`, no `iroh` unless the feature is base crate should compile lean (no `quinn`, no `iroh` unless the feature is
on). Verify both `cargo test` (default) and `cargo test --all-features` pass on). Verify both `cargo test` (default) and `cargo test --all-features` pass
+12 -5
View File
@@ -136,11 +136,18 @@ session, not just spawned implementation agents.
dial and the TLS config are concerns of the consumer, not of this dial and the TLS config are concerns of the consumer, not of this
crate. See ADR-007, ADR-008, ADR-009. crate. See ADR-007, ADR-008, ADR-009.
10. **`alktype` dependency** — use `alktype` for binary layout (the 10. **BAST documents for wire formats** — every binary wire format in
channels chunk header, future binary payload schemas) and JSON this crate carries a BAST (Binary Abstract Syntax Tree) document as
payload schema validation (`OperationSpec`'s `input_schema`/ its machine-readable spec (e.g. the channels chunk header's
`output_schema`). Do not roll your own offset map or validator. See `docs/architecture/chunk-header.bast.json`, embedded as
the alktype crate at `/workspace/@alkdev/alktype`. `CHUNK_HEADER_BAST`). BAST is plain JSON — no dependency required
to author or consume it. The `alktype` crate compiles BAST into
readers/writers/validators; future codegen derives
language-specific implementations. Trivial or hot-path formats
(chunk header, tty framing) stay hand-rolled with the BAST doc as
the contract; complex formats (sftp) use the alktype engine or
codegen. Do not roll your own offset map or validator for complex
formats. See the alktype crate at `/workspace/@alkdev/alktype`.
11. **Feature flags** — transports may be feature-gated if the need 11. **Feature flags** — transports may be feature-gated if the need
arises. The base crate should compile lean (no `quinn`, no `iroh` arises. The base crate should compile lean (no `quinn`, no `iroh`
Generated
+1 -29
View File
@@ -29,7 +29,6 @@ dependencies = [
name = "alkcall" name = "alkcall"
version = "0.1.1" version = "0.1.1"
dependencies = [ dependencies = [
"alktype",
"async-trait", "async-trait",
"bytes", "bytes",
"futures", "futures",
@@ -44,16 +43,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "alktype"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a102e5ec20cc0209b7c72d4abaa2c9b4f44b72a4cee2706be98d19125c2318dc"
dependencies = [
"jsonschema",
"serde_json",
]
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.21" version = "0.2.21"
@@ -332,12 +321,6 @@ dependencies = [
"foldhash", "foldhash",
] ]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]] [[package]]
name = "icu_collections" name = "icu_collections"
version = "2.2.0" version = "2.2.0"
@@ -441,16 +424,6 @@ dependencies = [
"icu_properties", "icu_properties",
] ]
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.1",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.18" version = "1.0.18"
@@ -756,7 +729,7 @@ dependencies = [
"ahash", "ahash",
"fluent-uri", "fluent-uri",
"getrandom 0.3.4", "getrandom 0.3.4",
"hashbrown 0.16.1", "hashbrown",
"itoa", "itoa",
"micromap", "micromap",
"parking_lot", "parking_lot",
@@ -841,7 +814,6 @@ version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [ dependencies = [
"indexmap",
"itoa", "itoa",
"memchr", "memchr",
"serde", "serde",
-1
View File
@@ -18,7 +18,6 @@ name = "alkcall"
default = [] default = []
[dependencies] [dependencies]
alktype = "0.1.0"
jsonschema = { version = "0.46", default-features = false } jsonschema = { version = "0.46", default-features = false }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
+2 -2
View File
@@ -272,8 +272,8 @@ feature flags) or in the downstream alknet crate.
- `@alkdev/alknet: docs/architecture/` — the source architecture docs - `@alkdev/alknet: docs/architecture/` — the source architecture docs
these were ported from (renumbered from alknet ADR-001..094 to alkcall these were ported from (renumbered from alknet ADR-001..094 to alkcall
ADR-001..045) ADR-001..045)
- `@alkdev/alktype` — the binary struct engine, used for channels chunk - `@alkdev/alktype` — the binary struct engine; compiles BAST documents
header layout and JSON payload schema validation (e.g. `chunk-header.bast.json`) into readers/writers/validators
- `@alkdev/pubsub` — the TypeScript EventEnvelope prior art the call - `@alkdev/pubsub` — the TypeScript EventEnvelope prior art the call
wire format was derived from wire format was derived from
+8
View File
@@ -21,6 +21,14 @@ multiplexing on the `BiStream` the channels layer gives it.
8 bytes of header, followed by `length` bytes of opaque payload. 8 bytes of header, followed by `length` bytes of opaque payload.
The machine-readable spec is the BAST document
[`chunk-header.bast.json`](chunk-header.bast.json) (embedded in the
crate as `channels::wire::CHUNK_HEADER_BAST`). BAST is plain JSON —
any language can consume it; the `alktype` crate compiles it into
readers/writers/validators, and future codegen derives
language-specific implementations. The Rust implementation is
hand-rolled (the hot path); the BAST document is the contract.
| field | offset | width | meaning | | field | offset | width | meaning |
|-------|--------|-------|---------| |-------|--------|-------|---------|
| `channel_id` | 0 | 4 (BE) | The logical channel this chunk belongs to. Channel 0 is pre-negotiated as `alk/call` (ADR-036). Channels 1..N are opened dynamically via `channel/open` (ADR-037). | | `channel_id` | 0 | 4 (BE) | The logical channel this chunk belongs to. Channel 0 is pre-negotiated as `alk/call` (ADR-036). Channels 1..N are opened dynamically via `channel/open` (ADR-037). |
+12
View File
@@ -0,0 +1,12 @@
{
"$defs": {
"ChunkHeader": {
"kind": "struct",
"endian": "big",
"fields": [
{ "name": "channel_id", "kind": "uint32" },
{ "name": "length", "kind": "uint32" }
]
}
}
}
+5 -1
View File
@@ -355,7 +355,11 @@ These were checked and are correct — the rename does not affect them:
- **No logic changes:** the rename is purely mechanical. No function - **No logic changes:** the rename is purely mechanical. No function
signatures, trait bounds, or control flow change. ✓ signatures, trait bounds, or control flow change. ✓
- **No new dependencies, no feature flag changes.** ✓ - **No new dependencies, no feature flag changes.** ✓
- **`alktype` dependency unaffected.** ✓ - **`alktype` dependency:** dropped in a follow-up commit (the only
usage, `alktype::validation::build_validator`, was a thin wrapper
over `jsonschema`; the chunk header BAST document
`docs/architecture/chunk-header.bast.json` carries the wire-format
spec without the dependency). ✓
- **483 tests pass before the rename; they should all pass after.** ✓ - **483 tests pass before the rename; they should all pass after.** ✓
- **The `alknet/` prefix in `derive_alpn_from_op_name` is the only - **The `alknet/` prefix in `derive_alpn_from_op_name` is the only
non-mechanical change** — the function's logic stays the same, only non-mechanical change** — the function's logic stays the same, only
+44
View File
@@ -34,6 +34,17 @@ pub const MAX_CHUNK_LEN: u32 = 16 * 1024 * 1024;
/// without an explicit open op exchange. /// without an explicit open op exchange.
pub const CHANNEL_ID_ZERO: u32 = 0; pub const CHANNEL_ID_ZERO: u32 = 0;
/// The chunk header's BAST (Binary Abstract Syntax Tree) document —
/// the machine-readable wire-format spec. The canonical copy is
/// `docs/architecture/chunk-header.bast.json`; this const embeds it so
/// downstream Rust crates can consume it without a file lookup. BAST is
/// plain JSON consumable by any language; the `alktype` crate compiles
/// it into readers/writers/validators, and future codegen derives
/// language-specific implementations from it. The hand-rolled
/// [`parse_header`]/[`write_header`] functions are the hot path; the
/// BAST document is the contract.
pub const CHUNK_HEADER_BAST: &str = include_str!("../../docs/architecture/chunk-header.bast.json");
/// The parsed 8-byte chunk header. /// The parsed 8-byte chunk header.
/// ///
/// `length = 0` is the EOF sentinel — the reassembled stream interprets /// `length = 0` is the EOF sentinel — the reassembled stream interprets
@@ -251,6 +262,39 @@ mod tests {
} }
} }
#[test]
fn chunk_header_bast_is_valid_json_and_describes_the_wire_format() {
let doc: serde_json::Value =
serde_json::from_str(CHUNK_HEADER_BAST).expect("BAST doc is valid JSON");
let def = doc
.get("$defs")
.and_then(|d| d.get("ChunkHeader"))
.expect("ChunkHeader def present");
assert_eq!(def.get("kind").and_then(|v| v.as_str()), Some("struct"));
assert_eq!(def.get("endian").and_then(|v| v.as_str()), Some("big"));
let fields = def
.get("fields")
.and_then(|v| v.as_array())
.expect("fields");
assert_eq!(fields.len(), 2);
assert_eq!(
fields[0].get("name").and_then(|v| v.as_str()),
Some("channel_id")
);
assert_eq!(
fields[0].get("kind").and_then(|v| v.as_str()),
Some("uint32")
);
assert_eq!(
fields[1].get("name").and_then(|v| v.as_str()),
Some("length")
);
assert_eq!(
fields[1].get("kind").and_then(|v| v.as_str()),
Some("uint32")
);
}
#[tokio::test] #[tokio::test]
async fn read_header_round_trips_through_duplex() { async fn read_header_round_trips_through_duplex() {
let (mut reader, mut writer) = tokio::io::duplex(64); let (mut reader, mut writer) = tokio::io::duplex(64);
+1 -1
View File
@@ -353,7 +353,7 @@ impl Dispatcher {
.registry .registry
.registration(&operation_name) .registration(&operation_name)
.and_then(|r| r.spec.publish_schema.as_ref()) .and_then(|r| r.spec.publish_schema.as_ref())
.and_then(|schema| match alktype::validation::build_validator(schema) { .and_then(|schema| match jsonschema::options().build(schema) {
Ok(v) => Some(v), Ok(v) => Some(v),
Err(e) => { Err(e) => {
warn!( warn!(