docs(arch): endpoint as pure accept-loop runner + acme-tls/1 guard relocation (ADR-083, OQ-60/61)

ADR-083: AlknetEndpoint becomes a pure accept-loop runner with a public
dispatch method. Transport construction moves out of the endpoint — the
assembly layer reads StaticConfig, builds transports from
TlsServerConfig(s), and hands pre-built quinn/iroh endpoints to the
endpoint via with_quinn/with_iroh. TCP+TLS dispatch is first-class (same
dispatch path as quinn/iroh); ADR-010 Amendment 1's duplicated-dispatch
workaround is retired. StaticConfig stays in core as the assembly-layer
config; the endpoint takes only drain_timeout.

The acme-tls/1 guard moves from dispatch_quinn to the shared dispatch
method — ACME TLS-ALPN-01 challenges arrive over TCP (CAs validate via
TCP to port 443, not QUIC), so the guard's quinn-specific location was a
latent bug once TCP+TLS exists. The guard is transport-agnostic; the
rationale (no handler, silent close) is unchanged. ADR-027 §5 amended.

OQ-60: where build_iroh_endpoint lives (assembly layer / alknet-tls
helper / transport module). build_quinn_server_config_from_rustls is
decided — it moves to alknet-tls as for_quinn() per ADR-082; only
build_iroh_endpoint is genuinely undecided.

OQ-61: multi-owner shutdown coordination. Boundary committed (endpoint
owns dispatched handlers; assembly layer owns spawned accept loops);
mechanism open.

ADR-082 amended: drops the Arc<TlsServerConfig> endpoint signature
(superseded by ADR-083); keeps its scope as the alknet-tls crate's
TlsServerConfig + accessors.

Review: zero critical issues, five warnings fixed (build_iroh_endpoint
destination contradiction, undeclared shutdown_sender, missing ADR-027
amendment marker, underspecified dispatch no-match behavior, stale
door-type timing clause).
This commit is contained in:
glm-5.2 committed 2026-07-14 07:59:04 +00:00
1 parent 9823c6e4ab
commit 81bde6f28f
8 files changed
+564 -50

No files matched your search

@@ -0,0 +1,50 @@
# OQ-60: Where Does Transport Construction Live?
- **Origin**: `docs/architecture/decisions/083-endpoint-as-accept-loop-runner.md`
(the endpoint refactor commits to the boundary — construction is not
in the endpoint — but not the location of `build_iroh_endpoint`).
- **Status**: open
- **Door type**: one-way (where `build_iroh_endpoint` lives determines
who depends on `iroh` for transport construction; moving it later
churns the dep graph and every binary's assembly code)
- **Priority**: high (the hub is the first multi-transport consumer;
its assembly code sets the pattern)
- **Blocked on**: nothing structural. The three options are clear; the
decision is a trade-off, not a missing capability.
- **Scope**: This OQ covers **`build_iroh_endpoint`** — the function
that reads `StaticConfig` and builds an `iroh::Endpoint`.
`build_quinn_server_config_from_rustls` is **decided** (it moves to
`alknet-tls` as `TlsServerConfig::for_quinn()` per ADR-082 — it's a
thin wrapper over a `rustls::ServerConfig`, which `alknet-tls` owns).
Only `build_iroh_endpoint` is genuinely undecided: it reads
`StaticConfig` (not a rustls config), builds an `iroh::Endpoint` from
an `Ed25519SecretKey` + relay URL, and doesn't fit the `alknet-tls`
cert-provider boundary.
- **Resolution**: Not yet decided. The options:
**Option A: In the assembly layer (the binary).** Each binary reads
`StaticConfig` and hand-assembles quinn/iroh/TCP+TLS. Pro: maximal
flexibility, core stays lean. Con: every binary duplicates the
"build an iroh endpoint from an `Ed25519SecretKey` + relay URL"
boilerplate; a 10-step procedure is copy-pasted per binary.
**Option B: In `alknet-tls` as convenience helpers.** `alknet-tls`
gains a `build_iroh_endpoint` helper. Pro: one place. Con: `alknet-tls`
then depends on `iroh`, bloats a crate whose stated job is "TLS setup,
not transport endpoint construction" (ADR-082 scopes `alknet-tls` to
cert config and its accessors — `build_iroh_endpoint` doesn't touch
certs, it touches iroh's relay/secret-key APIs).
**Option C: A new crate or module — `alknet-transport` / an
`alknet-core::transport` module — that owns transport construction.**
Pro: clean separation (TLS = certs, transport = endpoints, endpoint =
dispatch). Con: another layer in the dep graph.
The ADR-082 boundary ("`alknet-tls` is the cert provider, not the
transport constructor") is a strong argument against Option B. Option C
is the cleanest separation but adds a layer. Option A is simplest but
risks per-binary duplication that drifts.
- **Cross-references**: ADR-083 (endpoint refactor), ADR-082
(`alknet-tls` — the cert provider boundary; `for_quinn()` is in scope,
`build_iroh_endpoint` is not), ADR-010 (original endpoint design,
where construction was welded to the endpoint)
@@ -0,0 +1,42 @@
# OQ-61: Multi-Owner Shutdown Coordination
- **Origin**: `docs/architecture/decisions/083-endpoint-as-accept-loop-runner.md`
(the endpoint owns dispatched handlers; the assembly layer owns
spawned accept loops — coordination between them on shutdown is
unspecified).
- **Status**: open
- **Door type**: two-way (the coordination mechanism is an
implementation detail; the ownership boundary — endpoint owns
dispatched handlers, assembly layer owns spawned accept loops — is
the one-way part, committed in ADR-083)
- **Priority**: medium (matters for clean hub shutdown; doesn't block
the endpoint-shape decision or the TLS extraction)
- **Blocked on**: nothing structural. The question is which
coordination primitive (shared `shutdown_sender`, separate channel,
drain semantics) — a design choice, not a missing capability.
- **Resolution**: Not yet decided. The boundary is clear:
- The endpoint owns shutdown of **dispatched handlers** (it spawned
them via `tokio::spawn` in `dispatch`).
- The assembly layer owns shutdown of the **accept loops it spawned**
(the TCP+TLS listeners).
The open question is the **coordination mechanism**:
- Does the assembly layer use the endpoint's `shutdown_sender()` to
signal the TCP+TLS loops, or a separate channel?
- Does `endpoint.shutdown()` drain in-flight dispatched handlers
(including those from TCP+TLS loops), or only the quinn/iroh ones
it spawned via `run()`?
- What happens to in-flight `dispatch` calls after the endpoint is
shut down — are they rejected, or do they complete?
A likely shape: the assembly layer uses the endpoint's
`shutdown_sender()` for all its accept loops (one signal, all loops
stop accepting); the endpoint's `shutdown()` drains all dispatched
handlers regardless of which transport spawned them (the endpoint
owns them all once `dispatch` is called). But this needs to be
written down and verified against the drain semantics.
- **Cross-references**: ADR-083 (endpoint refactor — ownership
boundary), ADR-010 (original shutdown design — single-owner, the
simpler case being generalized)