Name the three-endpoint-type model (web/native/iroh) and the entry-point vs. endpoint ALPN distinction. This untangles the hub/endpoint/ALPN-config confusion that OQ-62 hinted at: - A hub composes a SUBSET of three endpoint types (web, native, iroh), each with its own identity model, auth model, and transport(s). A full hub runs all three; a minimal hub runs iroh alone (no public IP required). The first real use case is web + native. Corrects the hub README's 'must support TCP+TLS and QUIC' framing. - ALPNs split into entry points (accepted without identity — h2, http/1.1, future alknet/register) and endpoints (identity required before dispatch — alknet/channels, alknet/call, alknet/ssh). This resolves OQ-62: split ALPN lists by endpoint type (Option B), because each endpoint type serves a different client class with different negotiable ALPNs. The assembly-layer wiring pattern is now guessable. - Foundational handlers are two categories, not one: channels data-channel ALPNs (tunnel, socks5, fs, sftp — gated by channels, not in any TLS ALPN list) vs. SSH (an endpoint ALPN that wraps channels inside it, RFC 7250 keys, legacy compat, comes later). Files OQ-65 (WebSocket carrying channels — the browser story update) filed as a one-way-door question, open. ADR-048 is not superseded; OQ-65 may extend it. The web config advertises alknet/channels by default so the hub is ready if OQ-65 resolves to 'WebSocket carries channels.'
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-07-12 |
alknet-core
Core library for ALPN-based protocol dispatch. Every handler crate depends on alknet-core.
Documents
| Document | Status | Description |
|---|---|---|
| core-types.md | draft | ProtocolHandler trait, HandlerError, Connection (Box<dyn BidiStreamSource> — ADR-070), BidiStreamSource trait, BiStream, StreamError |
| endpoint.md | draft | ALPN router, HandlerRegistry, accept loop, graceful shutdown |
| auth.md | draft | AuthContext (incl. anonymous constructor), Identity, IdentityProvider, AuthToken, resolution flow, PeerEntry, CredentialStore |
| config.md | draft | StaticConfig, DynamicConfig, ArcSwap, ConfigReloadHandle, AuthPolicy.peers |
Applicable ADRs
| ADR | Title | Relevance |
|---|---|---|
| 001 | ALPN-Based Protocol Dispatch | Core architectural model |
| 002 | ProtocolHandler Trait | The trait every handler implements |
| 003 | Crate Decomposition | alknet-core's position in the crate graph |
| 004 | Auth as Shared Core | IdentityProvider in core |
| 006 | ALPN String Convention | ALPN format, one-ALPN-per-connection |
| 007 | BiStream Type Definition | Connection, BiStream trait, SendStream, RecvStream |
| 009 | One-Way Door Framework | Decision classification |
| 010 | ALPN Router and Endpoint | Endpoint, HandlerRegistry, accept loop |
| 011 | AuthContext Structure | AuthContext fields and resolution flow |
| 015 | Privilege Model and Authority Context | Per-request identity on OperationContext; admin scope for config reload |
| 030 | PeerEntry and Identity.id Decoupling | authorized_fingerprints → peers: Vec<PeerEntry>; Identity.id = peer_id (stable) |
| 031 | CredentialStore Repo Trait | Second repo trait in core; InMemoryCredentialStore default adapter |
| 033 | Storage Boundary and Repo/Adapter Pattern | Core defines traits + in-memory defaults; persistence adapters are separate crates |
| 065 | Connection::from_stream — Generic Single-Stream Connections |
from_stream/from_bidi accept any AsyncRead + AsyncWrite; yield-once accept_bi contract; unblocks TCP+TLS, SSH channels, WebTransport, wasm |
| 070 | BidiStreamSource Trait — Open Connection for Extension | Connection holds Box<dyn BidiStreamSource>; QUIC/iroh/stream wrap crate-private impls; from_source is the public constructor for downstream crates that implement the trait (channels, future transports) |
Relevant Open Questions
| OQ | Title | Status | Relevance |
|---|---|---|---|
| OQ-04 | Dynamic handler registration | resolved (start static) | HandlerRegistry is immutable at startup |
| OQ-05 | Multi-connectivity endpoint | resolved (quinn + iroh) | AlknetEndpoint supports both, both feature-gated |
| OQ-11 | Handler-level auth resolution observability | resolved | Handlers store resolved identity on Connection; two identity scopes (connection-level for observability, per-request for ACL) |
| OQ-33 | PeerId — logical id vs crypto identity | resolved by ADR-030 | PeerId = Identity.id = PeerEntry.peer_id (stable across key rotation) |
| OQ-34 | Persistent peer registry (storage boundary) | resolved by ADR-030+031+033 | Core defines repo traits + in-memory defaults; persistence adapters are separate crates |
| OQ-35 | dissolved | PeerEntry supports multiple credential paths; ApiKeyEntry is for tokens that ARE the identity |
|
| OQ-36 | Concrete persistence adapter shapes | resolved by ADR-035 | Read-sync / write-async split (IdentityStore); SQLite adapter caches in memory, honker NOTIFY for no-restart cache invalidation; alknet-store-sqlite crate |
| OQ-37 | X.509 outgoing-only case | resolved by ADR-034 | Three remote roles (public X.509 endpoint, transport relay, hub); PeerEntry asymmetry correct; client-side verifier by PeerEntry presence (CA vs fingerprint pin) |
| OQ-55 | AlknetClient / Client Establishment Extraction | deferred(scope) | Blocked on a second transport's real dial (not a second QUIC dial); extracting a QUIC-shaped connector now would bake QUIC in as the establishment shape — the welding ADR-065 unwound on the server side. The client take-over APIs (CallClient::spawn_dispatch, ChannelClient::from_connection — ADR-080) are transport-agnostic and decided; only the shared dial+TLS seam is deferred. |
Key Design Principles
- One trait, one dispatch point:
ProtocolHandleris the only abstraction handlers implement. No StreamInterface/MessageInterface split. - ALPN does the routing: The endpoint dispatches by ALPN string. No byte-peeking, no ListenerConfig enum.
- Handlers own their wire format: Each handler manages its own protocol parsing. alknet-core provides the Connection, not the framing.
- Auth is hybrid: The endpoint provides what it can (TLS-level auth). Handlers complete what they need. AuthContext may be partial.
- WASM door preserved: BiStream is a trait, Connection is an opaque type. Core types don't assume tokio or quinn in public APIs.