docs: port architecture specs and ADRs from alknet-http; write new alkhttp ADRs 067-070

Phase 1 (SDD) — architecture documentation:

Ported specs (adapted for alkcall, producer/consumer terms, 6-endpoint
gateway, channels-over-WS, Sub/Pub operation types):
- overview.md, http-server.md, http-adapters.md, http-mcp.md
- README.md index (rewritten for alkhttp)

New ADRs:
- 067: WebSocket carries the channels protocol (8-byte chunk demux,
  channel 0 = alk/call, upgrade path /alk/channels)
- 068: gateway /publish endpoint for Pub operations (NDJSON body)
- 069: WebTransport out of scope in alkhttp (alknet concern)
- 070: from_wss consumer adapter (wss feature, tokio-tungstenite)

Ported ADRs (25, same numbers, port notes + amendments where the
extraction changed facts): 001-004, 010, 014, 015, 017, 022, 023, 027,
034, 036, 037, 039, 041, 042, 044, 045, 046, 047, 048, 049, 051, 066.

websocket.md rewritten for the channels session; open-questions.md
seeded (OQ-01 WS byte-stream adapter, OQ-02 /publish framing,
OQ-03 from_wss reconnect, OQ-04 browser client ownership).

Verified: cargo test, clippy -D warnings, fmt, doc --no-deps.
This commit is contained in:
2026-08-27 14:19:24 +00:00
parent 28c521b2f3
commit 320ea87b08
42 changed files with 11275 additions and 1 deletions
@@ -0,0 +1,173 @@
# ADR-003: Crate Decomposition
*Ported from alknet ADR-003 (Crate Decomposition); re-targeted to alkhttp.*
## Status
Accepted
## Context
The previous architecture had a monolithic core crate 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 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 the shared core for auth/identity/config — but not on each other
- The vault crate (alkvault) is already standalone (no 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)
- The call crate includes the call protocol client and adapter traits, not just the server side — this enables agent and NAPI consumers 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 alknet workspace decomposed into the following crates (now extracted and
published as alkvault, alkcall, and the per-protocol crates; alkhttp is the
HTTP interface crate):
| Crate | Responsibility | Depends on |
|-------|---------------|------------|
| shared core (now `alkcall`, which vendors the core types) | ProtocolHandler trait, ALPN router, endpoint, BiStream, AuthContext, IdentityProvider, config, ArcSwap dynamic config | tokio, quinn, rustls, iroh (feature-gated, added by ADR-010) |
| `alkvault` | Local key vault: BIP39/SLIP-0010/AES-GCM key derivation, encryption | (standalone, no core dependency) |
| `alknet-ssh` | SshAdapter (russh, SOCKS5, port forwarding) | core, russh |
| call crate (now `alkcall`) | CallAdapter (JSON-RPC via hand-rolled EventEnvelope framing, operation registry, pub/sub, access control, call protocol client, adapter traits) | core |
| `alknet-agent` | Agent service: LLM execution loop (forked aisdk), tool dispatch via call protocol, provider key retrieval via vault | call |
| `alknet-git` | GitAdapter (gix, pkt-line protocol) | core, gix |
| `alknet-sftp` | SftpAdapter (russh-sftp protocol core) | core, russh-sftp |
| `alknet-msg` | MessageAdapter (E2E encryption, mixnet) | core |
| `alkhttp` | HttpAdapter (axum, REST API, MCP endpoint) | alkcall, axum |
| `alknet-dns` | DnsAdapter (hickory-proto, pkarr, service discovery) | core, hickory-proto |
| `alknet-napi` | Node.js native addon — thin NAPI projection of the call protocol client | call, napi-rs |
| `alknet` | CLI binary — registers handlers, starts endpoint | all handler crates, alkvault |
Dependency flow:
```
alkvault (standalone)
core ← all handler crates ← alknet (CLI)
call ← alknet-agent
call ← alknet-napi
```
No handler crate depends on another handler crate. Cross-handler communication goes through the call protocol (alkcall) or through the core's endpoint.
alknet-agent depends on the call crate (not the 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 alkvault), 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)
- alkvault 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 the core crate — 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 (alknet mono-repo): `docs/research/pivot/alpn-service-architecture.md`
- [ADR-001](001-alpn-protocol-dispatch.md): ALPN-based protocol dispatch
- [ADR-002](002-protocol-handler-trait.md): ProtocolHandler trait
- [ADR-004](004-auth-as-shared-core.md): Auth as shared core (IdentityProvider)
- ADR-005: irpc as call protocol foundation (superseded by ADR-064)
## Amendments
### Amendment 1 (2026-06-29): the call crate is a protocol-foundation crate
The Decision table lists the call crate as a handler crate that "depends
on the core, irpc." The dependency-flow diagram and the "No handler
crate depends on another handler crate" rule were written before
the HTTP crate (which implements `from_openapi`/`from_mcp`/`to_openapi`/
`to_mcp` and therefore needs the call crate's `OperationSpec`, `Handler`,
`HandlerRegistration`, and `OperationAdapter` trait) was specced.
**Clarification:** the call crate is both a handler crate (it implements
`ProtocolHandler` on ALPN `alknet/call`) *and* the protocol-foundation
crate that alknet-agent, alknet-napi, and the HTTP crate 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., alkhttp does not depend on `alknet-ssh`);
the call crate is a protocol-foundation crate in the same spirit that
the core crate is, just at a different layer (operations/RPC vs.
transport/auth/config).
The HTTP crate depending on the call crate is "HTTP uses the call protocol
types," not "HTTP depends on SSH." This is within the spirit of this
ADR's decomposition. The call-crate → HTTP-crate edge is recorded
in the alkhttp crate overview (`overview.md`) and in the adapter
location map (see the alkcall crate docs, client-and-adapters).
### Amendment 2 (2026-07-07): alknet-tty does not depend on the call crate
Amendment 1's protocol-foundation framing was extended to alknet-tty in
an earlier draft ("alknet-tty depends on the call crate for the
`FrameFramedReader`/`FrameFramedWriter` framing utility"). A
pre-implementation sanity check found this was unsound:
`FrameFramedReader::read_frame()` is hardcoded to deserialize
`EventEnvelope` — the length-prefix read and the type-specific
deserialize are one entangled call, not a separable "framing utility."
alknet-tty's negotiation frame is a `NegotiateRequest`, not an
`EventEnvelope`, so `read_frame()` cannot return what alknet-tty needs;
the claimed reuse did not exist in a usable form.
**Clarification:** alknet-tty does **not** depend on the call crate.
alknet-tty implements its own length-prefixed framing (~30 lines: 4-byte
big-endian length + UTF-8 JSON body) directly on tokio's
`AsyncRead`/`AsyncWrite`. The format coincides with the call crate's
framing by convention (both are length-prefixed JSON); the
implementations are independent. The Amendment 1 protocol-foundation
exception remains for the HTTP/agent/napi consumers (which use the call
crate's `OperationSpec`/`Handler`/`OperationAdapter` types — actual type
reuse, not framing glue); it no longer covers alknet-tty. See
ADR-057 (alknet-tty-no-alknet-call-dep, in the alknet mono-repo ADRs)
for the full decision
and the three options considered (duplicate / promote to core / use
the call crate).
### Amendment 3 (2026-07-09): irpc is not a dependency of any crate
The Decision table listed `irpc` as a dependency of the core crate
("tokio, quinn, rustls, irpc, iroh") and the call crate
("core, irpc"). This was carried over from the previous architecture
and never verified against the implementation: **no `.rs` file in the
workspace ever imported irpc**. The call protocol's wire format
(the call protocol's `protocol/wire.rs` in the call crate) is
hand-rolled length-prefixed JSON; the `EventEnvelope` shape was derived
from the `@alkdev/pubsub` TypeScript prior art (ADR-013), not from irpc.
The dead `irpc` / `irpc-derive` workspace deps and the call-crate consumer
dep were removed in commit `668d777`. See
ADR-064 (irpc-never-integrated-hand-rolled-framing, in the alknet
mono-repo ADRs) for the full
record (ADR-005, which accepted "irpc as the call protocol foundation," is
superseded).
## Port notes
- This is the historical alknet decomposition ADR, ported because alkhttp's
dependency edges are defined here (Amendment 1 in particular). The table's
crate names are annotated in place where the extraction renamed them:
alknet-core + alknet-call merged into **alkcall** (which vendors the core
types); alknet-vault → **alkvault**; alknet-http → **alkhttp**. The
dependency-flow diagram keeps the generic "core"/"call" labels it
historically used; "call ← alknet-agent / call ← alknet-napi" describe
the alknet mono-repo, not alkhttp's own edges.
- Per the task instructions, both amendments are retained. Amendment 1's
statement of the alkhttp edge ("alknet-http depends on alknet-call")
now reads as **alkhttp depends on alkcall alone** — the "depends on
`alknet-core, axum`" dependency in the table is now `alkcall, axum`.
- Link targets `crates/http/overview.md` and
`crates/call/client-and-adapters.md` (mono-repo doc paths) replaced:
the former points to `overview.md` in `docs/architecture/`, the latter
is a textual "alkcall crate docs" reference. ADR-057/ADR-064 links are
textual (those ADRs are not ported to alkhttp).
- ADR-057 and ADR-064, and the code-path references
(`crates/alknet-call/src/protocol/wire.rs`), are alkcall-internal
concerns; referenced textually.