The channels concept (docs/research/alknet-channels/phase-0-findings.md)
was developed in a separate session and is research-phase, not
architecture. The hub spec was inadvertently built assuming a channel
model that doesn't exist yet.
Changes:
- Remove all channels references from hub README (subtitle, channel
model section, channel management bullet, channel proxying section,
'does NOT do' bullet, references)
- Remove OQ-55 (channel/open operation) — channels research has its
own question tracking (OQ-CH-01 through OQ-CH-07)
- Hub spec now describes what it actually provides: peer lifecycle,
aggregated operation env, service discovery. One QUIC connection
per peer carrying the call protocol. No channels, no future
multiplexing, no research references.
- Replace on_worker_connected() post-hoc call with WorkerConnectedCallback
that fires inside CallAdapter::handle(). handle() blocks until disconnect
— there is no 'after handle accepts' point for the assembly layer to
hook into. The callback carries both on_connected (from_call + attach_peer)
and on_disconnected (detach_peer + drop channels).
- Add CallAdapter::with_worker_connected_callback(callback) builder method.
- Consolidate duplicate WorkerConnectedCallback struct definitions.
- Fix channel role: 'channel proxying' → 'channel management'. The hub
tracks channels; it does not proxy streams. What the caller does with
the resulting channel is the assembly layer's business.
- Resolve OQ-54: callback is the committed design. Update OQ file and
open-questions.md table.
Replace the 'future strategies' section with the channel model:
- Channel 0 is the call protocol — the universal control plane. Handles
operation discovery (from_call), operation routing (invoke_peer),
service discovery, and channel negotiation (channel/open, channel/close,
channel/list).
- Channels 1..N carry any ALPN as data planes. Opened via channel/open on
the call protocol. Each channel is a bidirectional QUIC stream, wrapped
as Connection::from_bidi (ADR-065), and handed to the same
ProtocolHandler::handle() that handles dedicated connections. The
handler does not know it's on a multiplexed channel.
- Channel negotiation is symmetric — either side can open a channel.
Same pattern as from_call: bidirectional, symmetric, negotiated over
the call protocol.
- The hub's role for channels 1..N is transparent stream proxying. The
hub does not interpret the protocol; the client and worker speak the
ALPN directly.
- Hub struct gains channel tracking (PeerId → ChannelId → ChannelInfo).
HubError gains ChannelAlreadyOpen, ChannelNotFound, ChannelOpenFailed.
- New OQ-55: channel/open operation spec (deferred to call-protocol
implementation phase).
Resolve the deferred docker system events subscription question.
docker/system/events is now a v1 Subscription operation using the
same StreamingHandler pattern already wired for logs, exec, and
image/pull. The internal ownership-store subscription for stale-entry
cleanup on destroy events is a follow-up refinement.
Scrub hedging language from ADR-060 and docker specs:
- Remove 'marginal gain' / 'future feature is additive' framing
- Replace 'no reaper' / 'not promptly cleaned up' with clean
statement that events subscription provides the prompt-cleanup path
- Remove stale-entry policy from ADR-060's two-way door classification
Three ADRs addressing gaps surfaced by the first hub consumer (alkapi):
- ADR-067: Aggregated peer-env wiring — Dispatcher::with_aggregated_env hook
so compose_root_env reads a shared PeerCompositeEnv across all calls,
not a fresh per-call one. The hub-defining gap (alkapi OQ-08 / G.1).
- ADR-068: PeerCompositeEnv::peer_operations override — adds
list_operation_names() to OperationEnv, overrides on OverlayOperationEnv
and PeerCompositeEnv. Fixes services/list-peers returning empty operation
lists for non-local peers (alkapi G.6).
- ADR-069: from_call is a manual free function, not auto-wired — reverses
the aspirational OQ-27 resolution to match the implementation. Cleans
the 'v1 default' hedging language in ADR-017 and client-and-adapters.md
(alkapi G.4).
New crate spec: alknet-hub — reusable hub pattern (aggregated env,
connection lifecycle, worker supervision with backoff, service discovery).
Spec fixes: builder API drift (with_local/with_local_streaming separation),
ScopedOperationEnv → ScopedPeerEnv type name, OQ count 51→54.
Three new OQs: OQ-52 (wait_for_close), OQ-53 (backoff defaults),
OQ-54 (inbound hook placement).
Three code commits landed a clean sweep discovered when building an
external app against the crates. This syncs the architecture specs to
match the codebase and amends the affected decisions.
ADR-064 (supersedes ADR-005): irpc was never integrated — no .rs file in
the workspace ever imported it. The wire protocol (wire.rs) is hand-rolled
length-prefixed JSON; the EventEnvelope shape was derived from the
@alkdev/pubsub TypeScript prior art (ADR-013), not from irpc. ADR-005's
premise ('irpc as the call protocol foundation') was never implemented as
stated. Superseded with a clear header; the body is kept as historical
record.
ADR-065: Connection::from_stream / from_bidi — Connection now accepts any
AsyncRead + AsyncWrite pair via ConnectionKind::Stream (yield-once
accept_bi contract: QUIC yields many streams, everything else yields one
then ConnectionClosed). Unblocks TCP+TLS, SSH channel dispatch,
WebTransport streams, and wasm streams through the same HandlerRegistry
as QUIC connections, with zero handler code changes. MockConnection /
ConnectionKind::Mock removed (tests use from_stream with sink/empty).
Stream-level Mock variants renamed to Stream (they were already generic).
Amended ADRs: ADR-003 (irpc removed from dep table), ADR-007 (from_stream
opens the server-side door; MockConnection removed), ADR-010 (TCP+TLS can
now dispatch through the registry via from_bidi; iroh 1.0 migration noted).
Updated specs: core-types.md (Connection/SendStream/RecvStream sections),
endpoint.md (TCP section, iroh note), call-protocol.md, operation-registry
(irpc Integration section replaced), overview.md, http-server.md,
webtransport.md, call README. OQ-09 (WASM) resolution amended: the
Connection door is now open via from_stream; the accept-loop runtime door
remains closed (tokio doesn't run on WASM).
See docs/research/transport-generalization/findings.md for the full trace.
Greenfield architecture spec set for the alknet-docker crate — a thin,
single-host bollard wrapper exposing docker container/image operations
as call-protocol ops on the shared alknet/call ALPN, plus a
DockerTtyBackend (impl TtyBackend) behind a tty feature for interactive
terminal sessions into containers over alknet/tty.
Six ADRs:
- 058: docker ops on alknet/call (no separate ALPN; raw-carriage handoff
dissolved by alknet-tty extraction — interactive attach moved to
alknet/tty via DockerTtyBackend, no carriage field on call.requested)
- 059: bollard 0.21 (verified current on crates.io) + feature selection
(http+pipe+time; no ssl/ssh/websocket/buildkit)
- 060: container resource model (ADR-050 application) — alknet.managed/
alknet.owner labels, list owned_only flag, hosted-services operator
role via static-resource fallback, handler-driven revoke with
autonomous-death tolerance, resource-action vocabulary
- 061: DockerTtyBackend in alknet-docker behind tty feature (attach vs
exec mode; POC drive_attach_raw as reference)
- 062: Docker client + OwnershipStore injection via closure capture
(not Capabilities, not OperationContext — matches from_openapi pattern)
- 063: exit code on terminal call.responded for non-interactive exec
(call.completed stays empty, ADR-012 unchanged)
Four spec docs (crates/docker/): README, overview, docker-operations,
docker-tty-backend. Four deferred-scope OQs (048-051): network/volume
ops, buildkit, system events subscription, create options surface.
Updates the tty-backend.md Backend implementations table (DockerTtyBackend
row now specced) and the architecture README (doc table, ADR table,
current-state paragraph, OQ count).
Grounded in the alknet-docker POC (docs/research/alknet-docker/poc-summary.md)
which validated the hard parts; the remaining lifecycle ops are mechanical
bollard wrapping. Reviewed by architecture-reviewer subagent; criticals
(the docker_client injection model conflicting with the Capabilities
contract) resolved via ADR-062/063 before commit.
C1: Box<dyn TtyControl + Clone> does not compile (Clone is not object-safe).
Replace with a TtyControlHandle newtype: #[derive(Clone)] wrapping
Arc<dyn TtyControl + Send + Sync>. The trait stays object-safe; the
newtype carries the Clone-ability. Updated across tty-backend.md,
ADR-053, OQ-43, and the tty README.
W1: 'Drop is the cleanup, threads exit on channel close' was false for
the local PTY waiter thread (blocked in Child::wait(), does not observe
channel close) — a child that ignores stdin EOF would be orphaned on
session cancel. New ADR-056 commits a backend cleanup contract: dropping
the exit_code future MUST kill the session target. The local backend
implements it via a ChildKiller held in the exit_code future's Drop
guard (disarmed on resolve). Corrected the lifecycle section in
tty-adapter.md, added the mechanism + constraint to tty-local.md, and
referenced ADR-056 from tty-backend.md, overview.md, both READMEs.
S1: error-frame < 16 MiB stated as a wire-format invariant (not an
assumption) so the 0x00-first-byte disambiguation trick is sound by
construction.
S2: carriage field validation (MUST be "raw" in v1, else
malformed_negotiation) specified.
S3: NegotiateRequest + TerminalParamsWire Rust structs added with
serde defaults and validation rules.
S4: modes field annotated 'backends MUST ignore content in v1' in both
tty-backend.md and ADR-053.
S5: AsyncWrite/Stream qualified with crate origins (tokio::io,
futures_core) in the TtyHandle struct definitions.
The backpressure chain is complete by construction: QUIC flow control
→ bounded drainer channel → bounded stdout channel → OS pipe/PTY buffer
→ process write() blocks. Every link awaits its producer; no unbounded
buffer breaks the chain. The reversal path (an additive ControlMessage
variant, not a wire-format header change) is noted in ADR-052's
consequences as a two-way door, not the expected path.
Tightened ADR-052 assumption 1 from a hedge ('expected to work; a POC
would confirm') to a decided constraint. Updated all OQ-45 references
across the tty spec docs from 'open (low risk)' to 'resolved'.
Establishes the tasks/architecture/ blocker-task half of the Safe Exit
protocol. Each deferred OQ now has an external-trigger tracker task
representing its unblocking condition, and the OQ's Blocked on field names
the tracker task ID — closing the gap where the deferral field existed but
the task-graph half was unenforced.
Changes:
- Add 6 external-trigger tracker tasks for the deferred OQs (OQ-09, 10, 32,
41, 44, 46) under tasks/architecture/, tagged [external-trigger, deferred-oq]
- Add structured Blocked on field to OQ-09 and OQ-10 (previously used legacy
'deferred' status with the reason in the Resolution prose); index now
surfaces all 6 deferred OQs with concrete conditions, no placeholders
- Document the architecture-task level mapping (level: implementation for
ADRs/specs, decomposition for spec-to-ADR breakdown, planning for backlog
seeding, research/review direct fits) and the deferred-OQ blocker-task
pattern in docs/sdd_process.md
- Mark architecture/safe-exit-blocker-task-mechanism and
architecture/oq-09-10-blocking-conditions completed (implemented by this pass)
- Fix 4 pre-existing ADR-050 implementation tasks: status: done → completed
(taskgraph enum is 'completed', not 'done') — taskgraph validate now passes
for all 100 tasks
The monolithic open-questions.md (1310 lines, 47 OQs) was large enough to be
unmanageable, with high size variance (OQ-42 at 220 lines next to OQ-06 at 8).
Decomposed into one file per OQ under docs/architecture/questions/ (NNN-slug.md,
mirroring the ADR convention), with open-questions.md retained as the index:
theme-grouped tables plus a cross-theme Deferred/Blocked section that surfaces
the 6 deferred OQs with their Blocked-on conditions inline (the safe-exit
visibility surface). Per-OQ content moved verbatim; all 62 inbound links stay
valid (none used anchors). README's curated OQ summary dropped (now redundant
with the index tables).
Also seeds tasks/architecture/ with this task plus two follow-ups found during
the decompose: OQ-09/10 missing structured Blocked-on fields, and the
tasks/architecture/ blocker-task half of the Safe Exit protocol being
unenforced.