First speccing pass for alknet-http (HTTP interface crate: h2/http1.1/h3 server + from_openapi/to_openapi/from_mcp/to_mcp adapters). Specs (crates/http/): - README.md, overview.md — crate index, two-roles-in-one-crate framing, adapter location map, feature gates (h3, mcp), no-env-vars invariant - http-server.md — HttpAdapter for h2/http1.1, axum over QUIC stream, Bearer auth, SSE projection for subscriptions, /healthz, stealth decoy - http-adapters.md — from_openapi (reqwest) and to_openapi (projection), error fidelity (HTTP_<status> per ADR-023), type definitions - http-mcp.md — from_mcp/to_mcp (feature-gated), streamable-HTTP-only - webtransport.md — h3/WebTransport handler, browser streaming path, HTTP/3 request vs WebTransport session distinguished at framing layer ADRs: - ADR-036 HTTP-to-Call Operation Mapping (Proposed) — direct path mapping; to_openapi is projection, not router (the load-bearing one-way door from Phase 0 DH-3) - ADR-037 MCP Stdio Transport Exclusion (Proposed) — streamable HTTP only; stdio is not built (RCE-vector security position) - ADR-038 HTTP/3 and WebTransport as First-Class HTTP Transports (Proposed) — corrects the Phase 0 DH-2 deferral framing; h3 is in scope, not deferred, per ADR-009 §'What this framework is NOT' - ADR-039 HTTP Server and Client Host Colocated in alknet-http (Proposed) — one crate for server + client host (shared HTTP deps, shared operation-spec->HTTP mapping) - ADR-003 Amendment 1 — clarifies alknet-call is a protocol-foundation crate (the alknet-http -> alknet-call dependency edge) Open questions (OQ-38, OQ-39, OQ-40 added under 'Theme: alknet-http'): - OQ-38 WebTransport relay-as-proxy scope (genuine scope question, not a deferral — the decision is made when the use case becomes concrete) - OQ-39 to_openapi published-spec versioning (one-way after first publication) - OQ-40 reqwest client config and connection pooling (two-way-door) Architecture README and overview updated with doc table, ADR table (036-039), current-state note, and crate graph (alknet-http -> alknet-call edge). Reviewed by architecture-reviewer subagent: 3 critical, 4 warning, 5 suggestion issues found and fixed (missing ADR-039, WebTransport stream routing conflation, undefined types, stale OQ-37 deferral language, README OQ table completeness, Bearer-only attribution, cross-references, ADR-038 ALPN quote, feature-gate placeholder, MCP temporal language).
102 lines
6.2 KiB
Markdown
102 lines
6.2 KiB
Markdown
# ADR-003: Crate Decomposition
|
|
|
|
## Status
|
|
|
|
Accepted
|
|
|
|
## Context
|
|
|
|
The previous alknet-core crate was a monolith containing transport, interface, server, client, call, auth, config, socks5, credentials, and HTTP — all in one crate with interdependent modules. This created coupling (interface types depended on auth, server depended on call, everything depended on config) and made it impossible to use individual components independently.
|
|
|
|
The new ALPN dispatch model eliminates the need for a shared interface layer. Each handler is self-contained — it receives a byte stream and manages its own protocol. This naturally decomposes into separate crates.
|
|
|
|
Key constraints:
|
|
- Protocol crates must depend on alknet-core for auth/identity/config — but not on each other
|
|
- alknet-vault is already standalone (no alknet-core dependency) and must remain so (see ADR-008)
|
|
- The CLI binary assembles everything — it's the only crate that depends on all handler crates
|
|
- Handlers with protocol-agnostic cores (SFTP, call protocol) preserve the WASM door — browser clients can implement the wire format over WebTransport (see ADR-009, ADR-013)
|
|
- alknet-call includes the call protocol client and adapter traits, not just the server side — this enables alknet-agent and alknet-napi to use it for remote invocation
|
|
- Rust is the canonical implementation language. TypeScript is a reference/browser adaptation, not a parallel implementation (see ADR-013)
|
|
|
|
## Decision
|
|
|
|
The workspace decomposes into the following crates:
|
|
|
|
| Crate | Responsibility | Depends on |
|
|
|-------|---------------|------------|
|
|
| `alknet-core` | ProtocolHandler trait, ALPN router, endpoint, BiStream, AuthContext, IdentityProvider, config, ArcSwap dynamic config | tokio, quinn, rustls, irpc, iroh (feature-gated, added by ADR-010) |
|
|
| `alknet-vault` | Local key vault: BIP39/SLIP-0010/AES-GCM key derivation, encryption | (standalone, no alknet-core) |
|
|
| `alknet-ssh` | SshAdapter (russh, SOCKS5, port forwarding) | alknet-core, russh |
|
|
| `alknet-call` | CallAdapter (JSON-RPC via irpc, operation registry, pub/sub, access control, call protocol client, adapter traits) | alknet-core, irpc |
|
|
| `alknet-agent` | Agent service: LLM execution loop (forked aisdk), tool dispatch via call protocol, provider key retrieval via vault | alknet-call |
|
|
| `alknet-git` | GitAdapter (gix, pkt-line protocol) | alknet-core, gix |
|
|
| `alknet-sftp` | SftpAdapter (russh-sftp protocol core) | alknet-core, russh-sftp |
|
|
| `alknet-msg` | MessageAdapter (E2E encryption, mixnet) | alknet-core |
|
|
| `alknet-http` | HttpAdapter (axum, REST API, MCP endpoint) | alknet-core, axum |
|
|
| `alknet-dns` | DnsAdapter (hickory-proto, pkarr, service discovery) | alknet-core, hickory-proto |
|
|
| `alknet-napi` | Node.js native addon — thin NAPI projection of the call protocol client | alknet-call, napi-rs |
|
|
| `alknet` | CLI binary — registers handlers, starts endpoint | all handler crates, alknet-vault |
|
|
|
|
Dependency flow:
|
|
```
|
|
alknet-vault (standalone)
|
|
alknet-core ← all handler crates ← alknet (CLI)
|
|
alknet-call ← alknet-agent
|
|
alknet-call ← alknet-napi
|
|
```
|
|
|
|
No handler crate depends on another handler crate. Cross-handler communication goes through the call protocol (alknet-call) or through alknet-core's endpoint.
|
|
|
|
alknet-agent depends on alknet-call (not alknet-core directly) because it uses the call protocol client for tool dispatch and the operation registry for tool registration. It receives LLM provider keys through capabilities injected at the assembly layer (from alknet-vault), never from environment variables and never over the call protocol. See ADR-008 and ADR-014.
|
|
|
|
alknet-napi is a thin projection layer — it exposes the Rust call protocol client to Node.js via NAPI. It does not contain business logic or adapter implementations. See ADR-013.
|
|
|
|
## Consequences
|
|
|
|
**Positive:**
|
|
- Each handler can be developed, tested, and versioned independently
|
|
- WASM-compatible handlers (sftp, call) don't pull in heavy dependencies (russh, axum)
|
|
- alknet-vault remains standalone — no circular dependency risk
|
|
- New handlers are added by creating a crate and registering it with the endpoint
|
|
- Clean separation of concerns — each crate has one job
|
|
|
|
**Negative:**
|
|
- More crates to manage in the workspace — workspace Cargo.toml and version coordination
|
|
- Shared types (AuthContext, BiStream) must live in alknet-core — if they change, all handlers recompile
|
|
- The CLI binary has a large dependency tree (all handlers) — but this is expected for a binary that assembles everything
|
|
- Testing cross-handler behavior requires integration tests in the CLI or a test utility crate
|
|
|
|
## References
|
|
|
|
- Pivot proposal: `docs/research/pivot/alpn-service-architecture.md`
|
|
- ADR-001: ALPN-based protocol dispatch
|
|
- ADR-002: ProtocolHandler trait
|
|
- ADR-004: Auth as shared core (IdentityProvider)
|
|
- ADR-005: irpc as call protocol foundation
|
|
|
|
## Amendments
|
|
|
|
### Amendment 1 (2026-06-29): `alknet-call` is a protocol-foundation crate
|
|
|
|
The Decision table lists `alknet-call` as a handler crate that "depends
|
|
on alknet-core, irpc." The dependency-flow diagram and the "No handler
|
|
crate depends on another handler crate" rule were written before
|
|
`alknet-http` (which implements `from_openapi`/`from_mcp`/`to_openapi`/
|
|
`to_mcp` and therefore needs `alknet-call`'s `OperationSpec`, `Handler`,
|
|
`HandlerRegistration`, and `OperationAdapter` trait) was specced.
|
|
|
|
**Clarification:** `alknet-call` is both a handler crate (it implements
|
|
`ProtocolHandler` on ALPN `alknet/call`) *and* the protocol-foundation
|
|
crate that `alknet-agent`, `alknet-napi`, and `alknet-http` consume for
|
|
the operation registry, adapter contract, and call client. The "no
|
|
handler crate depends on another handler crate" rule applies to peer
|
|
handler crates (e.g., `alknet-http` does not depend on `alknet-ssh`);
|
|
`alknet-call` is a protocol-foundation crate in the same spirit that
|
|
`alknet-core` is, just at a different layer (operations/RPC vs.
|
|
transport/auth/config).
|
|
|
|
`alknet-http` depending on `alknet-call` is "HTTP uses the call protocol
|
|
types," not "HTTP depends on SSH." This is within the spirit of this
|
|
ADR's decomposition. The `alknet-call` → `alknet-http` edge is recorded
|
|
in the `alknet-http` spec (`crates/http/overview.md`) and in the adapter
|
|
location map (`crates/call/client-and-adapters.md`). |