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:
1 parent
08e7df2aa0
commit
44d4b496e8
10 files changed
+95
-42
No files matched your search
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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"] }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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). |
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$defs": {
|
||||||
|
"ChunkHeader": {
|
||||||
|
"kind": "struct",
|
||||||
|
"endian": "big",
|
||||||
|
"fields": [
|
||||||
|
{ "name": "channel_id", "kind": "uint32" },
|
||||||
|
{ "name": "length", "kind": "uint32" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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!(
|
||||||
|
|||||||
Reference in new issue
Block a user