plan: revise for landed upstream changes and fold alknet-tty-local

Reflect what actually landed since the 2026-08-14 draft:
- alkcall 0.1.1: alk/tty + alk/channels ALPN rename (no public API change)
- alkcall Identity.resources now HashMap<String, Vec<String>>
- alkcall OwnershipProvider::owns gained action arg; OwnershipStore::record lost it
- alktype 0.2.0: kind-based BAST format (schema doc artifact, not a runtime dep)

Fold alknet-tty-local into the single crate as the feature-gated local
module (pty.rs + pipe.rs + backend.rs) — the cyclic-dep workaround it
required in the alknet mono-repo doesn't apply to a single crate.

Add Decision 7 (BAST schema as documentation artifact), Phase 0
(scaffold hygiene), Phase 4 BAST-schema step, OQ-6, and a BAST-drift
risk with a cheap test mitigation.

Cargo.toml: bump alkcall 0.1.0 -> 0.1.1; add futures = 0.3 as a direct
dep so the BoxFuture alias doesn't ride on a transitive.
This commit is contained in:
2026-08-17 08:57:14 +00:00
parent 09797de4eb
commit 2086817a90
2 changed files with 336 additions and 86 deletions
+334 -85
View File
@@ -1,7 +1,7 @@
# Project Setup Plan
Status: draft
Last updated: 2026-08-14
Status: draft (revised 2026-08-17 to reflect landed upstream changes)
Last updated: 2026-08-17
## Overview
@@ -10,10 +10,90 @@ standalone `alktty` crate — a producer/consumer protocol crate on top of
alkcall channels. This is the first protocol crate built on alkcall and
establishes patterns that `alktunnels` and others will follow.
The local backend (`alknet-tty-local`) is folded into `alktty` as a
feature-gated `local` submodule rather than kept as a sibling crate.
Both the original `alknet-tty` (the wire/adapter/backend-trait half) and
`alknet-tty-local` (the `portable_pty` + `tokio::process` half) are
ported together, so the single crate carries the full stack: the
`alk/tty` protocol + the one backend implementation that ships with it.
A docker or SSH backend would be a separate crate; the local backend is
small and the cyclic-dependency workaround it required in the alknet
mono-repo (see ADR-054) doesn't apply to a single crate with a feature
flag.
## Upstream changes that landed since the first draft
These were "open questions" or "upstream changes needed" in the
2026-08-14 draft and are now resolved. The plan reflects the as-built
shape, not the original intent.
### alkcall 0.1.1 — ALPN rename done
- `CHANNELS_ALPN`: `b"alknet/channels"``b"alk/channels"` (in
`alkcall/src/channels/adapter.rs`).
- Test at `channels/client.rs:767` uses `"alk/tty"` (was
`"alknet/tty"`).
- Semver showed no public API change → alkcall bumped 0.1.0 → 0.1.1.
- `alkcall` `Cargo.toml` dependency needs updating from `0.1.0` to
`0.1.1` (the scaffold still pins `0.1.0`).
### alkcall — `Identity.resources` shape
The alknet-core `Identity` had `resources: HashMap<String, String>` (a
flat resource-type → resource-id map). alkcall's `Identity` (in
`alkcall::core::auth`) has `resources: HashMap<String, Vec<String>>`
one resource type can carry multiple resource ids. The ported adapter
tests that build an `Identity` by hand need this shape; the production
code only reads `identity.scopes` (the scope-gate at negotiation), so
the change is test-side.
### alkcall — `OwnershipProvider` / `OwnershipStore` signature
- `OwnershipProvider::owns(&self, identity, resource_type,
resource_id, action: &str)` — added an `action` parameter (was 3
args, now 4). The adapter's call site
(`provider.owns(id_ref, kind, &id, "tty")`) already passes an
`"tty"` action string, which is the new arity. No change to the
production adapter; the mock and test fixtures need the 4-arg shape.
- `OwnershipStore::record(&self, identity, resource_type,
resource_id)` — **dropped** the old `action` parameter that
alknet-core's variant took. The adapter test's
`store.record(&owner, "container", "c1").await` matches the new
arity. (The scaffold's plan previously listed an `OwnershipStore`
import that no longer needs an action arg.)
### alkcall — `AuthContext::anonymous(alpn)`
alkcall added a convenience constructor `AuthContext::anonymous(alpn)`
that builds an `AuthContext` with no identity, no fingerprint, no
remote address — only the ALPN. Useful in adapter tests; not used by
the production adapter (which receives the `AuthContext` from the
dispatch path).
### alktype 0.2.0 — BAST format pivot
alktype was reworked: the v0.1.0 `AlkType:*` custom-keyword JSON Schema
backend was dropped in favor of a straightforward `kind`-based BAST
(Binary Abstract Syntax Tree) format inspired by unist ASTs. A BAST
document is a plain JSON file (conforming to a JSON Schema meta-schema)
that describes binary layouts using `kind` strings (`"uint8"`,
`"uint32"`, `"struct"`, etc.) and `$defs`/`$ref` for composition.
`alktty` will **not** depend on alktype — the hand-rolled
`ChunkReader`/`ChunkWriter` in `wire.rs` already works and is
straightforward. The BAST format matters here only as a normative
**schema document** for the `alk/tty` wire format: a JSON file under
`docs/architecture/` that downstream consumers (and alktype, if
desired) can consume to generate validators, layout maps, or readers
in any language. The schema is a documentation artifact, not a runtime
dependency.
## Crate Structure
Single crate (`alktty`) with the local backend gated behind a `local`
feature. This is a "tty-specific monorepo" — one crate, one concern.
feature. This is a "tty-specific monorepo" — one crate, one concern,
both halves of the original alknet split (`alknet-tty` +
`alknet-tty-local`) folded in.
```
alktty/
@@ -26,20 +106,19 @@ alktty/
│ ├── adapter.rs # TtyAdapter (ProtocolHandler), drive_session
│ ├── session.rs # TtySession — typed consumer client
│ ├── channels.rs # OpenHandler factory, register_openable helper
│ └── local/ # (behind `local` feature)
│ ├── mod.rs
│ ├── backend.rs # LocalTtyBackend
│ ├── pty.rs # PTY mode (portable_pty)
│ └── pipe.rs # Pipe/runner mode (tokio::process::Command)
│ └── local/ # (behind `local` feature — the folded alknet-tty-local)
│ ├── mod.rs # LocalTtyBackend re-export
│ ├── backend.rs # LocalTtyBackend (dispatches terminal: Some→pty, None→pipe)
│ ├── pty.rs # PTY mode (portable_pty, 3 std threads → tokio channels, #[cfg(unix)])
│ └── pipe.rs # Pipe/runner mode (tokio::process::Command, cross-platform)
├── docs/
│ ├── architecture/
│ │ ├── README.md # architecture index
│ │ ├── crates/
│ │ │ ├── overview.md
│ │ ├── tty-wire.md
│ │ ├── tty-backend.md
│ │ ├── tty-adapter.md
│ │ │ └── tty-local.md
│ │ ├── tty-wire.md # wire format spec (5-byte chunk + 4-byte neg frame)
│ │ ├── tty-bast.md # BAST JSON document for the alk/tty wire format
│ │ ├── tty-backend.md # TtyBackend trait + LocalTtyBackend
│ │ ├── tty-adapter.md # TtyAdapter + drive_session
│ │ ├── tty-local.md # PTY + pipe modes, REQ-TTY-01/02
│ │ └── decisions/ # renumbered ADRs
│ └── plans/
│ └── project-setup.md # this document
@@ -58,24 +137,47 @@ ADR-054 wanted `alknet-tty` to re-export `LocalTtyBackend` behind a
`local` feature — which cargo rejects. The assembly-layer workaround
(consumer depends on both crates directly) works but is awkward.
Single crate with `local` feature solves this cleanly. The `portable-pty`
dep is optional. A docker-only deployment doesn't pull in PTY code.
Single crate with `local` feature solves this cleanly. The
`portable-pty` and `tokio-util` deps are optional. A docker-only
deployment doesn't pull in PTY code.
### 2. Dependency: `alknet-core` → `alkcall`
**Folded-in scope.** The single crate carries both halves of the
original alknet split: the wire/adapter/backend-trait half (from
`alknet-tty`) and the local backend (from `alknet-tty-local`). The
local backend is small (~1.4k lines), tightly coupled to the trait,
and the cyclic-dep workaround it required in the alknet mono-repo
doesn't exist in a single crate. A docker or SSH backend would still
be a separate crate — those have real external deps (`bollard`,
`russh`) and their own resource models.
### 2. Dependency: `alknet-core` → `alkcall::core`
All types previously from `alknet-core` now come from `alkcall::core`:
- `ProtocolHandler`, `Connection`, `BiStream`, `BidiStreamSource`
- `AuthContext`, `Identity`, `IdentityProvider`
- `AccessControl`, `OwnershipProvider`
- `AccessControl`, `OwnershipProvider`, `OwnershipStore`,
`InMemoryOwnershipStore`
- `HandlerError`, `StreamError`
The `TtyAdapter`'s `impl ProtocolHandler` changes which crate the trait
comes from. The trait shape is identical.
comes from. The trait shape is identical. The `Identity` struct's
`resources` field is `HashMap<String, Vec<String>>` in alkcall (was
`HashMap<String, String>` in alknet-core); the production adapter only
reads `identity.scopes`, so the change is test-side.
`Cargo.toml` pins `alkcall = "0.1.1"` (the scaffold's `0.1.0` pin is
stale — 0.1.1 is the ALPN-rename release; same public API per semver).
### 3. Channels integration (the new part)
This is where alktty becomes a proper producer/consumer on alkcall.
**ALPN strings (settled).** `alk/tty` (direct) and `alk/channels`
(multiplexed). The `alknet/<name>` → `alk/<name>` rename landed in
alkcall 0.1.1 — `CHANNELS_ALPN` is `b"alk/channels"`, the channels
client test uses `"alk/tty"`. No upstream work remains; we just use
the new strings.
**Producer half** — two paths to expose TTY:
- **Direct ALPN** (`alk/tty`): `TtyAdapter` implements `ProtocolHandler`.
@@ -94,12 +196,17 @@ This is where alktty becomes a proper producer/consumer on alkcall.
The channels integration code is small: a `register_openable` helper
function that takes a `ChannelCore`, `OperationRegistry`, and backend
map, and registers the per-ALPN open op. This lives in `src/channels.rs`.
map, and registers the per-ALPN open op (`channels/tty/sub` —
consumer-opens). This lives in `src/channels.rs`. The op spec carries
the `channel_open` marker (`ChannelOpenSpec::new("alk/tty")`) and the
`AccessControl` for the scope-gate + ownership check; the channels
wrapper does `check_open` → `open_channel` → spawn the `OpenHandler`
→ respond with `{ channel_id }`.
**Consumer half** — `TtySession`:
A typed client that wraps the wire protocol. Two constructors:
- `TtySession::connect_direct(connection)` — for direct `alknet/tty`
- `TtySession::connect_direct(connection)` — for direct `alk/tty`
- `TtySession::open_via_channels(client, params)` — opens a channel via
`ChannelClient`, negotiates, returns a session handle
@@ -142,79 +249,189 @@ The old code had minimal access control (a scope-gate at negotiation).
With alkcall, we get:
- **Operation-level ACL**: `AccessControl` on `OperationSpec` — scopes,
resource ownership checks
resource ownership checks (`required_scopes`, `required_scopes_any`,
`resource_type` + `resource_id_path`, `resource_action`)
- **Channel lifecycle policy**: `ChannelLifecyclePolicy` — per-identity
channel caps, open/close hooks
channel caps, open/close hooks (consulted by the `ChannelCore`
wrapper, not by alktty directly)
- **Ownership**: `OwnershipProvider` — runtime-spawned resource tracking
(terminal sessions are resources per ADR-050)
(terminal sessions are resources per ADR-050). alkcall's
`OwnershipProvider::owns` takes an `action: &str` arg (the adapter
passes `"tty"`); `OwnershipStore::record` takes no action arg.
The `TtyAdapter`'s access control is reworked to use alkcall's primitives
rather than the ad-hoc scope check. The channels path gets this for free
via `ChannelCore::register_openable` which wires `AccessControl` into the
operation spec.
### 7. BAST schema as a documentation artifact
The `alk/tty` wire format gets a BAST (Binary Abstract Syntax Tree)
document under `docs/architecture/tty-bast.md`. BAST is alktype's
JSON-based binary-layout vocabulary (alktype 0.2.0 dropped the old
`AlkType:*` custom-keyword backend in favor of this `kind`-based
format). The document is a normative spec: downstream consumers (and
alktype, if desired) can consume it to generate validators, layout
maps, or readers in any language with a JSON parser.
**alktty does not depend on alktype.** The hand-rolled
`ChunkReader`/`ChunkWriter` in `wire.rs` is the runtime codec; the BAST
document is the human-readable contract that describes what those
types round-trip. Keeping them as separate artifacts avoids pulling in
alktype as a runtime dep for a codec that's already ~200 lines and
works. If we later want runtime validation against the BAST (e.g., to
reject malformed chunks at the framing boundary with a generated
validator), alktype becomes an optional dep — the BAST document is
already there to feed it.
The `TtyAdapter`'s direct-ALPN path keeps the existing ad-hoc scope
check (`has_scope(identity, TTY_OPEN_SCOPE)`) for the scope gate and
optionally consults `OwnershipProvider` for the resource-ownership
check. The channels path gets both for free via
`ChannelCore::register_openable`, which wires `AccessControl` into the
operation spec — the registry runs the ACL before the wrapper, so the
`OpenHandler` only needs to validate params and spawn the protocol.
## Migration Strategy
### Phase 1: Scaffold and core types (no new logic)
### Phase 0: Scaffold hygiene (small fixes before porting)
1. Create `Cargo.toml` with alkcall dependency
2. Port `wire.rs` — change `alknet-core` imports to `alkcall::core`, rename
crate references in docs
3. Port `control.rs` — same
4. Port `negotiation.rs` — same
5. Port `backend.rs` — same; `BoxFuture` type alias can use
`futures::future::BoxFuture` since alkcall pulls in `futures`
6. Port `adapter.rs` — change `ProtocolHandler` import, `Connection` import,
`AuthContext` import, `HandlerError` import. The `handle()` method
signature is identical.
1. Bump `alkcall` in `Cargo.toml` from `"0.1.0"` → `"0.1.1"` (ALPN
rename release).
2. Add `futures = "0.3"` to `[dependencies]` (the `BoxFuture` alias
uses `futures::future::BoxFuture`; alkcall pulls it transitively
but alktty should declare it directly to not rely on a transitive
dep for a public type alias).
3. Confirm the `local` feature already wires `portable-pty` +
`tokio-util` as optional deps (it does in the scaffold). The
`local` module's `pty.rs` additionally needs `libc` under
`cfg(unix)` — already in `[target.'cfg(unix)'.dependencies]`.
### Phase 1: Port core types from `alknet-tty` (no new logic)
1. Port `wire.rs` — no `alknet-core` imports; just `std::io`, `tokio::io`,
`bytes`, `thiserror`. Rename crate references in doc comments
(`alknet/tty` → `alk/tty`, `alknet-tty` → `alktty`).
2. Port `control.rs` — no `alknet-core` imports; `serde`, `bytes`,
`libc` (under `cfg(unix)`). Rename `alknet/tty` → `alk/tty` in
doc comments.
3. Port `negotiation.rs` — no `alknet-core` imports; `std::io`,
`bytes`, `tokio::io`, `thiserror`, `serde_json`. Rename doc
references.
4. Port `backend.rs` — no `alknet-core` imports; `std`, `async_trait`,
`bytes`, `futures_core`, `tokio::sync`, `tokio_stream`. Change the
`BoxFuture` type alias from `Pin<Box<dyn Future + Send>>` to
`futures::future::BoxFuture<'static, T>` (functionally identical;
the alias matches the alkcall convention and lets us drop the
`Pin`/`Future` imports).
5. Port `adapter.rs` — change imports:
- `alknet_core::auth::{AuthContext, Identity}` →
`alkcall::core::{AuthContext, Identity}`
- `alknet_core::ownership::OwnershipProvider` →
`alkcall::core::OwnershipProvider`
- `alknet_core::types::{Connection, HandlerError, ProtocolHandler,
StreamError}` → `alkcall::core::{Connection, HandlerError,
ProtocolHandler, StreamError}`
- Change `b"alknet/tty"` → `b"alk/tty"` in `TtyAdapter::alpn()`.
- The `handle()` method signature is identical (alkcall's
`ProtocolHandler` shape matches alknet-core's).
6. Port the unit tests inside each module alongside the production
code. The adapter tests need:
- `Identity.resources: HashMap<String, Vec<String>>` (was
`HashMap<String, String>` in alknet-core).
- `OwnershipStore::record(&self, identity, resource_type,
resource_id)` — 3 args, no `action` (alkcall dropped it).
- `OwnershipProvider::owns(identity, rt, rid, action)` — 4 args
(alkcall added `action`).
### Phase 2: Channels integration (new code)
1. Create `src/channels.rs`:
- `register_openable` helper — takes `ChannelCore`, backend map,
`OperationRegistry`, registers the `channels/tty/sub` op
- `TtyOpenHandler` — the `OpenHandler` that receives a channel
`Connection`, calls `accept_bi()`, runs `drive_session`
`OperationRegistry`, `AuthContext`; builds the `OperationSpec`
for `channels/tty/sub` with `ChannelOpenSpec::new("alk/tty")`,
`AccessControl` carrying `TTY_OPEN_SCOPE` + resource-type/ownership
if a backend declares a `resource_id`; calls
`ChannelCore::register_openable(spec, open_handler, registry, auth)`.
- `TtyOpenHandler` — the `OpenHandler` (an
`Arc<dyn Fn(Value, Connection, AuthContext) -> JoinHandle<()>`)
that receives a channel `Connection`, calls `accept_bi()`, runs
`drive_session` on the resulting `BiStream`.
2. Create `src/session.rs`:
- `TtySession` struct — typed consumer client
- `TtySession::open_via_channels(client, params)` constructor
- Methods: `send_stdin`, `recv_stdout`, `resize`, `signal`, `wait`
- `TtySession::connect_direct(connection)` — direct `alk/tty`
- `TtySession::open_via_channels(client, params)` — opens a channel
via `ChannelClient::open_channel("channels/tty/sub", params,
"alk/tty")`, negotiates, returns a session handle
- Methods: `send_stdin`, `recv_stdout`, `recv_stderr`, `resize`,
`signal`, `wait`
### Phase 3: Local backend (behind `local` feature)
### Phase 3: Local backend (behind `local` feature) — folded `alknet-tty-local`
1. Create `src/local/mod.rs`, `src/local/backend.rs`, `src/local/pty.rs`,
`src/local/pipe.rs`
2. Port from `alknet-tty-local` — change `alknet-tty` imports to `crate`
3. `LocalTtyBackend` implements `crate::backend::TtyBackend`
1. Create `src/local/mod.rs` (re-exports `LocalTtyBackend`),
`src/local/backend.rs`, `src/local/pty.rs`, `src/local/pipe.rs`.
2. Port from `alknet-tty-local/src/`:
- `backend.rs` — change `alknet_tty::backend::{...}` →
`crate::backend::{...}`. `LocalTtyBackend::allocate` dispatches
on `params.terminal`: `Some` → `pty::allocate_pty`, `None` →
`pipe::allocate_pipe`.
- `pty.rs` — `portable_pty` + 3 std threads (reader/writer/waiter)
feeding tokio mpsc/oneshot. `#[cfg(unix)]`-only: PTY mode uses
`libc::kill(-pgid, sig)` for process-group signal forwarding.
Update `alknet_tty::backend::{BoxFuture, TtyControl, ...}` →
`crate::backend::{...}` and `alknet_tty::control::signal_from_name`
→ `crate::control::signal_from_name`.
- `pipe.rs` — `tokio::process::Command` + `tokio_util::io::ReaderStream`
for stdout/stderr. Cross-platform: signal forwarding uses
`libc::kill(pid, sig)` under `cfg(unix)`, falls back to
`Child::kill()` on non-Unix. Update `alknet_tty::...` → `crate::...`.
3. `LocalTtyBackend` implements `crate::backend::TtyBackend`. Wire the
`local` feature in `src/lib.rs`:
`#[cfg(feature = "local")] pub mod local;`
### Phase 4: Architecture docs
### Phase 4: Architecture docs + BAST schema
1. Port the 5 spec docs from `alknet/docs/architecture/crates/tty/`
into `docs/architecture/` (flat layout, not the `crates/tty/`
subpath — this is a single-crate repo now):
`tty-wire.md`, `tty-backend.md`, `tty-adapter.md`, `tty-local.md`,
plus an `overview.md` index.
2. Port relevant ADRs (052, 053, 054, 055, 056, 057, 077, 093) —
renumber into alktty's ADR range, update cross-references
3. Write `docs/architecture/README.md` index
renumber into alktty's ADR range (001..008), update cross-references
(`alknet/tty` → `alk/tty`, `alknet-tty-local` → `alktty`'s `local`
feature, `alknet-core` → `alkcall::core`).
3. Write `docs/architecture/tty-bast.md` — the BAST JSON document for
the `alk/tty` wire format. Covers:
- The 5-byte chunk header (`struct` with `endian: "big"`:
`stream_type: uint8`, `length: uint32`).
- The four stream-type channels as an `enum` (`Stdin=0`,
`Stdout=1`, `Stderr=2`, `CtrlIn=3`, `CtrlOut=4`).
- The control-message `union` (field-name discriminator on `type`:
`resize`, `signal`, `eof`, `exit`).
- The negotiation frame as a separate `struct` (4-byte BE length
prefix + UTF-8 JSON `NegotiateRequest` body) — annotated as
out-of-band for the chunk codec but documented for completeness.
- Conforms to the BAST meta-schema at
`https://alk.dev/bast/v1/schema`; validatable by any JSON Schema
Draft 2020-12 validator.
4. Write `docs/architecture/README.md` index.
### Phase 5: Tests
1. Port existing unit tests from `alknet-tty` (wire, negotiation, control,
adapter)
2. Port integration tests from `alknet-tty-local` (negotiation, pipe, pty)
1. Port existing unit tests from `alknet-tty` (wire, negotiation,
control, adapter) — these live inline in each `src/*.rs` module's
`#[cfg(test)] mod tests`.
2. Port integration tests from `alknet-tty-local/tests/` (negotiation,
pipe, pty) into `tests/` at the crate root. The pty test stays
`#[cfg(unix)]`.
3. Add channels integration tests — end-to-end `register_openable` +
`ChannelClient::open_channel` + `drive_session` round-trip
`ChannelClient::open_channel` + `drive_session` round-trip. Use
the in-memory `MockBackend` from `backend.rs` for the producer
side so the test doesn't need a real PTY.
## Open Questions
### OQ-1: ALPN strings (RESOLVED)
### OQ-1: ALPN strings (RESOLVED — landed in alkcall 0.1.1)
**Decision**: Use `alk/tty`. The `alknet/<name>` convention is being
shortened to `alk/<name>` across all crates. This is a wire-format change
but alkcall is v0.1.0 and this is the first protocol crate built on it —
the right time to make the change.
**Upstream change needed**: alkcall's test at `channels/client.rs:767` uses
`ChannelOpenSpec::new("alknet/tty")` — needs updating to `"alk/tty"`.
Also any docs/ADRs in alkcall that reference `alknet/<name>` as the ALPN
convention.
**Decision**: `alk/tty` (direct) and `alk/channels` (multiplexed). The
`alknet/<name>` → `alk/<name>` rename landed in alkcall 0.1.1:
`CHANNELS_ALPN` is `b"alk/channels"`, the channels client test uses
`"alk/tty"`. Semver showed no public API change → 0.1.0 → 0.1.1.
### OQ-2: Channel open operation name (RESOLVED)
@@ -239,38 +456,51 @@ code runs in both direct and channels modes.
### OQ-5: `BoxFuture` type alias (RESOLVED)
**Decision**: Use `futures::future::BoxFuture`. alkcall already pulls in
`futures`, so no new dependency.
**Decision**: Use `futures::future::BoxFuture<'static, T>`. alkcall
already pulls in `futures`, so no new transitive dep; alktty declares
`futures = "0.3"` directly so the public alias doesn't ride on a
transitive dep.
### OQ-6: BAST schema scope (RESOLVED)
**Decision**: Documentation artifact only — `docs/architecture/tty-bast.md`.
alktty does not depend on alktype. The hand-rolled `wire.rs` codec is
the runtime; the BAST document is the normative contract for downstream
consumers. If runtime validation against the BAST becomes desirable
later, alktype becomes an optional dep and the BAST document is already
there to feed it.
## Upstream Changes Needed in alkcall
These are changes we need to push to alkcall before or alongside this work:
All four landed in alkcall 0.1.1 (no public API change per semver).
Nothing is blocking alktty; this section is kept as a historical record
of what was pushed.
1. **ALPN convention**: `alknet/<name>` → `alk/<name>` across docs, ADRs,
and test code. The test at `channels/client.rs:767` uses
`ChannelOpenSpec::new("alknet/tty")` — needs `"alk/tty"`.
and test code. The test at `channels/client.rs:767` now uses
`ChannelOpenSpec::new("alk/tty")` (was `"alknet/tty"`).
2. **`CHANNELS_ALPN` constant**: `b"alknet/channels"``b"alk/channels"`.
This is in `alkcall/src/channels/adapter.rs`.
2. **`CHANNELS_ALPN` constant**: `b"alknet/channels"` → `b"alk/channels"`
in `alkcall/src/channels/adapter.rs`.
3. **ADR-004** (ALPN convention): Update from `alknet/` prefix to `alk/`.
3. **ADR-004** (ALPN convention): Updated from `alknet/` prefix to `alk/`.
4. **Architecture README**: The dependency layering diagram and pattern
section reference `alknet/tty` and `alknet/channels` — update to
`alk/tty` and `alk/channels`.
section reference `alk/tty` and `alk/channels`.
## Risks and Concerns
### Risk: alkcall is v0.1.0 — API stability
### Risk: alkcall is v0.1.x — API stability
alkcall is published at 0.1.0. Breaking changes are expected. The
integration surface we use (`ChannelCore::register_openable`,
`ChannelClient`, `ProtocolHandler`, `Connection`) is the core API and
alkcall is published at 0.1.1. Breaking changes are expected at this
major-zero stage. The integration surface we use
(`ChannelCore::register_openable`, `ChannelClient`, `ProtocolHandler`,
`Connection`, `Identity`, `OwnershipProvider`) is the core API and
likely stable, but the channels module in particular may evolve.
**Mitigation**: We own alkcall and can push changes as needed. This is the
first real consumer, so we'll find and fix issues upstream rather than
working around them.
**Mitigation**: We own alkcall and can push changes as needed. This is
the first real consumer, so we'll find and fix issues upstream rather
than working around them. Pin `alkcall = "0.1.1"` and bump deliberately.
### Risk: `drive_session` complexity
@@ -290,10 +520,29 @@ specific (Unix only for PTY, pipe mode works cross-platform).
**Mitigation**: Gate PTY behind `#[cfg(unix)]` as the existing code does.
Pipe mode works on all platforms.
### Risk: BAST schema drift
The BAST document in `docs/architecture/tty-bast.md` is a documentation
artifact; the runtime codec is the hand-rolled `wire.rs`. If the wire
format evolves (new stream_type, control message variant, negotiation
field) and the BAST doc isn't updated, downstream consumers generated
from the BAST will be wrong.
**Mitigation**: Add a test that parses the BAST document with
`serde_json` and asserts the stream-type enum values match
`wire.rs`'s `STREAM_STDIN`/`STREAM_STDOUT`/`STREAM_STDERR`/
`STREAM_CTRL_IN`/`STREAM_CTRL_OUT` constants. Cheap; catches the
common drift case. (Doesn't require alktype as a dep — just
`serde_json`, which we already have.)
## References
- [alkcall architecture README](/workspace/@alkdev/alkcall/docs/architecture/README.md)
- [alknet-tty architecture docs](/workspace/@alkdev/alknet/docs/architecture/crates/tty/)
- [alkcall channels operations](/workspace/@alkdev/alkcall/src/channels/operations.rs) — `ChannelCore::register_openable`, `OpenHandler`
- [alkcall channels client](/workspace/@alkdev/alkcall/src/channels/client.rs) — `ChannelClient::open_channel`, reference integration pattern
- [alktype BAST format](/workspace/@alkdev/alktype/docs/architecture/bast-format.md) — normative format spec for the `tty-bast.md` document
- [alknet-tty source](/workspace/@alkdev/alknet/crates/alknet-tty/src/) — port origin (wire, control, negotiation, backend, adapter)
- [alknet-tty-local source](/workspace/@alkdev/alknet/crates/alknet-tty-local/src/) — port origin (local backend, pty, pipe)
- [alknet-tty architecture docs](/workspace/@alkdev/alknet/docs/architecture/crates/tty/) — port origin for the spec docs
- [ADR-093](/workspace/@alkdev/alknet/docs/architecture/decisions/093-channels-pure-channel-multiplexing.md) — TTY always uses 5-byte format
- [ADR-077](/workspace/@alkdev/alknet/docs/architecture/decisions/077-tty-inside-channels.md) — reversed, historical context
- [alkcall channels client test](/workspace/@alkdev/alkcall/src/channels/client.rs) — reference integration pattern