docs(architecture): resolve one-way doors, clean up Phase 0 specs
Resolve blocking one-way door decisions: - ADR-007: BiStream is a trait, handlers receive Connection not BiStream - ADR-008: Secret service is CLI-embedded, exposed via call protocol - ADR-009: One-way door decision framework (classify by reversal cost) Update existing documents: - overview.md: add design principles, revise ProtocolHandler signature, update shared types, add WASM as design constraint - open-questions.md: add door-type classifications, resolve OQ-01/OQ-08, move OQ-09/OQ-10 to deferred section, mark two-way doors as impl-deferred - README.md: reflect resolved questions, remove crate spec stubs from index - ADR-002: cross-reference ADR-007 for signature revision Clean up premature artifacts: - Remove 11 empty crate spec stubs (16-28 lines each, no unique content) - Specs will be created when each crate enters Phase 1
This commit is contained in:
@@ -1,31 +1,24 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-06-15
|
last_updated: 2026-06-16
|
||||||
---
|
---
|
||||||
|
|
||||||
# Alknet Architecture
|
# Alknet Architecture
|
||||||
|
|
||||||
## Current State
|
## Current State
|
||||||
|
|
||||||
**Pre-implementation.** The project has completed a pivot from a three-layer model (StreamInterface/MessageInterface, ListenerConfig, OperationEnv) to an ALPN-as-service model. The greenfield workspace contains only `alknet-secret` (stable) and research/reference material. Architecture specs are being produced following the SDD process (Phase 1).
|
**Pre-implementation.** The project has completed a pivot from a three-layer model to an ALPN-as-service model. The greenfield workspace contains only `alknet-secret` (stable) and research/reference material. Foundational ADRs (001–009) are in place, including the BiStream type definition (ADR-007), secret service integration (ADR-008), and the one-way door decision framework (ADR-009). Architecture specs are ready for Phase 1 implementation planning.
|
||||||
|
|
||||||
|
**Next step**: Resolve remaining two-way-door questions during implementation. Start with alknet-core (ProtocolHandler trait, Connection, endpoint, router, auth types, config).
|
||||||
|
|
||||||
## Architecture Documents
|
## Architecture Documents
|
||||||
|
|
||||||
| Document | Status | Description |
|
| Document | Status | Description |
|
||||||
|----------|--------|-------------|
|
|----------|--------|-------------|
|
||||||
| [overview.md](overview.md) | draft | Workspace-level overview, crate graph, shared types |
|
| [overview.md](overview.md) | draft | Workspace-level overview, crate graph, shared types, design principles |
|
||||||
| [open-questions.md](open-questions.md) | draft | Centralized OQ tracker across all crates |
|
| [open-questions.md](open-questions.md) | draft | Centralized OQ tracker with door-type classifications |
|
||||||
| [crates/alknet-core/spec.md](crates/alknet-core/spec.md) | planned | Core crate: ProtocolHandler, endpoint, router, auth, config |
|
|
||||||
| [crates/alknet-ssh/spec.md](crates/alknet-ssh/spec.md) | planned | SSH handler: russh, SOCKS5, port forwarding |
|
Crate-specific specs will be created when each crate is ready for Phase 1 architecture work, not in advance.
|
||||||
| [crates/alknet-call/spec.md](crates/alknet-call/spec.md) | planned | Call protocol: irpc, operation registry, access control |
|
|
||||||
| [crates/alknet-secret/spec.md](crates/alknet-secret/spec.md) | planned | Key derivation and encryption (already implemented) |
|
|
||||||
| [crates/alknet-sftp/spec.md](crates/alknet-sftp/spec.md) | planned | SFTP handler: russh-sftp protocol core |
|
|
||||||
| [crates/alknet-git/spec.md](crates/alknet-git/spec.md) | planned | Git handler: gix, pkt-line protocol |
|
|
||||||
| [crates/alknet-http/spec.md](crates/alknet-http/spec.md) | planned | HTTP handler: axum, REST API, MCP |
|
|
||||||
| [crates/alknet-dns/spec.md](crates/alknet-dns/spec.md) | planned | DNS handler: hickory-proto, pkarr, service discovery |
|
|
||||||
| [crates/alknet-msg/spec.md](crates/alknet-msg/spec.md) | planned | Messaging: E2E encryption, mixnet |
|
|
||||||
| [crates/alknet-napi/spec.md](crates/alknet-napi/spec.md) | planned | Node.js native addon: call protocol client |
|
|
||||||
| [crates/alknet/spec.md](crates/alknet/spec.md) | planned | CLI binary: handler registration, endpoint startup |
|
|
||||||
|
|
||||||
## ADR Table
|
## ADR Table
|
||||||
|
|
||||||
@@ -37,16 +30,29 @@ last_updated: 2026-06-15
|
|||||||
| [004](decisions/004-auth-as-shared-core.md) | Auth as Shared Core (IdentityProvider) | Accepted |
|
| [004](decisions/004-auth-as-shared-core.md) | Auth as Shared Core (IdentityProvider) | Accepted |
|
||||||
| [005](decisions/005-irpc-as-call-protocol-foundation.md) | irpc as Call Protocol Foundation | Accepted |
|
| [005](decisions/005-irpc-as-call-protocol-foundation.md) | irpc as Call Protocol Foundation | Accepted |
|
||||||
| [006](decisions/006-alpn-convention-and-connection-model.md) | ALPN String Convention and Connection Model | Accepted |
|
| [006](decisions/006-alpn-convention-and-connection-model.md) | ALPN String Convention and Connection Model | Accepted |
|
||||||
|
| [007](decisions/007-bistream-type-definition.md) | BiStream Type Definition | Accepted |
|
||||||
|
| [008](decisions/008-secret-service-integration.md) | Secret Service Integration Point | Accepted |
|
||||||
|
| [009](decisions/009-one-way-door-decision-framework.md) | One-Way Door Decision Framework | Accepted |
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
See [open-questions.md](open-questions.md) for the full tracker.
|
See [open-questions.md](open-questions.md) for the full tracker.
|
||||||
|
|
||||||
Key questions affecting current work:
|
**Resolved one-way doors:**
|
||||||
- **OQ-01**: BiStream type definition — what exactly does BiStream expose? (open)
|
- **OQ-01**: BiStream type — trait with Connection parameter (ADR-007)
|
||||||
- **OQ-02**: AuthContext resolution timing — hybrid model resolved (see ADR-004) (resolved)
|
- **OQ-02**: AuthContext timing — hybrid model (ADR-004)
|
||||||
- **OQ-03**: ALPN string naming convention — resolved (see ADR-006) (resolved)
|
- **OQ-03**: ALPN naming — `alknet/` prefix, no version (ADR-006)
|
||||||
- **OQ-04**: Dynamic handler registration at runtime vs static at startup (open)
|
- **OQ-06**: ALPN per connection, not per stream (ADR-006)
|
||||||
|
- **OQ-08**: Secret service — CLI-embedded via call protocol (ADR-008)
|
||||||
|
|
||||||
|
**Two-way doors (deferred to implementation):**
|
||||||
|
- **OQ-04**: Dynamic handler registration — start static, add ArcSwap later
|
||||||
|
- **OQ-05**: Multi-transport endpoint — start with quinn, add transport trait later
|
||||||
|
- **OQ-07**: Call protocol scope — start with one stream per operation
|
||||||
|
|
||||||
|
**Deferred (not active):**
|
||||||
|
- **OQ-09**: WASM target boundaries — design constraint, not deliverable
|
||||||
|
- **OQ-10**: Git adapter scope — start with smart protocol, add ERC721 later
|
||||||
|
|
||||||
## Document Lifecycle
|
## Document Lifecycle
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
status: planned
|
|
||||||
last_updated: 2026-06-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# alknet-call
|
|
||||||
|
|
||||||
> **Status: Planned** — This spec has not been written yet.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Call protocol handler implementing `ProtocolHandler` on ALPN `alknet/call`. Provides JSON-RPC via irpc with operation registry, streaming subscriptions, pub/sub, and access control.
|
|
||||||
|
|
||||||
## Key Questions
|
|
||||||
|
|
||||||
- **OQ-07**: Call protocol scope within a connection — one stream per operation vs multiplexed
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [overview.md](../../overview.md)
|
|
||||||
- ADR-005: irpc as call protocol foundation
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
status: planned
|
|
||||||
last_updated: 2026-06-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# alknet-core
|
|
||||||
|
|
||||||
> **Status: Planned** — This spec has not been written yet. It will be produced as part of Phase 1 architecture work.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Core crate providing the `ProtocolHandler` trait, ALPN router, endpoint, `BiStream`, `AuthContext`, `IdentityProvider`, configuration types, and shared infrastructure used by all handler crates.
|
|
||||||
|
|
||||||
## Key Questions
|
|
||||||
|
|
||||||
- **OQ-01**: BiStream type definition — trait vs concrete type vs newtype
|
|
||||||
- **OQ-05**: Multi-transport endpoint — TCP, TLS, iroh support scope
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [overview.md](../../overview.md)
|
|
||||||
- ADR-001: ALPN-based protocol dispatch
|
|
||||||
- ADR-002: ProtocolHandler trait
|
|
||||||
- ADR-003: Crate decomposition
|
|
||||||
- ADR-004: Auth as shared core
|
|
||||||
- ADR-006: ALPN string convention and connection model
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
status: planned
|
|
||||||
last_updated: 2026-06-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# alknet-dns
|
|
||||||
|
|
||||||
> **Status: Planned** — This spec has not been written yet.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
DNS handler implementing `ProtocolHandler` on ALPN `alknet/dns`. Uses hickory-proto (`#![no_std]`, WASM-compatible) for DNS wire format and pkarr for self-sovereign DNS. Provides service discovery, control channel via AuthToken in query labels, and encrypted DNS transports (DoT, DoQ, DoH3).
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [overview.md](../../overview.md)
|
|
||||||
- ADR-002: ProtocolHandler trait
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
status: planned
|
|
||||||
last_updated: 2026-06-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# alknet-git
|
|
||||||
|
|
||||||
> **Status: Planned** — This spec has not been written yet.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Git smart protocol handler implementing `ProtocolHandler` on ALPN `alknet/git`. Uses gix (Apache-2.0/MIT) for pack generation, ref resolution, and object store. Custom pkt-line protocol adapter for QUIC streams. No HTTP layer — git protocol directly over QUIC.
|
|
||||||
|
|
||||||
## Key Questions
|
|
||||||
|
|
||||||
- **OQ-10**: Git adapter scope — smart protocol only or full server?
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [overview.md](../../overview.md)
|
|
||||||
- ADR-002: ProtocolHandler trait
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
status: planned
|
|
||||||
last_updated: 2026-06-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# alknet-http
|
|
||||||
|
|
||||||
> **Status: Planned** — This spec has not been written yet.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
HTTP handler implementing `ProtocolHandler` on ALPN `alknet/http`. Provides axum router with auth middleware, REST API, dashboard, and MCP endpoint. Also handles standard HTTP ALPNs (`h2`, `http/1.1`) and WebTransport upgrade on `h3`.
|
|
||||||
|
|
||||||
## Key Questions
|
|
||||||
|
|
||||||
- How does HttpAdapter handle both `alknet/http` and standard ALPNs (`h2`, `http/1.1`, `h3`)?
|
|
||||||
- WebTransport upgrade on `h3` — is this a separate handler or integrated into HttpAdapter?
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [overview.md](../../overview.md)
|
|
||||||
- ADR-002: ProtocolHandler trait
|
|
||||||
- ADR-006: ALPN string convention and connection model
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
status: planned
|
|
||||||
last_updated: 2026-06-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# alknet-msg
|
|
||||||
|
|
||||||
> **Status: Planned** — This spec has not been written yet.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Messaging handler implementing `ProtocolHandler` on ALPN `alknet/msg`. Provides E2E encrypted direct messages (encrypt with recipient's public key) and mixnet support (Chaum 1981: nested encryption, batch-and-reorder, return addresses as digital pseudonyms).
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [overview.md](../../overview.md)
|
|
||||||
- ADR-002: ProtocolHandler trait
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
status: planned
|
|
||||||
last_updated: 2026-06-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# alknet-napi
|
|
||||||
|
|
||||||
> **Status: Planned** — This spec has not been written yet.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Node.js native addon providing a call protocol client. Uses napi-rs for FFI. Depends only on alknet-call (not alknet-core) to keep the dependency tree minimal. Exposes connect/disconnect, call operations, and event subscriptions to JavaScript/TypeScript.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [overview.md](../../overview.md)
|
|
||||||
- ADR-003: Crate decomposition
|
|
||||||
- ADR-005: irpc as call protocol foundation
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
status: planned
|
|
||||||
last_updated: 2026-06-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# alknet-secret
|
|
||||||
|
|
||||||
> **Status: Planned** — This spec has not been written yet. The crate is already implemented and stable.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Standalone crate for BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, AES-256-GCM encryption, and the `SecretProtocol` irpc service. Does not depend on alknet-core.
|
|
||||||
|
|
||||||
## Key Questions
|
|
||||||
|
|
||||||
- **OQ-08**: Secret service integration point — irpc service, ALPN handler, or embedded library?
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [overview.md](../../overview.md)
|
|
||||||
- ADR-003: Crate decomposition (alknet-secret is standalone)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
status: planned
|
|
||||||
last_updated: 2026-06-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# alknet-sftp
|
|
||||||
|
|
||||||
> **Status: Planned** — This spec has not been written yet.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
SFTP handler implementing `ProtocolHandler` on ALPN `alknet/sftp`. Provides russh-sftp protocol core with 26 packet types, custom serde codec, and pure data transformation. WASM-ready: only `read_packet()` couples to I/O.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [overview.md](../../overview.md)
|
|
||||||
- ADR-002: ProtocolHandler trait
|
|
||||||
- russh-sftp reference: `docs/research/references/ssh/russh-sftp/`
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
status: planned
|
|
||||||
last_updated: 2026-06-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# alknet-ssh
|
|
||||||
|
|
||||||
> **Status: Planned** — This spec has not been written yet. It will be produced as part of Phase 2 architecture work.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
SSH handler implementing `ProtocolHandler` on ALPN `alknet/ssh`. Provides russh-based SSH-2 handshake, channel multiplexing, SOCKS5 proxy, and port forwarding (direct-tcpip, forwarded-tcpip, streamlocal-forward).
|
|
||||||
|
|
||||||
## Port Source
|
|
||||||
|
|
||||||
| Old module | Lines | Notes |
|
|
||||||
|---|---|---|
|
|
||||||
| `src/interface/ssh.rs` | 982 | SSH channel handling |
|
|
||||||
| `src/server/handler.rs` | 974 | SSH server handler |
|
|
||||||
| `src/server/channel_proxy.rs` | 555 | Channel proxy |
|
|
||||||
| `src/client/*` | ~1900 | SOCKS5 client, connect logic |
|
|
||||||
| `src/socks5/*` | ~800 | SOCKS5 protocol |
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [overview.md](../../overview.md)
|
|
||||||
- ADR-002: ProtocolHandler trait
|
|
||||||
- ADR-004: Auth as shared core
|
|
||||||
- russh reference: `docs/research/references/ssh/russh/`
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
status: planned
|
|
||||||
last_updated: 2026-06-15
|
|
||||||
---
|
|
||||||
|
|
||||||
# alknet (CLI)
|
|
||||||
|
|
||||||
> **Status: Planned** — This spec has not been written yet.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
CLI binary that assembles all handler crates and starts the alknet endpoint. Registers ProtocolHandler implementations with the ALPN router based on configuration. The only crate that depends on all handler crates.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [overview.md](../../overview.md)
|
|
||||||
- ADR-003: Crate decomposition
|
|
||||||
@@ -53,5 +53,6 @@ pub trait ProtocolHandler: Send + Sync + 'static {
|
|||||||
- Pivot proposal: `docs/research/pivot/alpn-service-architecture.md`
|
- Pivot proposal: `docs/research/pivot/alpn-service-architecture.md`
|
||||||
- ADR-001: ALPN-based protocol dispatch
|
- ADR-001: ALPN-based protocol dispatch
|
||||||
- ADR-004: Auth as shared core (IdentityProvider)
|
- ADR-004: Auth as shared core (IdentityProvider)
|
||||||
|
- ADR-007: BiStream type definition (revised this ADR's signature from BiStream to Connection)
|
||||||
- iroh ProtocolHandler pattern: `docs/research/references/iroh/`
|
- iroh ProtocolHandler pattern: `docs/research/references/iroh/`
|
||||||
- Replaces StreamInterface, MessageInterface, and ListenerConfig
|
- Replaces StreamInterface, MessageInterface, and ListenerConfig
|
||||||
119
docs/architecture/decisions/007-bistream-type-definition.md
Normal file
119
docs/architecture/decisions/007-bistream-type-definition.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# ADR-007: BiStream Type Definition
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
OQ-01 asked whether BiStream should be a concrete type wrapping quinn's `SendStream` + `RecvStream`, a trait `BiStream: AsyncRead + AsyncWrite + Send + Unpin`, or a type alias/newtype. This is a one-way door decision: if BiStream is a concrete type bound to a specific QUIC library, WASM targets and alternative transports cannot implement it. If it's a trait, the door stays open.
|
||||||
|
|
||||||
|
### iroh's pattern
|
||||||
|
|
||||||
|
iroh's `ProtocolHandler::accept` receives a `Connection`, not a stream. The handler calls `connection.accept_bi()` to get `(SendStream, RecvStream)` pairs. This means iroh's handlers own the entire connection lifecycle and can open/accept multiple streams.
|
||||||
|
|
||||||
|
### Alknet's pattern differs
|
||||||
|
|
||||||
|
Alknet's handlers are different from iroh's for two reasons:
|
||||||
|
|
||||||
|
1. **One ALPN per connection** (ADR-006). An incoming connection is already dispatched to exactly one handler by ALPN. The handler receives the connection and can manage streams however it wants.
|
||||||
|
|
||||||
|
2. **Some handlers need connection-level ownership**. SSH multiplexes channels over multiple streams within a single connection. The call protocol opens a new stream per operation. These handlers need the connection, not just a single stream.
|
||||||
|
|
||||||
|
### WASM constraint
|
||||||
|
|
||||||
|
If alknet-core defines BiStream as `quinn::SendStream + quinn::RecvStream` joined via `tokio::io::join`, then:
|
||||||
|
- WASM targets cannot implement it (quinn doesn't compile to WASM)
|
||||||
|
- WebTransport clients in browsers cannot participate as full peers
|
||||||
|
- The cost of making BiStream a trait later would require changing every handler's signature
|
||||||
|
|
||||||
|
If BiStream is a trait, WASM targets implement it over WebTransport streams. Native targets implement it over quinn streams. The cost is minimal — a trait vs a concrete type adds a small amount of indirection and trait object overhead that is negligible compared to I/O latency.
|
||||||
|
|
||||||
|
### Testing constraint
|
||||||
|
|
||||||
|
A BiStream trait allows test implementations (in-memory channels, mock streams) without requiring a running QUIC connection. A concrete quinn type requires mocking at a higher level (connection mocking) which is more complex.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
### BiStream is a trait
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait BiStream: AsyncRead + AsyncWrite + Send + Unpin {}
|
||||||
|
```
|
||||||
|
|
||||||
|
Handlers receive a `Connection` (not a single BiStream) in their `handle` method. This differs from the original ADR-002 signature and aligns with iroh's proven pattern.
|
||||||
|
|
||||||
|
### Revised ProtocolHandler signature
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[async_trait]
|
||||||
|
pub trait ProtocolHandler: Send + Sync + 'static {
|
||||||
|
fn alpn(&self) -> &'static [u8];
|
||||||
|
async fn handle(&self, connection: Connection, auth: &AuthContext) -> Result<(), HandlerError>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `Connection` wraps a QUIC connection (or, in test contexts, a mock) and provides:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct Connection {
|
||||||
|
// Private: wraps the underlying QUIC connection or test mock
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
pub async fn accept_bi(&self) -> Result<(SendStream, RecvStream), StreamError>;
|
||||||
|
pub async fn open_bi(&self) -> Result<(SendStream, RecvStream), StreamError>;
|
||||||
|
pub fn remote_alpn(&self) -> &[u8];
|
||||||
|
// Additional methods as needed: close, remote_addr, etc.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`SendStream` and `RecvStream` are concrete types that implement `AsyncWrite` and `AsyncRead` respectively. They wrap the underlying QUIC stream types.
|
||||||
|
|
||||||
|
### Why Connection, not BiStream, as the handler parameter
|
||||||
|
|
||||||
|
The original ADR-002 specified `handle(&self, stream: BiStream, auth: &AuthContext)`. This was modeled on the idea that a handler receives a single bidirectional stream. But:
|
||||||
|
|
||||||
|
- **SSH** needs to open/accept multiple streams (channels) on one connection
|
||||||
|
- **Call protocol** opens a new stream per operation
|
||||||
|
- **HTTP** maps requests to streams within an HTTP/2 or HTTP/3 connection
|
||||||
|
- **iroh** already uses this pattern successfully
|
||||||
|
|
||||||
|
Passing a single BiStream would force handlers that need multiple streams to somehow obtain the Connection through other means, which is awkward. Passing the Connection directly is simpler and more flexible.
|
||||||
|
|
||||||
|
Handlers that only need a single stream (simple protocols) call `connection.accept_bi().await` once and work with that stream. Handlers that need multiple streams (SSH, call) use the Connection to open/accept as needed.
|
||||||
|
|
||||||
|
### Why BiStream is still defined as a trait
|
||||||
|
|
||||||
|
Even though handlers receive a `Connection` rather than a single `BiStream`, the BiStream trait is still useful:
|
||||||
|
|
||||||
|
1. **Client-side**: A client connecting to an alknet endpoint needs a way to represent "I have a bidirectional stream to speak my protocol on." That stream should be implementable over WebTransport in WASM.
|
||||||
|
2. **Testing**: Mock BiStream implementations for unit tests.
|
||||||
|
3. **Portability**: If alknet later supports transports other than QUIC (raw TCP, iroh P2P), those transports need to produce BiStream-compatible streams.
|
||||||
|
|
||||||
|
The BiStream trait is a thin convenience — `AsyncRead + AsyncWrite + Send + Unpin` — that can be implemented by any byte transport. It does not mandate tokio or quinn.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
**Positive:**
|
||||||
|
- WASM door stays open: browser clients can implement BiStream over WebTransport streams
|
||||||
|
- Testing is straightforward: mock BiStream implementations without QUIC infrastructure
|
||||||
|
- Handlers that need multiple streams (SSH, call) have direct access to the Connection
|
||||||
|
- Handlers that need a single stream call `accept_bi()` once — simple case stays simple
|
||||||
|
- Aligns with iroh's proven ProtocolHandler pattern
|
||||||
|
- Alternative transports (TCP, iroh P2P) can implement Connection and BiStream traits
|
||||||
|
|
||||||
|
**Negative:**
|
||||||
|
- Slight runtime overhead from trait dispatch vs concrete types (negligible compared to I/O)
|
||||||
|
- Two concepts (Connection and BiStream) instead of one (BiStream alone) — more types in alknet-core
|
||||||
|
- ADR-002's `handle` signature changes from `(BiStream, AuthContext)` to `(Connection, AuthContext)` — this is a revision to the original trait signature
|
||||||
|
- Handlers must call `accept_bi()` explicitly even for simple protocols — one additional line of code per handler
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- ADR-002: ProtocolHandler trait (signature revised by this ADR)
|
||||||
|
- ADR-003: Crate decomposition
|
||||||
|
- ADR-006: ALPN string convention and connection model
|
||||||
|
- OQ-01: BiStream type definition (resolved by this ADR)
|
||||||
|
- iroh ProtocolHandler pattern: `docs/research/references/iroh/iroh/`
|
||||||
|
- Pivot proposal: `docs/research/pivot/alpn-service-architecture.md`
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# ADR-008: Secret Service Integration Point
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
alknet-secret is a standalone crate with zero alknet crate dependencies. It provides BIP39 mnemonic generation, SLIP-0010 Ed25519 HD key derivation, AES-256-GCM encryption, and an irpc-based `SecretProtocol` service. It is already implemented and stable.
|
||||||
|
|
||||||
|
The question (OQ-08) is: how does the rest of the alknet system access alknet-secret's capabilities? The options are:
|
||||||
|
|
||||||
|
1. **irpc service over `alknet/call`**: Other services call SecretProtocol operations through the call protocol on `alknet/call`. The secret service is just another set of operations registered in the call protocol's operation registry.
|
||||||
|
|
||||||
|
2. **ALPN handler on `alknet/secret`**: alknet-secret implements `ProtocolHandler` and gets its own ALPN. Remote nodes call it over a dedicated QUIC connection.
|
||||||
|
|
||||||
|
3. **Direct library dependency**: alknet-core or handler crates depend on alknet-secret directly, breaking its independence.
|
||||||
|
|
||||||
|
4. **CLI-embedded with call protocol exposure**: The CLI binary instantiates SecretServiceHandle locally and registers secret operations in the call protocol's registry.
|
||||||
|
|
||||||
|
This is a one-way door because if alknet-secret gets pulled into alknet-core as a dependency, its independence is permanently lost. The standalone property is valuable — alknet-secret has no QUIC, no tokio runtime requirement (the handle works without it), and no alknet crate dependencies. It can be used in contexts where QUIC networking doesn't exist (CLI tools, test harnesses, WASM key derivation).
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
**Option 4: CLI-embedded with call protocol exposure.**
|
||||||
|
|
||||||
|
The CLI binary (the `alknet` crate) is the integration point. It:
|
||||||
|
|
||||||
|
1. Instantiates `SecretServiceHandle` locally at startup (or on-demand with Unlock/Lock lifecycle).
|
||||||
|
2. Registers secret operations (DeriveEd25519, DeriveEncryptionKey, Encrypt, Decrypt, etc.) in the call protocol's operation registry.
|
||||||
|
3. Other handlers access secret capabilities by calling operations on `alknet/call` — they don't import alknet-secret directly.
|
||||||
|
|
||||||
|
alknet-secret remains standalone with no alknet crate dependencies. Its `SecretServiceHandle` is used directly (in-process, no serialization) by the CLI binary. Its `SecretProtocol` irpc service is available for remote access through the call protocol.
|
||||||
|
|
||||||
|
**alknet-secret does NOT get its own ALPN.** Here's why:
|
||||||
|
|
||||||
|
- `alknet/secret` as a separate ALPN would mean a remote node opens a QUIC connection to access key derivation — this is architecturally wrong. Key derivation is a local operation that should never cross the network in its raw form.
|
||||||
|
- If a remote node needs derived keys (e.g., for end-to-end encryption), the local node derives them and sends only the public component over `alknet/call` — never the seed or private key.
|
||||||
|
- The secret service's Unlock/Lock lifecycle (holding the master seed in RAM) is inherently local. There's no safe way to expose Unlock/Lock over the network.
|
||||||
|
|
||||||
|
**What if a handler needs a key at runtime?** The handler calls through the call protocol. The CLI registers secret operations in the call registry at startup. The call protocol routes the request to the locally-running SecretServiceHandle. No handler crate depends on alknet-secret.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
**Positive:**
|
||||||
|
- alknet-secret remains fully standalone — no QUIC dependency, no tokio runtime requirement for the handle
|
||||||
|
- Key derivation and encryption are local-only by default — the master seed never leaves the node
|
||||||
|
- Remote access to public key material (not secrets) flows through the existing call protocol — no separate ALPN needed
|
||||||
|
- The CLI binary is the single integration point — clean dependency graph, no circular dependencies
|
||||||
|
- The `SecretServiceHandle` is used in-process with zero serialization overhead — direct method calls, not irpc messages
|
||||||
|
- Test harnesses can use `SecretServiceHandle` directly without any QUIC infrastructure
|
||||||
|
|
||||||
|
**Negative:**
|
||||||
|
- Handlers that need keys must go through the call protocol — this adds a hop even for local calls (mitigated: local call protocol calls can be short-circuited to direct method calls via irpc's local dispatch)
|
||||||
|
- The CLI binary has a larger dependency tree since it imports both alknet-call and alknet-secret (expected: the CLI assembles everything)
|
||||||
|
- If the call protocol is not yet running when a handler needs a key, the handler must wait for initialization (mitigated: the CLI starts SecretServiceHandle before accepting connections)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- ADR-003: Crate decomposition (alknet-secret is standalone)
|
||||||
|
- ADR-005: irpc as call protocol foundation
|
||||||
|
- OQ-08: Secret service integration point (resolved by this ADR)
|
||||||
|
- alknet-secret implementation: `crates/alknet-secret/`
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# ADR-009: One-Way Door Decision Framework
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Not all architectural decisions carry the same reversal cost. Some decisions are easy to change later — if you pick the wrong data structure, you refactor. Other decisions are nearly impossible to reverse — if you build a type hierarchy that forecloses WASM compatibility, every handler written against that hierarchy must be rewritten.
|
||||||
|
|
||||||
|
This distinction matters especially during Phase 0 (exploration) and early Phase 1 (architecture). The project is post-pivot with foundational ADRs in place but no implementation code yet (except alknet-secret). Decisions made now shape the API surface that every handler depends on.
|
||||||
|
|
||||||
|
Without an explicit framework, one-way doors can be treated as casually as two-way doors, leading to costly rework. Or conversely, two-way doors can be over-analyzed, blocking progress on decisions that are cheap to reverse.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
### Classification
|
||||||
|
|
||||||
|
Every architectural decision is classified as one of:
|
||||||
|
|
||||||
|
**One-way door** — Reversing this decision requires rewriting significant code across multiple crates or permanently closes a capability door. Examples:
|
||||||
|
- BiStream as a concrete quinn type (closes WASM door permanently)
|
||||||
|
- alknet-secret pulled into alknet-core as a dependency (loses standalone property permanently)
|
||||||
|
- ProtocolHandler signature changes (every handler must be rewritten)
|
||||||
|
|
||||||
|
**Two-way door** — Reversing this decision is cheap or additive. Examples:
|
||||||
|
- Static vs dynamic handler registration (can add ArcSwap later)
|
||||||
|
- Single transport vs multi-transport endpoint (can add transport trait later)
|
||||||
|
- Call protocol stream model (can add multiplexing later)
|
||||||
|
|
||||||
|
### Process
|
||||||
|
|
||||||
|
- **One-way doors** require an ADR before implementation. If the right choice is unclear, validate with a POC before writing the ADR. If a POC can't resolve the uncertainty within a reasonable timebox, default to the option that keeps more doors open.
|
||||||
|
- **Two-way doors** can be decided during implementation. Start with the simplest option and add complexity when needed. Note the decision in a commit message or a brief ADR if the context is worth capturing, but don't block on it.
|
||||||
|
- When in doubt, classify up. If it's unclear whether a door is one-way or two-way, treat it as one-way until proven otherwise.
|
||||||
|
|
||||||
|
### WASM as a design constraint
|
||||||
|
|
||||||
|
WASM compatibility is not an immediate implementation goal, but it is a **design constraint on one-way doors**. Decisions that would permanently prevent WASM targets from participating as peers require explicit justification. This means:
|
||||||
|
- Core types (BiStream, ProtocolHandler, AuthContext) must not assume tokio or quinn
|
||||||
|
- Protocol parsers that are pure data transformations should remain transport-agnostic
|
||||||
|
- The cost of keeping the WASM door open is low (trait vs concrete type, abstracted I/O) and the cost of closing it is high (impossible to reverse without rewriting every handler)
|
||||||
|
|
||||||
|
This is not "WASM support now." It's "don't close the WASM door accidentally."
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
**Positive:**
|
||||||
|
- One-way doors get the deliberation they deserve — ADRs, POCs, explicit justification
|
||||||
|
- Two-way doors don't block progress — start simple, add complexity when needed
|
||||||
|
- WASM compatibility is preserved as a constraint, not treated as an active deliverable
|
||||||
|
- The framework creates a shared vocabulary for discussing decision urgency ("is this a one-way door?")
|
||||||
|
|
||||||
|
**Negative:**
|
||||||
|
- Classification requires judgment — some decisions are genuinely ambiguous (mitigated: classify up when in doubt)
|
||||||
|
- POC timeboxing can feel constraining on genuine hard problems (mitigated: the timebox is "reasonable," not "arbitrary")
|
||||||
|
- The framework adds a step to every architectural discussion ("is this one-way or two-way?") — but this step is fast and prevents expensive mistakes
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- ADR-007: BiStream type definition (one-way door: WASM compatibility)
|
||||||
|
- ADR-008: Secret service integration point (one-way door: standalone crate independence)
|
||||||
|
- SDD process: `docs/sdd_process.md` (Phase 0 exploration, POC specialist)
|
||||||
|
- Pivot proposal: `docs/research/pivot/alpn-service-architecture.md`
|
||||||
@@ -1,165 +1,116 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-06-15
|
last_updated: 2026-06-16
|
||||||
---
|
---
|
||||||
|
|
||||||
# Open Questions
|
# Open Questions
|
||||||
|
|
||||||
Questions are organized by theme. Each question has a stable OQ-ID for cross-referencing from spec documents.
|
Questions are organized by theme. Each question has a stable OQ-ID for cross-referencing from spec documents.
|
||||||
|
|
||||||
|
Door type classifications follow ADR-009:
|
||||||
|
- **One-way door**: Reversal requires rewriting significant code or permanently closes a capability. Requires ADR before implementation.
|
||||||
|
- **Two-way door**: Reversal is cheap or additive. Can be decided during implementation.
|
||||||
|
|
||||||
## Theme: Core Types
|
## Theme: Core Types
|
||||||
|
|
||||||
### OQ-01: BiStream Type Definition
|
### OQ-01: BiStream Type Definition
|
||||||
|
|
||||||
- **Origin**: [overview.md](overview.md), future [crates/alknet-core/spec.md](crates/alknet-core/spec.md)
|
- **Origin**: [overview.md](overview.md)
|
||||||
- **Status**: open
|
- **Status**: resolved
|
||||||
|
- **Door type**: One-way
|
||||||
- **Priority**: high
|
- **Priority**: high
|
||||||
- **Resolution**: (pending)
|
- **Resolution**: BiStream is a trait (`AsyncRead + AsyncWrite + Send + Unpin`). Handlers receive a `Connection` (not a single BiStream). This preserves the WASM door — browser clients can implement BiStream over WebTransport streams. See ADR-007.
|
||||||
- **Cross-references**: ADR-002
|
- **Cross-references**: ADR-002, ADR-007, ADR-009
|
||||||
|
|
||||||
What exactly does BiStream expose? The pivot proposal defines it as a joined `(SendStream, RecvStream)` implementing `AsyncRead + AsyncWrite`. Should it be:
|
|
||||||
- A concrete type wrapping quinn's `SendStream` + `RecvStream`?
|
|
||||||
- A trait `BiStream: AsyncRead + AsyncWrite + Send + Unpin` with a QuinnBiStream implementation?
|
|
||||||
- A type alias or newtype?
|
|
||||||
|
|
||||||
The choice affects WASM compatibility (quinn doesn't compile to WASM) and testing (mock BiStream needs to be constructible without a QUIC connection). If BiStream is a trait, WASM targets can implement it over WebTransport streams.
|
|
||||||
|
|
||||||
### OQ-02: AuthContext Resolution Timing
|
### OQ-02: AuthContext Resolution Timing
|
||||||
|
|
||||||
- **Origin**: [overview.md](overview.md), future [crates/alknet-core/spec.md](crates/alknet-core/spec.md)
|
- **Origin**: [overview.md](overview.md)
|
||||||
- **Status**: resolved
|
- **Status**: resolved
|
||||||
|
- **Door type**: One-way
|
||||||
- **Priority**: high
|
- **Priority**: high
|
||||||
- **Resolution**: Hybrid model (Option C) — endpoint resolves what it can (e.g., TLS client certificate), handler resolves what it must (e.g., AuthToken in first frame). AuthContext may be partial when `handle()` is called. See ADR-002 and ADR-004.
|
- **Resolution**: Hybrid model (Option C) — endpoint resolves what it can (e.g., TLS client certificate), handler resolves what it must (e.g., AuthToken in first frame). AuthContext may be partial when `handle()` is called. See ADR-004.
|
||||||
- **Cross-references**: ADR-002, ADR-004
|
- **Cross-references**: ADR-002, ADR-004
|
||||||
|
|
||||||
~~Should AuthContext be fully resolved before `handle()` is called, or should the handler participate in credential extraction?~~
|
|
||||||
|
|
||||||
Resolved: The hybrid approach. The endpoint resolves TLS-level credentials (client certificate fingerprints). Handlers that use protocol-level credentials (AuthToken, Bearer headers) extract them inside `handle()` and call `IdentityProvider` to resolve. The `AuthContext` passed to `handle()` may contain only transport-level information.
|
|
||||||
|
|
||||||
## Theme: ALPN and Routing
|
## Theme: ALPN and Routing
|
||||||
|
|
||||||
### OQ-03: ALPN String Naming Convention
|
### OQ-03: ALPN String Naming Convention
|
||||||
|
|
||||||
- **Origin**: [overview.md](overview.md)
|
- **Origin**: [overview.md](overview.md)
|
||||||
- **Status**: resolved
|
- **Status**: resolved
|
||||||
|
- **Door type**: One-way
|
||||||
- **Priority**: medium
|
- **Priority**: medium
|
||||||
- **Resolution**: Custom ALPNs use `alknet/<name>` prefix (no version), standard ALPNs use IANA strings, no version negotiation initially. See ADR-006.
|
- **Resolution**: Custom ALPNs use `alknet/<name>` prefix (no version), standard ALPNs use IANA strings. No version negotiation initially. See ADR-006.
|
||||||
- **Cross-references**: ADR-001, ADR-006
|
- **Cross-references**: ADR-001, ADR-006
|
||||||
|
|
||||||
~~The pivot proposal uses `alknet/ssh`, `alknet/call`, `alknet/git`, etc. Questions:~~
|
|
||||||
|
|
||||||
Resolved: See ADR-006 for the full convention. Custom ALPNs use `alknet/` prefix without version numbers. Standard ALPNs (`h2`, `http/1.1`, `h3`) use their IANA strings and route to HttpAdapter. If a protocol needs a breaking change, a new ALPN string is registered.
|
|
||||||
|
|
||||||
### OQ-04: Dynamic Handler Registration at Runtime vs Static at Startup
|
### OQ-04: Dynamic Handler Registration at Runtime vs Static at Startup
|
||||||
|
|
||||||
- **Origin**: [overview.md](overview.md)
|
- **Origin**: [overview.md](overview.md)
|
||||||
- **Status**: open
|
- **Status**: open
|
||||||
- **Priority**: medium
|
- **Door type**: Two-way
|
||||||
- **Resolution**: (pending)
|
- **Priority**: low
|
||||||
|
- **Resolution**: (deferred to implementation) Start with static registration at startup. The `ArcSwap<DynamicConfig>` pattern from the previous implementation can be applied later if needed. ALPN advertisement requires endpoint restart anyway (TLS ALPN is negotiated during handshake), so dynamic registration has limited value in v1.
|
||||||
- **Cross-references**: ADR-001
|
- **Cross-references**: ADR-001
|
||||||
|
|
||||||
Can handlers be registered and deregistered while the endpoint is running, or only at startup?
|
|
||||||
|
|
||||||
The previous implementation used `ArcSwap<DynamicConfig>` for hot-reloading routing rules. The same pattern could apply to handler registration. However:
|
|
||||||
- Adding a handler at runtime requires updating the TLS ALPN advertisement (may require endpoint restart)
|
|
||||||
- Removing a handler at runtime affects in-flight connections
|
|
||||||
- The CLI assembles handlers at startup — is dynamic registration even needed?
|
|
||||||
|
|
||||||
## Theme: Transport and Endpoint
|
## Theme: Transport and Endpoint
|
||||||
|
|
||||||
### OQ-05: Multi-Transport Endpoint
|
### OQ-05: Multi-Transport Endpoint
|
||||||
|
|
||||||
- **Origin**: [overview.md](overview.md)
|
- **Origin**: [overview.md](overview.md)
|
||||||
- **Status**: open
|
- **Status**: open
|
||||||
- **Priority**: medium
|
- **Door type**: Two-way
|
||||||
- **Resolution**: (pending)
|
- **Priority**: low
|
||||||
|
- **Resolution**: (deferred to implementation) Start with quinn (QUIC over UDP). The endpoint can be made transport-agnostic later by abstracting the connection accept loop behind a trait. iroh connectivity produces QUIC connections that can feed into the same ALPN router.
|
||||||
- **Cross-references**: ADR-001
|
- **Cross-references**: ADR-001
|
||||||
|
|
||||||
The previous implementation supported TCP, TLS, and iroh (QUIC P2P) transports. In the new model, does the endpoint:
|
|
||||||
- Accept connections on a single QUIC listener (quinn) and the ALPN handles the rest?
|
|
||||||
- Also support raw TCP listeners (for clients that don't speak QUIC)?
|
|
||||||
- Support iroh as an additional transport that produces QUIC connections?
|
|
||||||
|
|
||||||
iroh's Router pattern accepts a quinn::Endpoint and dispatches by ALPN. The alknet endpoint likely wraps a quinn::Endpoint with TLS configuration that advertises all registered ALPNs. Iroh connectivity could be an additional transport that produces the same BiStream type.
|
|
||||||
|
|
||||||
### OQ-06: Server-Side ALPN vs Client-Side ALPN
|
### OQ-06: Server-Side ALPN vs Client-Side ALPN
|
||||||
|
|
||||||
- **Origin**: ADR-001
|
- **Origin**: ADR-001
|
||||||
- **Status**: resolved
|
- **Status**: resolved
|
||||||
|
- **Door type**: One-way
|
||||||
- **Priority**: low
|
- **Priority**: low
|
||||||
- **Resolution**: One ALPN per connection. Clients open one QUIC connection per ALPN. See ADR-006.
|
- **Resolution**: One ALPN per connection. Clients open one QUIC connection per ALPN. QUIC connections are cheap (multiplexed over the same UDP flow). See ADR-006.
|
||||||
- **Cross-references**: ADR-001, ADR-006
|
- **Cross-references**: ADR-001, ADR-006
|
||||||
|
|
||||||
~~The ADR focuses on server-side dispatch (client connects, server selects ALPN). What about the client side? When a client opens a stream to a specific ALPN on an existing connection, does it: - Open a new QUIC connection with the target ALPN? - Open a bidirectional stream on an existing connection with ALPN metadata?~~
|
|
||||||
|
|
||||||
Resolved: ALPN is negotiated per-connection, not per-stream. A client that wants multiple ALPNs opens one QUIC connection per ALPN. QUIC connections are cheap (multiplexed over the same UDP flow). See ADR-006.
|
|
||||||
|
|
||||||
## Theme: Call Protocol
|
## Theme: Call Protocol
|
||||||
|
|
||||||
### OQ-07: Call Protocol Scope Within a Connection
|
### OQ-07: Call Protocol Scope Within a Connection
|
||||||
|
|
||||||
- **Origin**: ADR-005
|
- **Origin**: ADR-005
|
||||||
- **Status**: open
|
- **Status**: open
|
||||||
|
- **Door type**: Two-way
|
||||||
- **Priority**: medium
|
- **Priority**: medium
|
||||||
- **Resolution**: (pending)
|
- **Resolution**: (deferred to implementation) Start with one stream per operation/request in the call protocol. Multiplexing within a stream can be added later if needed without breaking existing clients. Resolve when speccing alknet-call.
|
||||||
- **Cross-references**: ADR-005
|
- **Cross-references**: ADR-005
|
||||||
|
|
||||||
If a client opens a connection with ALPN `alknet/call`, can it make multiple operations over that connection? Is each operation a separate QUIC stream, or can operations be multiplexed within a single stream?
|
|
||||||
|
|
||||||
irpc supports both request/response and streaming. The mapping to QUIC streams needs definition:
|
|
||||||
- One stream per request/response pair?
|
|
||||||
- One stream per streaming subscription?
|
|
||||||
- Or a single long-lived stream with multiplexed operations?
|
|
||||||
|
|
||||||
## Theme: Security
|
## Theme: Security
|
||||||
|
|
||||||
### OQ-08: Secret Service Integration Point
|
### OQ-08: Secret Service Integration Point
|
||||||
|
|
||||||
- **Origin**: [overview.md](overview.md)
|
- **Origin**: [overview.md](overview.md)
|
||||||
- **Status**: open
|
- **Status**: resolved
|
||||||
|
- **Door type**: One-way
|
||||||
- **Priority**: medium
|
- **Priority**: medium
|
||||||
- **Resolution**: (pending)
|
- **Resolution**: CLI-embedded with call protocol exposure. The CLI binary instantiates `SecretServiceHandle` locally and registers secret operations in the call protocol's operation registry. alknet-secret has no ALPN and no alknet-core dependency. Key derivation is local-only; only public key material crosses the network via `alknet/call`. See ADR-008.
|
||||||
- **Cross-references**: ADR-004
|
- **Cross-references**: ADR-003, ADR-005, ADR-008
|
||||||
|
|
||||||
alknet-secret is standalone (no alknet-core dependency). How does the rest of the system access it?
|
## Deferred Questions
|
||||||
- As an irpc service that other services call? (current implementation)
|
|
||||||
- As an ALPN handler on `alknet/secret` that only internal services use?
|
|
||||||
- As a library that alknet-core calls directly (breaking the independence)?
|
|
||||||
- As a library that the CLI embeds, exposing it via the call protocol?
|
|
||||||
|
|
||||||
The answer affects whether alknet-secret needs to know about BiStream and ProtocolHandler, or if it remains purely an irpc service that the CLI bootstraps and other services call over local irpc.
|
These questions are acknowledged but not active. They will be promoted to open when their crate becomes a Phase 1 implementation target.
|
||||||
|
|
||||||
## Theme: WASM and Browser
|
|
||||||
|
|
||||||
### OQ-09: WASM Target Boundaries
|
### OQ-09: WASM Target Boundaries
|
||||||
|
|
||||||
- **Origin**: [overview.md](overview.md)
|
- **Origin**: [overview.md](overview.md)
|
||||||
- **Status**: open
|
- **Status**: deferred
|
||||||
- **Priority**: medium
|
- **Door type**: One-way (when applicable)
|
||||||
- **Resolution**: (pending)
|
- **Priority**: low
|
||||||
- **Cross-references**: OQ-01
|
- **Resolution**: Not an active question — WASM compatibility is a design constraint (see ADR-009, overview.md design principles), not a deliverable. Specific WASM targeting decisions will be made when individual crates are implemented. The BiStream trait decision (ADR-007) has already preserved the most important WASM door.
|
||||||
|
- **Cross-references**: ADR-007, ADR-009
|
||||||
Which crates need to compile to WASM?
|
|
||||||
- alknet-call client (for browser/NAPI usage) — yes
|
|
||||||
- alknet-sftp protocol core — yes (browser SFTP clients)
|
|
||||||
- alknet-git pkt-line parser — potentially
|
|
||||||
- alknet-core (BiStream, AuthContext) — only the types, not the endpoint/router
|
|
||||||
|
|
||||||
This affects the BiStream type definition (OQ-01) and dependency choices. If alknet-call's client needs to work in WASM, it can't depend on tokio or quinn directly — it needs abstracted I/O.
|
|
||||||
|
|
||||||
## Theme: Git and Distributed
|
|
||||||
|
|
||||||
### OQ-10: Git Adapter Scope — Smart Protocol Only or Full Server?
|
### OQ-10: Git Adapter Scope — Smart Protocol Only or Full Server?
|
||||||
|
|
||||||
- **Origin**: [overview.md](overview.md)
|
- **Origin**: [overview.md](overview.md)
|
||||||
- **Status**: open
|
- **Status**: deferred
|
||||||
|
- **Door type**: Two-way
|
||||||
- **Priority**: low
|
- **Priority**: low
|
||||||
- **Resolution**: (pending)
|
- **Resolution**: Deferred per the cleanup plan. Start with git smart protocol over QUIC streams. ERC721 integration and full server capabilities are additive. Resolve when speccing alknet-git.
|
||||||
- **Cross-references**: ADR-001
|
- **Cross-references**: ADR-001
|
||||||
|
|
||||||
The pivot proposal mentions Git over QUIC streams (no HTTP layer). Does the GitAdapter implement:
|
|
||||||
- Just the git smart protocol (ls-refs, fetch, receive-pack) over QUIC streams?
|
|
||||||
- A full git server with ref management, pack generation, etc.?
|
|
||||||
- The ERC721 integration for on-chain repository management?
|
|
||||||
|
|
||||||
This is deferred per the cleanup plan but worth noting as an open question.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-06-15
|
last_updated: 2026-06-16
|
||||||
---
|
---
|
||||||
|
|
||||||
# Alknet Overview
|
# Alknet Overview
|
||||||
@@ -64,15 +64,18 @@ The central abstraction. Every handler implements one trait:
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ProtocolHandler: Send + Sync + 'static {
|
pub trait ProtocolHandler: Send + Sync + 'static {
|
||||||
fn alpn(&self) -> &'static [u8];
|
fn alpn(&self) -> &'static [u8];
|
||||||
async fn handle(&self, stream: BiStream, auth: &AuthContext) -> Result<(), HandlerError>;
|
async fn handle(&self, connection: Connection, auth: &AuthContext) -> Result<(), HandlerError>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `alpn()` returns the handler's ALPN identifier (e.g., `b"alknet/ssh"`, `b"alknet/call"`)
|
- `alpn()` returns the handler's ALPN identifier (e.g., `b"alknet/ssh"`, `b"alknet/call"`)
|
||||||
- `handle()` receives a bidirectional stream and an `AuthContext` (which may be partial — see authentication section), returning `HandlerError` on failure
|
- `handle()` receives a `Connection` (not a single stream) and an `AuthContext` (which may be partial — see authentication section), returning `HandlerError` on failure
|
||||||
|
- Handlers that need a single stream call `connection.accept_bi()` once; handlers that multiplex (SSH, call) open/accept streams as needed
|
||||||
- Each handler manages its own wire format
|
- Each handler manages its own wire format
|
||||||
|
|
||||||
See [ADR-002](decisions/002-protocol-handler-trait.md) for the full rationale.
|
This differs from the original ADR-002 signature which passed `BiStream`. See ADR-007 for the rationale: handlers like SSH and call need connection-level ownership to manage multiple streams.
|
||||||
|
|
||||||
|
See [ADR-002](decisions/002-protocol-handler-trait.md) and [ADR-007](decisions/007-bistream-type-definition.md) for the full rationale.
|
||||||
|
|
||||||
## ALPN Registry
|
## ALPN Registry
|
||||||
|
|
||||||
@@ -88,7 +91,7 @@ See [ADR-002](decisions/002-protocol-handler-trait.md) for the full rationale.
|
|||||||
| `h3` | HttpAdapter (WebTransport upgrade) | Browser-compatible WebTransport, then ALPN upgrade |
|
| `h3` | HttpAdapter (WebTransport upgrade) | Browser-compatible WebTransport, then ALPN upgrade |
|
||||||
| `h2` / `http/1.1` | HttpAdapter | Standard HTTP for browsers, curl |
|
| `h2` / `http/1.1` | HttpAdapter | Standard HTTP for browsers, curl |
|
||||||
|
|
||||||
> **Note**: `alknet/secret` is not in the ALPN registry because alknet-secret is a standalone crate with no alknet-core dependency. Its integration point is an open question (see OQ-08).
|
> **Note**: `alknet/secret` is not in the ALPN registry. alknet-secret is a standalone crate with no alknet-core dependency. The CLI binary embeds it and exposes its operations through `alknet/call`. See ADR-008 for the integration rationale.
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
@@ -115,13 +118,20 @@ See [ADR-005](decisions/005-irpc-as-call-protocol-foundation.md) for the full ra
|
|||||||
|
|
||||||
## WASM Compatibility
|
## WASM Compatibility
|
||||||
|
|
||||||
Handlers that operate on byte streams with pure data transformation compile to WASM:
|
WASM is not an immediate implementation target, but it is a **design constraint on one-way doors** (see ADR-009). Decisions that would permanently prevent WASM targets from participating as peers require explicit justification.
|
||||||
|
|
||||||
|
This means:
|
||||||
|
- Core types (BiStream, Connection, ProtocolHandler, AuthContext) must not assume tokio or quinn
|
||||||
|
- Protocol parsers that are pure data transformations remain transport-agnostic
|
||||||
|
- The cost of keeping the WASM door open is low (trait vs concrete type, abstracted I/O) and the cost of closing it is high
|
||||||
|
|
||||||
|
Handlers with transport-agnostic cores are particularly WASM-friendly:
|
||||||
- russh-sftp's protocol core is already transport-agnostic
|
- russh-sftp's protocol core is already transport-agnostic
|
||||||
- hickory-proto is `#![no_std]` with `wasm-bindgen` feature
|
- hickory-proto is `#![no_std]` with `wasm-bindgen` feature
|
||||||
- The call protocol's JSON framing is inherently cross-language
|
- The call protocol's JSON framing is inherently cross-language
|
||||||
- Git's pkt-line is simple enough to implement anywhere
|
- Git's pkt-line is simple enough to implement anywhere
|
||||||
|
|
||||||
A browser gets a WebTransport stream and speaks SFTP, Git, or call protocol directly.
|
A browser gets a WebTransport stream and speaks SFTP, Git, or call protocol directly — but only if we haven't closed that door with concrete type choices.
|
||||||
|
|
||||||
## Shared Types
|
## Shared Types
|
||||||
|
|
||||||
@@ -130,8 +140,9 @@ The following types live in alknet-core and are used across handler crates:
|
|||||||
| Type | Purpose |
|
| Type | Purpose |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `ProtocolHandler` | The trait every handler implements |
|
| `ProtocolHandler` | The trait every handler implements |
|
||||||
| `BiStream` | Bidirectional QUIC stream (`AsyncRead + AsyncWrite`) |
|
| `Connection` | QUIC connection (or mock) — handlers open/accept streams on it |
|
||||||
| `AuthContext` | Resolved identity for a connection |
|
| `BiStream` | Trait: `AsyncRead + AsyncWrite + Send + Unpin` — bidirectional byte stream |
|
||||||
|
| `AuthContext` | Resolved identity for a connection (may be partial) |
|
||||||
| `Identity` | Authenticated peer identity |
|
| `Identity` | Authenticated peer identity |
|
||||||
| `IdentityProvider` | Trait for resolving credentials to identity |
|
| `IdentityProvider` | Trait for resolving credentials to identity |
|
||||||
| `AuthToken` | Opaque authentication token |
|
| `AuthToken` | Opaque authentication token |
|
||||||
@@ -139,6 +150,24 @@ The following types live in alknet-core and are used across handler crates:
|
|||||||
| `DynamicConfig` | Hot-reloadable configuration (`ArcSwap`) |
|
| `DynamicConfig` | Hot-reloadable configuration (`ArcSwap`) |
|
||||||
| `ConfigReloadHandle` | Handle for triggering config reloads |
|
| `ConfigReloadHandle` | Handle for triggering config reloads |
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
### One-Way and Two-Way Doors
|
||||||
|
|
||||||
|
Not all decisions carry the same reversal cost. One-way door decisions (BiStream type, crate independence) require ADRs and possibly POCs before commitment. Two-way door decisions (static vs dynamic registration, single vs multi-transport) can be decided during implementation — start simple, add complexity when needed. See [ADR-009](decisions/009-one-way-door-decision-framework.md).
|
||||||
|
|
||||||
|
### WASM Door Preservation
|
||||||
|
|
||||||
|
WASM compatibility is not an active deliverable, but it is a design constraint. Decisions that would permanently close the WASM door (e.g., concrete quinn types in public APIs) require explicit justification. The cost of keeping the door open is low; the cost of closing it is irreversibly high.
|
||||||
|
|
||||||
|
### One ALPN, One Connection, One Handler
|
||||||
|
|
||||||
|
Each ALPN gets its own QUIC connection. The handler owns the entire connection lifecycle. Handlers that need multiple streams (SSH, call) call `connection.accept_bi()` or `connection.open_bi()` as needed. There is no multiplexing layer between connections.
|
||||||
|
|
||||||
|
### Handler Independence
|
||||||
|
|
||||||
|
No handler crate depends on another handler crate. Cross-handler communication goes through the call protocol (`alknet/call`) or through alknet-core's endpoint. The only crate that depends on all handlers is the CLI binary.
|
||||||
|
|
||||||
## Design Decisions
|
## Design Decisions
|
||||||
|
|
||||||
All design decisions are documented as ADRs in [decisions/](decisions/).
|
All design decisions are documented as ADRs in [decisions/](decisions/).
|
||||||
@@ -151,15 +180,19 @@ All design decisions are documented as ADRs in [decisions/](decisions/).
|
|||||||
| [004](decisions/004-auth-as-shared-core.md) | Auth as Shared Core | IdentityProvider in core, handlers extract credentials |
|
| [004](decisions/004-auth-as-shared-core.md) | Auth as Shared Core | IdentityProvider in core, handlers extract credentials |
|
||||||
| [005](decisions/005-irpc-as-call-protocol-foundation.md) | irpc as Call Protocol Foundation | Call protocol uses irpc for registry, framing, dispatch |
|
| [005](decisions/005-irpc-as-call-protocol-foundation.md) | irpc as Call Protocol Foundation | Call protocol uses irpc for registry, framing, dispatch |
|
||||||
| [006](decisions/006-alpn-convention-and-connection-model.md) | ALPN String Convention and Connection Model | `alknet/` prefix, one ALPN per connection |
|
| [006](decisions/006-alpn-convention-and-connection-model.md) | ALPN String Convention and Connection Model | `alknet/` prefix, one ALPN per connection |
|
||||||
|
| [007](decisions/007-bistream-type-definition.md) | BiStream Type Definition | BiStream is a trait, handlers receive Connection not BiStream |
|
||||||
|
| [008](decisions/008-secret-service-integration.md) | Secret Service Integration Point | CLI-embedded, exposed via call protocol, no ALPN for secrets |
|
||||||
|
| [009](decisions/009-one-way-door-decision-framework.md) | One-Way Door Decision Framework | Classify decisions by reversal cost; one-way doors need ADRs |
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
Open questions are tracked in [open-questions.md](open-questions.md). Key questions affecting this document:
|
Open questions are tracked in [open-questions.md](open-questions.md). Key questions affecting this document:
|
||||||
|
|
||||||
- **OQ-01**: BiStream type definition (open)
|
- **OQ-01**: BiStream type definition (resolved: trait, Connection parameter — see ADR-007)
|
||||||
- **OQ-02**: AuthContext resolution timing (resolved: hybrid — see ADR-004)
|
- **OQ-02**: AuthContext resolution timing (resolved: hybrid — see ADR-004)
|
||||||
- **OQ-03**: ALPN string naming convention (resolved: see ADR-006)
|
- **OQ-03**: ALPN string naming convention (resolved: see ADR-006)
|
||||||
- **OQ-04**: Dynamic handler registration at runtime vs static at startup (open)
|
- **OQ-04**: Dynamic handler registration at runtime vs static at startup (two-way door, defer to implementation)
|
||||||
|
- **OQ-08**: Secret service integration point (resolved: CLI-embedded via call protocol — see ADR-008)
|
||||||
|
|
||||||
## Failure Modes
|
## Failure Modes
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user