Files
alkhttp/docs/architecture/decisions/034-outgoing-only-x509-and-three-peer-roles.md
T
glm-5.3-flash 4a825d33e7 feat(infra): full-surface integration suite + docs sync + publish prep
Full-surface integration suite (tests/full_surface.rs, mcp feature):
- one HttpAdapter over real TCP (ProtocolHandler::handle path) serving
  gateway endpoints, /openapi.json, /mcp, and the WS channels session
- gateway: search/schema/call/subscribe/batch/publish presence,
  envelope shapes, error fidelity end-to-end
- from_openapi import -> Internal-by-default invisible from the wire ->
  External facade composes it via env.invoke -> upstream HTTP API
  called end-to-end (ADR-015 composition model exercised)
- to_openapi 6-path doc validated against openapiv3 over the wire
- to_mcp: MCP client connects to /mcp on the served adapter, lists the
  4 gateway tools, search returns ACL-filtered ops (Sub excluded)

Production fix: the WS upgrade route was reserved but never wired into
HttpAdapter's router (the ws-upgrade-session tests built their own
router). Now wired with ws_bearer_auth (401 without a resolvable
token) around ws_upgrade_handler.

Docs sync: all 28 'Port notes' sections/blockquotes stripped from
ported ADRs/specs; OQ-01/OQ-02 statuses corrected to resolved in
overview.md, websocket.md, and the README table (open-questions.md was
already current).

Publish prep: cargo publish --dry-run --allow-dirty succeeds;
cargo doc --no-deps warning-free (ADR link targets fixed); feature
combinations (default / test-support / mcp / wss / all) compile
warning-free under clippy -D warnings.

Verified: cargo test (182 lib default), --all-features (227 lib + 29
integration), clippy -D warnings x3 feature sets, fmt, doc,
publish --dry-run.
2026-08-28 16:07:56 +00:00

27 KiB
Raw Blame History

ADR-034: Outgoing-Only X.509 and the Three Peer Roles

Ported from alknet ADR-034 (Outgoing-Only X.509 and the Three Peer Roles); re-targeted to alkhttp.

Status

Accepted (resolves OQ-37)

Relevance for this crate: §4 — browsers are not peers — governs alkhttp's browser-facing surface (the WebSocket path, alkhttp ADR-044/ADR-048). The X.509/TLS machinery this ADR discusses (server cert verifiers, TlsIdentity, WebPKI verification, fingerprint pinning) is an alknet concern: alkhttp is transport-coupling-free and owns no TLS configuration or verifier

selection. The X.509-relevant sections (§2, §3, §5) are retained for provenance and marked accordingly; what alkhttp consumes is the peer- model closure of §4 and the three-role vocabulary of §1.

Context

OQ-37 framed the open question as: "the three credential types (Ed25519, X.509, bearer token) and how X.509 server identity fits the peer model." During resolution, it became clear that three distinct remote roles had been conflated under the single label "X.509 endpoint," and that the conflation was the actual source of the confusion — not the TLS mechanics, which alknet ADR-027 and alknet ADR-030 had already settled.

