docs(arch): ADR-057 — alknet-tty does not depend on alknet-call (self-contained negotiation framing)

The earlier specs claimed alknet-tty reuses alknet-call's
FrameFramedReader/FrameFramedWriter 'framing utility' (ADR-052 §6,
ADR-003 Am. 1). 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 utility. alknet-tty's
negotiation payload is a NegotiateRequest, not an EventEnvelope, so the
claimed reuse did not exist in a usable form.

ADR-057: alknet-tty implements its own ~30-line length-prefixed
framing directly on tokio AsyncRead/AsyncWrite. The format coincides
with alknet-call's framing by convention (both are length-prefixed
JSON); the implementations are independent. alknet-tty depends on
alknet-core only — no alknet-call dependency. The ~30 lines of framing
is an idiom, not a domain abstraction worth a cross-crate dependency.

Updates:
- ADR-003 Amendment 2: clarifies alknet-tty does not depend on
  alknet-call; the Am. 1 protocol-foundation exception remains for
  alknet-http/agent/napi (actual type reuse), not alknet-tty.
- ADR-052 §6: framing is self-contained in alknet-tty; format coincides
  by convention, not by code reuse.
- overview.md: dependency block drops alknet-call; new 'Why no
  alknet-call dependency' subsection explains the unsound-reuse finding.
- tty-wire.md, tty-backend.md, tty README, crates/tty/README.md,
  top-level README: ADR tables, Design Decisions tables, and dependency
  edges updated to reflect alknet-core-only dependency.
- ADR-053, ADR-054: References updated to ADR-003 Am. 2 + ADR-057.
This commit is contained in:
glm-5.2 committed 2026-07-07 11:15:52 +00:00
1 parent 25f0f55592
commit dbac972aaa
10 files changed
+297 -76

No files matched your search

@@ -77,12 +77,10 @@ separate ALPN, not a set of operations in the call protocol's
The bidi stream has two phases:
- **Negotiation (JSON carriage).** The client opens a bidi stream and
writes a single length-prefixed JSON frame — the same 4-byte
big-endian length prefix + UTF-8 JSON body framing as alknet-call's
`FrameFramedReader`/`FrameFramedWriter`
(`crates/alknet-call/src/protocol/wire.rs`). The frame carries the
terminal parameters and backend selector the server needs to allocate
the session:
writes a single length-prefixed JSON frame — a 4-byte big-endian
length prefix + UTF-8 JSON body. The frame carries the terminal
parameters and backend selector the server needs to allocate the
session:
```json
{
@@ -209,17 +207,23 @@ chunk framing (stream_type byte first); the `0x00`-as-length-prefix vs
`0x00`-as-invalid-stream_type disambiguation is what makes the two
distinguishable on the wire.
### 6. Negotiation frame reuses alknet-call's framing, not its types
### 6. Negotiation framing is self-contained in alknet-tty (no alknet-call dependency)
The 4-byte length prefix + JSON body is byte-identical to
`alknet_call::protocol::wire::FrameFramedReader`/`FrameFramedWriter`.
alknet-tty reuses the *framing utility* (the length-prefix read/write),
not the `EventEnvelope` type — the negotiation payload is a
tty-specific struct (`NegotiateRequest`), not a `call.requested` event.
This keeps alknet-tty's dependency on alknet-call limited to the framing
codec, not the call protocol's type system. (See ADR-053 for the
dependency edge and ADR-003 Amendment 1 for the protocol-foundation
exception.)
The 4-byte length prefix + JSON body is implemented in alknet-tty as a
small, self-contained module (~30 lines: read 4-byte BE length,
bounds-check, read N bytes; write the inverse). The format coincides
with alknet-call's `EventEnvelope` framing by convention (both are
length-prefixed JSON) — not by code reuse. alknet-tty does not depend on
alknet-call: the negotiation payload is a tty-specific struct
(`NegotiateRequest`), not a `call.requested` `EventEnvelope`, and
alknet-call's `FrameFramedReader::read_frame()` is hardcoded to
deserialize `EventEnvelope` (the length-prefix read and the
type-specific deserialize are one entangled call), so it is not reusable
for a different payload type. alknet-tty implements its own framing on
tokio's `AsyncRead`/`AsyncWrite`. See [ADR-057](057-alknet-tty-no-alknet-call-dep.md)
for the decision (and the three options considered: duplicate / promote
to alknet-core / use alknet-call) and [ADR-003](003-crate-decomposition.md)
Amendment 2 for the dependency-edge clarification.
## Consequences
@@ -333,8 +337,10 @@ not.
versions
- [ADR-007](007-bistream-type-definition.md) — handler receives a
`Connection`, accepts bidi streams
- [ADR-003](003-crate-decomposition.md) Amendment 1 — alknet-call as
protocol-foundation crate (framing utility reuse)
- [ADR-003](003-crate-decomposition.md) Amendment 2 — alknet-tty does
not depend on alknet-call; self-contained negotiation framing
- [ADR-057](057-alknet-tty-no-alknet-call-dep.md) — the dependency-edge
decision this ADR's §6 reflects
- [ADR-012](012-call-protocol-stream-model.md) — the call protocol's
stream model (which tty is *not* using for the body, by design)
- [ADR-049](049-streaming-handler-for-subscriptions.md) — the