The three roles are real and structurally different:

  1. Public X.509 endpoint — a remote HTTPS or alk/call-over-TLS server reachable by domain name, authenticated by a CA-issued X.509 cert. The local node is a client of it. Examples: a third-party API (vast.ai, api.openai.com), a public hub that the local node dials over the open internet, an alk/call peer that has chosen to expose a domain + X.509 instead of (or in addition to) an Ed25519 raw key. The client authenticates to the server by bearer token (browsers and most HTTP clients cannot do TLS client-auth); the server authenticates to the client by CA verification (WebPKI), not by fingerprint pinning.

  2. Transport relay — iroh's DERP-equivalent (iroh-relay). A connectivity-assistance node that forwards encrypted datagrams between peers who cannot directly connect (NAT traversal). It is infrastructure, not an application peer: it does not register operations, does not participate in the call protocol's peer graph, and has no PeerEntry / PeerId in the auth model. Nodes inherit it for free when the iroh feature is on; the relay's own identity (an Ed25519 NodeId) is iroh's concern, not the protocol stack's. (Transport-relay mechanics are an alknet concern; alkhttp has no iroh dependency and no relay role — the relay is named here only to keep the three-role vocabulary intact.)

  3. Hub / hosting node — an application peer that acts as a hub in a hub-and-spoke (head/worker) topology. It is an ordinary PeerEntry that happens to also expose a public domain + X.509 (so browsers / external HTTPS clients can reach it) and an Ed25519 identity (so other nodes can reach it P2P via iroh or direct quinn). The git-hosting-relay-with-gossip-sync use case is this role: the hub is a full peer that additionally serves browsers. (The hub's browser-facing surface is what alkhttp serves — the gateway endpoints and the WebSocket session.)

The pre-ADR-034 framing asked whether PeerEntry should be made symmetric — i.e., whether the local node should hold a PeerEntry for every remote it might dial, including pure-public-API servers it has no P2P relationship with. This ADR answers no: the asymmetry is correct and reflects a real difference in trust model. PeerEntry (and the PeerId it produces) is the model for peers in the call-protocol peer graph (alkcall ADR-024) — peers that get a stable logical identity, are addressable via PeerRef::Specific, and whose ops land in the peer-keyed overlay. A pure-client connection to a public HTTPS API is not that.

This distinction matters because forcing a stable logical peer_id onto "the operator of api.example.com" is wrong: a public domain's operator can change hands, the cert can be reissued, and the local node has no stable logical identity to attach — only "domain X verified by CA Y today." That is a different trust model from "this Ed25519 key is worker-a, and key rotation updates the fingerprint but not the identity" (alkcall ADR-025).

Decision

1. Name the three roles; stop using "relay" ambiguously

The architecture documents use three distinct terms:

Role Identity Transport Peer? Example
Public X.509 endpoint Domain + CA-issued X.509 HTTPS / alk/call-over-TLS No (client only, unless also role 3) api.alk.dev, vast.ai
Transport relay iroh NodeId (Ed25519) iroh's DERP-like protocol No (infrastructure) relay.iroh.network
Hub / hosting node Ed25519 raw key and/or X.509 iroh / direct quinn / HTTPS Yes (full PeerEntry) git-hosting hub, head node

Existing specs that say "relay" when they mean "domain-hosted service" or "hub" are amended by reference to this table. alknet ADR-027's "domain- hosted services" and alkcall ADR-025's "X.509 cert" credential path refer to the public X.509 endpoint role and the hub role; iroh's transport relay is a separate, inherited component referenced only in the iroh transport path (alknet concern).

2. Outgoing-only X.509 is not a PeerEntry on the client side (TLS mechanics: alknet/alkcall concern; the peer-model rule: alkhttp-relevant)

When a CallClient (or the alkhttp from_openapi / from_mcp adapters) dials a remote that is a public X.509 endpoint and the local node has no P2P relationship with it (no PeerEntry for the remote):

  • The server is authenticated by CA verification (rustls::WebPkiServerVerifier with the platform root store or a configured CA bundle) (the verifier itself is alknet/alkcall dial-layer machinery — alkhttp consumes its outcome, it does not build verifiers). There is no fingerprint to pin — pinning a SHA256:<hex of DER> fingerprint against an external CA-issued cert is brittle (cert renewal changes the fingerprint) and is not the WebPKI trust model. The trigger for CA verification is the absence of a PeerEntry for the remote combined with an X.509 transport; the verifier selection rule is stated in full in §3 below. The ConnectionCredentials.remote_identity: Option<RemoteIdentity> field (alkcall ADR-012, extending alkcall ADR-022 §7) carries an expected fingerprint/cert when the caller has one to pin (Some); for a pure-client X.509 dial with no PeerEntry, remote_identity is None and the CA path applies. The Option is load-bearing — None is the public-X.509-endpoint state, not a missing field: an implementer must not default it to a placeholder, and must not treat None as "skip verification" (None + X.509 = CA verification; None + Ed25519 raw key = fail closed). (alkcall ADR-022 §7 specified remote_identity as "expected fingerprint or cert"; this ADR extends its semantics so that remote_identity: None + no PeerEntry + X.509 transport selects CA verification, and remote_identity: None
    • Ed25519 raw-key transport fails closed.)
  • The client authenticates to the server by bearer token (ConnectionCredentials.auth_token), carried in the call-protocol auth_token payload field (or the HTTP Authorization header for alkhttp's from_openapi / from_mcp). What the server does with that token depends on which kind of public X.509 endpoint it is:
    • Third-party API (api.openai.com, vast.ai — not an alknet node): the server applies its own auth scheme (its own API-key validation, its own ACL). The protocol's PeerEntry / ApiKeyEntry types do not apply on the far side; the client just carries the token in the shape the remote expects (an HTTP header, a call-protocol auth_token payload) and treats the remote's response as authoritative.
    • Hub reached over its public X.509 path (a role-3 hub dialed over the domain instead of P2P): the hub resolves the client's token via its own PeerEntry.auth_token_hash or ApiKeyEntry — the server's bookkeeping, not the client's. The client still holds no PeerEntry for the hub on its own side unless it also has a P2P trust relationship with that hub (in which case the §3 mixed-fingerprint path applies, not this one).
  • The client may still present its TLS client cert (Ed25519 raw public key, per alknet OQ-29) when one is configured; bearer token is the authorization credential, and TLS client-auth (when presented) is additional identity material the server may use. For a third-party API the cert is ignored; for a hub it may be extracted as a fingerprint. Presenting or omitting the client cert is the caller's choice via ConnectionCredentials; this ADR does not require disabling client-auth on this path. (TLS client-cert presentation is dial-layer machinery — an alknet/alkcall concern, not alkhttp's.)
  • The connection does not get a PeerId on the client side. It is not added to PeerCompositeEnv (alkcall ADR-024). There is no PeerRef::Specific routing to it. The connection is a live CallConnection (or, for alkhttp's reqwest-backed client host, an HTTP client session) the caller holds directly; ops discovered via the from_call-pattern import (alkcall ADR-028) or via alkhttp's from_openapi / from_mcp land in that connection's Layer 2 overlay (alkcall ADR-019) and are invoked through the connection handle, not through the peer-keyed routing layer.

This is the asymmetry OQ-37 worried about, stated as a deliberate design property: PeerEntry is for peers in the call-protocol peer graph. Pure-client connections to public X.509 endpoints are not in that graph on the client side. The server may have a PeerEntry for us (resolving our bearer token, in the hub sub-case); we don't need one for it.

3. The hub case is already covered by ADR-030's mixed-fingerprint PeerEntry (alknet/alkcall concern)

A hub / hosting node that is reachable both P2P (Ed25519 raw key via iroh or direct quinn) and via a public domain (X.509 for browsers) is a single PeerEntry with mixed fingerprints:

PeerEntry {
    peer_id: "hub-a".into(),
    fingerprints: vec![
        "ed25519:<hex of hub's Ed25519 pub key>",   // P2P path
        "SHA256:<hex of hub's X.509 cert DER>",      // HTTPS / browser-facing path
    ],
    auth_token_hash: Some("<sha256 of peer's bearer token>"),
    scopes: vec![...],
    resources: {...},
    ...
}

(The PeerEntry struct with mixed fingerprints is an alkcall type — alkcall ADR-025. Fingerprint normalization across quinn/iroh — alkcall ADR-025 §6 — is an alknet/alkcall dial-layer concern.)

When a node dials this hub P2P, the Ed25519 fingerprint matches; when it dials over the public X.509 path (e.g., because P2P connectivity failed), the X.509 fingerprint matches — both resolve to the same peer_id ("hub-a"). The X.509 path here uses fingerprint pinning (the SHA256:<hex> is in PeerEntry), not CA verification, because the local node has a prior P2P trust relationship with this specific hub and has recorded its cert's fingerprint. This is the one case where X.509 fingerprint pinning is correct: the peer is a known peer, not an arbitrary public API.

The choice between CA verification (role 1) and fingerprint pinning (role 3, X.509 path) is driven by whether the local node has a PeerEntry for the remote — this is the authoritative verifier selection rule, referenced from §2:

Local has PeerEntry for remote? Remote cert type Client verifier
No (public X.509 endpoint) X.509 WebPkiServerVerifier (CA verification)
No Ed25519 raw key fails closed (no CA to fall back to — raw-key remotes are always known peers; fingerprint IS identity)
Yes (hub, Ed25519 path) Ed25519 raw key fingerprint match (ed25519:<hex>)
Yes (hub, X.509 path) X.509 fingerprint match (SHA256:<hex>)

This is the key-type-aware verifier from alknet OQ-29, with the peer-model criterion made explicit: the verifier choice is determined by whether the remote is a known peer (PeerEntry present → pin) or an external server (PeerEntry absent → CA, or fail closed for raw keys). (The verifier construction itself is dial-layer machinery — an alknet/alkcall concern; alkhttp's from_openapi/from_mcp/client host consume connections established under this rule but do not select verifiers.)

4. Browsers connecting to a hub are not peers

A browser reaching a hub over WebTransport (or HTTPS — and, per alkhttp ADR-044, over WebSocket) is served by the hub's HTTP handler — in the extracted crate tree, alkhttp. The browser authenticates by bearer token (HTTP Authorization), resolved by the hub's IdentityProvider::resolve_from_token against the hub's PeerEntry.auth_token_hash or ApiKeyEntry. The browser is not a peer on the hub's side either — it does not get a PeerId, does not enter PeerCompositeEnv, and its "ops" are HTTP routes / browser streams served by alkhttp, not entries in the call-protocol peer-keyed overlay. The hub's PeerEntry for the browser (if any) is about authorizing the bearer token, not about peer-graph membership.

This keeps the peer graph populated only by full nodes (role 3 hubs and role-3-style spoke nodes), never by browsers or pure HTTP clients.

Amendment (rationale added by alknet ADR-044 §5, ported to alkhttp ADR-044): The closure above is correct but states the conclusion without the supporting argument. The distinction that makes it correct is: "peer" means an addressable node in the call-protocol peer graph — a stable PeerId, reachable via PeerRef::Specific, whose ops land in PeerCompositeEnv, whose identity is stable across reconnects. It does not mean "any endpoint that exchanges calls during a live session." A browser is the second thing but not the first, on three concrete grounds: (1) no stable cryptographic identity of its own (it presents a bearer token the hub issued; nothing to pin), (2) ephemeral (close the tab → connection dies → the connection-local overlay dies with it; a PeerEntry keyed to a browser would be dead within seconds), (3) not addressable from other nodes (another node has no way to reach "the browser currently connected to hub-A"; the hub holds it as a live CallConnection handle, not a peer-graph entry). The connection-local Layer 2 overlay (alkcall ADR-019; the inbound mirror of §2 above) is what gives the browser bidirectional-call capability without peer-graph membership. This rationale is transport-agnostic — it applies to WebSocket (the browser path in alkhttp, alkhttp ADR-044) and to WebTransport (an alknet transport; not in alkhttp scope — see alkhttp ADR-069) equally. See alkhttp ADR-044 §5 for the full statement.

5. WebTransport relay-as-proxy is a transport-only feature, scoped separately (alknet concern)

WebTransport is not in alkhttp scope at all — per alkhttp ADR-069 it was removed from this crate entirely (it is an alknet concern). This section is retained for the auth-model point (the proxy is transport-only and does not change identity resolution), which remains true regardless of where the proxy lives.

A WebTransport proxy that terminates the browser's WebTransport connection and proxies encrypted traffic to a hub's P2P endpoint (avoiding the need for the hub itself to expose a public X.509 endpoint) is a real feature, especially for the browser-to-P2P-peer case. It is not load-bearing on the auth model resolved here:

  • The proxy does not change how identities resolve. The browser still authenticates by bearer token; the hub still resolves it via PeerEntry.auth_token_hash. The proxy is transport-only.
  • The fingerprint normalization committed in alkcall ADR-025 §6 (ed25519:<hex> for raw keys across quinn and iroh) was already designed to keep the proxied path clean: a proxied connection's Ed25519 identity is the same ed25519:<hex> whether the client connected directly or through the proxy.

Amendment (wording only — the decision stands): An earlier draft of this section framed the relay-as-proxy as belonging to an "h3/WebTransport deferral bucket" and "lands when h3 / WebTransport lands." That framing was a residual of the "two-way door as deferral" anti-pattern (alkcall ADR-032 §"What this framework is NOT") that alknet ADR-038 was later written to reject. alknet ADR-038 has since been superseded by alknet ADR-044, which re-defers h3/WebTransport as a genuine scope decision (the browser bidirectional path uses WebSocket). In the alkhttp crate, the scope question is closed differently: WebTransport is removed from alkhttp entirely (alkhttp ADR-069) — there is no "revives later" posture in this crate. The auth-model decision in this §5 (the proxy is transport-only; it does not change identity resolution) is unchanged by any of these ADRs. The scope question (which crate hosts the proxy, if it is ever built) remains an alknet concern — the alknet OQ-38 tracking is not carried into alkhttp.

6. On-chain / smart-contract peer discovery fits the OQ-36 adapter pattern (alknet/alkcall concern)

The downstream use case — storing relay/repo info and org/user ACL on a smart-contract platform, with relays (hubs) syncing git repos via iroh's gossip protocol — is a discovery and ACL-source concern, not an auth-model concern. It does not change any of decisions 14:

  • The hubs are role-3 PeerEntry peers (mixed fingerprints, full peer- graph membership, gossip-synced).
  • The smart contract is a source of PeerEntry records. It maps cleanly onto the repo/adapter pattern (alknet ADR-033): a future on-chain peer-store adapter implementing IdentityProvider against a smart contract is additive, exactly like a SQLite peer store (alknet ADR-035). The auth model (PeerEntry, PeerId, Identity) is unchanged; only the source of the records changes.
  • The repo/ACL data on-chain is consumed by the hub's authorization layer (AccessControl::check against scopes/resources populated from the on-chain PeerEntry), not by the TLS / fingerprint path.

Designing that adapter now would be premature — it is downstream of both the repo/adapter exploration (alknet OQ-36) and the git crate (alknet OQ-10). It is noted here only to confirm it does not reopen OQ-37. (This is recorded for provenance; the peer-store adapters are alknet/alkcall assembly-layer components, not alkhttp surface.)

What this does NOT change

  • PeerEntry struct shape (alkcall ADR-025) — unchanged. Mixed fingerprints (Ed25519 + X.509) were already supported.
  • Identity / IdentityProvider trait — unchanged (vendored in the alkcall crate). The verifier choice is a CallClient / from_openapi / from_mcp dial-layer concern, not an IdentityProvider concern.
  • ConnectionCredentials struct — unchanged (alkcall ADR-012). remote_identity already carries the expected key type; this ADR specifies how the verifier is chosen from it (CA for unknown X.509 remotes, fingerprint match for known peers).
  • PeerCompositeEnv / PeerRef (alkcall ADR-024) — unchanged. Pure-client X.509 connections simply do not enter the peer-keyed overlay.
  • TlsIdentity (alknet ADR-027) — unchanged. The server-side X.509 / ACME / RawKey modes are unaffected; this ADR is about the client-side verifier choice for outgoing connections. (And TlsIdentity itself is an alknet concern — not present in alkhttp.)
  • The no-env-vars invariant — unaffected. The bearer token for the outgoing X.509 case still comes from Capabilities (alkcall ADR-010 secret-material flow), not env vars.

Consequences

Positive:

  • OQ-37 is resolved. The "make PeerEntry symmetric" instinct is rejected with a clear criterion: PeerEntry is for peers in the call-protocol peer graph; pure-client connections to public X.509 endpoints are not in that graph on the client side.
  • The three remote roles are named, so future specs and conversations can distinguish "public X.509 endpoint," "transport relay," and "hub / hosting node" instead of overloading "relay."
  • The client-side verifier choice has a single rule: known peer (PeerEntry present) → fingerprint pin; unknown X.509 remote (PeerEntry absent) → CA verification. This closes the AcceptAnyServerCertVerifier security hole for X.509 that alknet OQ-29 flagged, with the peer-model criterion made explicit.
  • The hub case (mixed Ed25519 + X.509 fingerprints, browser access via HTTPS and the browser transports) is confirmed to need no new types — alkcall ADR-025's fingerprints: Vec<String> already covers it.
  • The relay-as-proxy and on-chain-discovery use cases are recorded with clear homes (the relay-as-proxy is a transport-only feature whose scope is an alknet concern — not tracked in alkhttp; the on-chain discovery follows the alknet OQ-36 adapter pattern) so they don't get lost and don't reopen the auth model.

Negative:

  • The dial-layer client paths (alkcall's CallClient; alkhttp's from_openapi / from_mcp outbound adapters via the reqwest-backed client host) must respect the "is this remote a known PeerEntry?" distinction when a TLS client config is built for an outgoing connection. In alkhttp the credential injection point is the adapter layer (alkhttp ADR-014 secret-material flow): ConnectionCredentials — including remote_identity — are supplied by the consumer's assembly layer, and the verifier itself is built in the dial layer (alknet/alkcall), not in this crate. This is a small implementation cost and is local to connection establishment; it is not a structural change.
  • Operators must understand the distinction between "I have a PeerEntry for this remote (pin its fingerprint)" and "I'm calling a public API (trust the CA)." In practice this is intuitive (it's the difference between ~/.ssh/known_hosts and a browser's CA trust store), but the docs must state it clearly, which this ADR and the spec amendments do.
  • Pure-client X.509 connections have no PeerId on the client side, so any future feature that wants to route to "the connection I opened to api.alk.dev" must hold the CallConnection handle directly rather than using PeerRef::Specific. This is the correct constraint — PeerRef::Specific is for known peers, not for arbitrary dials — but it is a constraint downstream code must respect.

Assumptions

  1. A remote reachable by Ed25519 raw key is always a known peer. Raw-key remotes have no CA; the fingerprint IS the trust anchor. An unknown Ed25519 remote cannot be verified at all (there is no CA to fall back to), so the connection fails closed. This means the "public X.509 endpoint" role is the only role where the local node dials a remote it has no PeerEntry for. This is correct and intended — it is the same model iroh uses. (The iroh transport is an alknet concern; the fail-closed rule is stated here because it is a property of the credential model, not of any transport.)

  2. Browsers never enter the peer-keyed overlay. A browser is served by alkhttp (gateway routes and, per alkhttp ADR-044/ADR-048, the WebSocket session) and authenticates by bearer token. The hub may have a PeerEntry for the browser's token (to authorize it), but the browser is not a PeerId-bearing peer. This is the explicit closure of the "browser as peer" path — browsers are clients, not peers. The rationale (addressability vs. bidirectionality — a browser has no stable identity of its own, is ephemeral, and is not addressable from other nodes) is stated in alkhttp ADR-044 §5, which amends §4 above by reference. The closure applies transport- agnostically.

  3. X.509 fingerprint pinning is only for known hubs. Pinning an X.509 fingerprint for an arbitrary public API is brittle (cert renewal) and is not done. The PeerEntry.fingerprints X.509 entry is for the hub case where the local node has a P2P trust relationship and wants to also recognize the hub's domain-facing cert.

  4. The on-chain / smart-contract discovery use case does not change the auth model. It is a source of PeerEntry records, implemented as an additive IdentityProvider adapter (alknet ADR-033 / alknet OQ-36). The hub-and-gossip topology it implies is built from role-3 hubs, which this ADR confirms are ordinary PeerEntry peers.

References

  • OQ-37 (resolved by this ADR) — the three auth types and how X.509 server identity fits the peer model
  • alknet ADR-027TlsIdentity (RawKey / X509 / Acme), the browser limitation (no RFC 7250), WebTransport requires X.509 (ported to alkhttp — same file name in this directory; the TLS machinery itself is an alknet concern)
  • alkcall ADR-024 — the peer-keyed overlay model that PeerEntry / PeerId feed into; pure-client connections are not in this graph
  • alkcall ADR-025 — PeerEntry with mixed fingerprints; fingerprint normalization (ed25519: across quinn/iroh); the SHA256:<hex> X.509 fingerprint format
  • alknet ADR-033 / alknet ADR-035 — the repo/adapter pattern that an on-chain IdentityProvider adapter follows and the concrete SQLite adapter shape (the on-chain adapter would follow the same trait + separate-crate pattern) (alknet decisions, not ported to alkhttp)
  • alkcall ADR-022 §7 — CallCredentials (now ConnectionCredentials, alkcall ADR-012) with remote_identity; alkcall ADR-022 specified "expected fingerprint or cert", this ADR §2 extends its semantics so that remote_identity: None + no PeerEntry + X.509 transport selects CA verification
  • alkcall ADR-019 — the Layer 2 per-connection overlay where ops discovered via the from_call pattern (alkcall ADR-028) or via alkhttp's from_openapi / from_mcp on a pure-client X.509 connection land
  • alknet OQ-29 (resolved) — key-type-aware server cert verification; this ADR adds the peer-model criterion (known peer vs. public X.509 endpoint) that selects the verifier
  • alknet OQ-10 (deferred) — git adapter scope; the on-chain / gossip-synced git-hosting hub use case in §6 is downstream of the git crate (alknet OQ)
  • alknet OQ-36 (resolved by alknet ADR-035) — concrete persistence adapter shapes; the on-chain IdentityProvider adapter in §6 follows the same repo/adapter pattern (alknet OQ)
  • alknet ADR-038 — superseded by alknet ADR-044; not ported to alkhttp. See alknet ADR-034 §5's amendment history and alkhttp ADR-069 (WebTransport removed from alkhttp scope).
  • alkhttp ADR-044 — the WebSocket browser path; §5 states the "browser is not a peer" rationale that amends this ADR's §4
  • alkhttp ADR-048 — the WebSocket session shape; the browser bidirectional path this ADR's §4 governs
  • iroh transport relay (iroh-relay) — referenced to distinguish it from the hub role (iroh docs; an alknet transport concern)
  • alkcall crate docs — CallClient/ConnectionCredentials dial path, verifier selection by PeerEntry presence; see the alkcall crate's own documentation for the client-and-adapters spec