- WS-31: websocket.md §"Data channels for browsers" + ADR-067's landed note record that WS-session discovery is the bootstrap set — the hook's bootstrap `services/*` registrations overwrite a base-registry `services/*` registration on the WS path by design (a deployment's custom `services/list` is shadowed on WS sessions only). - ADR-048's landed note: correction + completion — the WS-26 retention sentence was aspirational at the landed commit (WS-28) and is now real; the UP-02 posture's override half is now an explicit surface (`with_ws_op_register_acl` / `OpRegisterAcl`), with the note that `ChannelsPolicy` could not carry an op ACL. - ADR-067's landed note: review-007 notes (WS-28 fix + gate, WS-29 surface, WS-31 record). - OQ-05 resolution: the retention claim carries the WS-28 correction. - review-006 UP-02 log + WS-26 paragraph: corrections marking what the pre-fix tree did not have, with the landed remediation named. - review-002 WS-17: the "bounded at 64 sessions" claim corrected — the bare-registry semaphore was per-request and bounded nothing; `SessionSlots` is the shared-cap surface. - review-007 status: open for remediation → remediated, with the decisions taken (both "implement" options) and the gate names. Verification: cargo test 454 passed / 0 failed; cargo doc --no-deps clean (6 pre-existing warnings, identical at baseline). Review: docs/reviews/007-ws-data-channel-surface-review.md
426 lines
24 KiB
Markdown
426 lines
24 KiB
Markdown
# ADR-048: WebSocket Carries the Native Call-Protocol Session, Not the Gateway Shape
|
||
|
||
*Ported from alknet ADR-048 (WebSocket Carries the Native Call-Protocol Session, Not the Gateway Shape); re-targeted to alkhttp.*
|
||
|
||
## Status
|
||
|
||
Accepted
|
||
|
||
> **Reconciliation note (2026-08-29):** the bidirectionality this ADR
|
||
> promises for the WS path (§4 — the hub calling browser-registered ops
|
||
> through the connection-local Layer 2 overlay) is **decided design,
|
||
> not a v1 implementation commitment**. Today the WS path installs
|
||
> channel 0 only; no data-channel open path and no browser-side
|
||
> registration wiring exists (review-001 finding WS-03). The cut and
|
||
> its rationale are recorded in
|
||
> [OQ-05](../open-questions.md); the native-session contract below
|
||
> stands as what the deferred wiring must satisfy.
|
||
>
|
||
> **Re-point (2026-09-03):** the two alkcall mechanisms this wiring
|
||
> composes on now exist. alkcall review 004 Units 1–3 landed the
|
||
> per-session fork as the dispatch registry (alkcall ADR-047 §4
|
||
> amendment #2), the full-duplex serving loop
|
||
> (`Dispatcher::serve_single_stream`, opt-in via
|
||
> `ChannelClient::from_connection_with_serving`), and the
|
||
> `op/register` bootstrap op (alkcall ADR-022 amendment 2026-09-03 —
|
||
> the bootstrap-op set each side may serve). The browser
|
||
> bidirectionality promise is no longer standing on unmechanized
|
||
> commitments: the deferred WS wiring (OQ-05, alkhttp review 003
|
||
> Unit 2) composes on those alkcall ADRs.
|
||
>
|
||
> **Updated (2026-09-04):** alkcall review 005 remediated the landed
|
||
> mechanisms (serving-loop concurrency, `op/register` collision
|
||
> policy, spec round-trip completeness) and alkcall 0.3.0 shipped
|
||
> them; alkhttp now consumes 0.3. The ADR-022 collision policy
|
||
> (2026-09-04 sub-amendment) binds here too: a peer-announced op may
|
||
> replace other peer-announced ops but never the serving side's own
|
||
> registrations — the WS session's `op/register` handler gates on the
|
||
> session fork.
|
||
>
|
||
> **Landed (2026-09-04, review 006 Unit 2+3):** the WS wiring this
|
||
> note tracks is in — the hook forks the base registry per session,
|
||
> registers the channel ops + openables + bootstrap discovery +
|
||
> `op/register` on the fork, and dispatches over it; the session
|
||
> retains its live `Arc<CallConnection>` in `WsSessions` (the hub's
|
||
> reach-the-browser handle). §4's hub→browser call direction now has
|
||
> its object: announced ops land in the connection overlay via
|
||
> `op/register`, and the hub composes them through the connection
|
||
> handle (gate:
|
||
> `op_register_served_per_session_and_collision_is_already_exists`).
|
||
> The `op/register` ACL posture on this surface: registered with
|
||
> `AccessControl::default()` (the SRV-10 permissive-crate-default
|
||
> precedent); deployments gate via a stricter `ChannelsPolicy`
|
||
> passed through the hook (review 006 UP-02 decision).
|
||
>
|
||
> **Review-007 correction + completion (2026-09-05):** the retention
|
||
> sentence above was, at the time of the landed note, aspirational —
|
||
> review 007 WS-28 found the `ConnectionGuard` dropping at the end of
|
||
> its `if let` block (the handle visible for microseconds) and
|
||
> remediation bound it in the channel-0 task's frame; the retention is
|
||
> now real, gated by
|
||
> `live_connections_visible_mid_session_and_drain_after_teardown`.
|
||
> The ACL posture's second half is likewise now real as an explicit
|
||
> surface (the `ChannelsPolicy` object could not carry an op ACL):
|
||
> `HttpAdapter::with_ws_op_register_acl` / the `OpRegisterAcl`
|
||
> request extension set the `op/register` op's `AccessControl` —
|
||
> default unchanged (`AccessControl::default()`, any authenticated
|
||
> peer may announce); a deployment restricting which authenticated
|
||
> peers may announce threads a stricter value. The UP-02 posture
|
||
> stands; only the override surface it named now exists.
|
||
|
||
## Status amendment (alkhttp port)
|
||
|
||
- **The WS path carries the channels session** (alkhttp ADR-067): a
|
||
browser WS connection is demultiplexed by the channels protocol —
|
||
8-byte chunk headers per alkcall ADR-034/ADR-035 — rather than being a
|
||
bare `EventEnvelope` stream. **Channel 0 is pre-negotiated as
|
||
`alk/call`** (alkcall ADR-036) and carries the native call-protocol
|
||
session **exactly as this ADR describes** — the dispatch loop, the
|
||
overlay rules, and the browsers-not-peers property all stand
|
||
unchanged; they apply to channel 0.
|
||
- **Framing on channel 0 is length-prefixed JSON** (alkcall's frame
|
||
format, alkcall ADR-014) **inside the 8-byte chunk header** — **not**
|
||
one-envelope-per-WS-message. The WS message boundary carries chunks,
|
||
not envelopes; see alkhttp ADR-067 §framing. Sentences below that
|
||
speak of "one `EventEnvelope` = one binary WS message" are the
|
||
original 2026 framing, retained as decision history; the operative
|
||
framing is the channels-chunk model just described.
|
||
- **The default WS upgrade path is `/alk/channels`** (was
|
||
`/alknet/call` in the alknet original — renamed per the alkcall ADR-004
|
||
`alk/` ALPN convention and per alkhttp ADR-067's channels session).
|
||
|
||
## Context
|
||
|
||
alkhttp ADR-044 (Accepted) removed h3/WebTransport from this crate's
|
||
scope (see alkhttp ADR-069; the alknet record deferred it) and committed
|
||
WebSocket as the browser bidirectional path: a browser upgrades an
|
||
HTTP/1.1 or HTTP/2 request to WebSocket and the resulting full-duplex WS
|
||
connection carries the call protocol's `EventEnvelope` frames (in the
|
||
current wire model: on channel 0, as length-prefixed JSON inside
|
||
channels chunks). alkhttp ADR-044 §1 established that the call
|
||
protocol's `call.requested`/`call.responded`/`call.completed`/`call.aborted`
|
||
exchange "works over WebSocket with no protocol change — the same `Dispatcher`,
|
||
the same `PendingRequestMap`, the same correlation by request ID."
|
||
|
||
alkhttp ADR-044 §1 also established *what shape the WS session carries* — the native
|
||
`EventEnvelope` call-protocol session — but it does so as part of a larger
|
||
argument about why WebTransport isn't required, not as a crisp rule an
|
||
implementer is told not to violate. Two facts make the distinction worth its
|
||
own explicit decision record:
|
||
|
||
1. **The HTTP surface has a deliberate, well-documented invoke contract: the
|
||
`to_openapi` gateway pattern** (alkhttp ADR-042, alkhttp ADR-047). The gateway is 5 fixed
|
||
endpoints (`/search`, `/schema`, `/call`, `/batch`, `/subscribe`), where
|
||
`/call` takes `{ "operation": "/fs/readFile", "input": {...} }` and invokes
|
||
through `OperationRegistry::invoke()`. It is a well-shaped, simple contract.
|
||
An implementer writing the WS handler could plausibly ask: "should the WS
|
||
path expose the same 5-endpoint gateway shape, so a WS client looks like an
|
||
HTTP client?" That is a reasonable question, and the answer is no — but the
|
||
answer is currently implicit in alkhttp ADR-044's framing, not stated as a rule.
|
||
|
||
2. **The two surfaces serve different architectural roles, and that difference
|
||
is load-bearing.** The HTTP gateway is, by HTTP's nature, a *one-directional
|
||
projection* — client initiates, server responds (see the alkhttp server
|
||
spec, §"One-directional projection"). The whole reason WebSocket exists in
|
||
this architecture is to *restore the call protocol's native bidirectionality
|
||
for browsers* (alkhttp ADR-044 §4): a WS connection is full-duplex, so both
|
||
sides can initiate `call.requested` frames. Putting the gateway's
|
||
one-directional shape on WS re-introduces the one-directional limitation
|
||
that WS exists to fix. The two surfaces are not interchangeable; they are
|
||
deliberately different tools for deliberately different jobs.
|
||
|
||
### The two invoke contracts, contrasted
|
||
|
||
| Aspect | HTTP gateway (`/call`, alkhttp ADR-042/047) | WS native session (this ADR) |
|
||
|---------|--------------------------------------|------------------------------|
|
||
| Direction | One-directional (client→server calls only) | Bidirectional (either side can `call.requested`) |
|
||
| Wire unit | HTTP request/response | Channels chunk (8-byte header, alkcall ADR-034/035); channel 0 payload = length-prefixed-JSON `EventEnvelope` (alkcall ADR-014) |
|
||
| Invoke shape | `POST /call` with `{ "operation": "/fs/readFile", "input": {...} }` | `call.requested` event with `{ operation, input }` payload (the call protocol's native shape) |
|
||
| Discovery | `GET /search` (gateway endpoint) | `services/list` as an ordinary call-protocol op |
|
||
| Schema | `GET /schema` (gateway endpoint) | `services/schema` as an ordinary call-protocol op |
|
||
| Streaming | `POST /subscribe` (SSE frames) | `call.responded` events as channel-0 frames (no SSE) |
|
||
| Dispatcher | axum route handler → `OperationRegistry::invoke()` | shared `Dispatcher` (alkcall ADR-015, stream-agnostic) |
|
||
| Multiplexing | HTTP/2 native; HTTP/1.1 sequential | Channels channel IDs (alkcall ADR-034/035) + request ID (alkcall ADR-015) |
|
||
|
||
The WS row is the call protocol's own native session, with WebSocket as the
|
||
transport instead of QUIC (carried on channel 0 of the channels session per
|
||
alkcall ADR-036). The HTTP row is a projection of that session into
|
||
HTTP's one-directional shape, with the gateway as the deliberate interface for
|
||
clients that only speak HTTP.
|
||
|
||
### Why the gateway shape is wrong on WS
|
||
|
||
Three concrete reasons:
|
||
|
||
1. **It duplicates the native invoke path with a lossier one.** The call
|
||
protocol's `call.requested` event *is* the invoke primitive; the gateway's
|
||
`/call` is that primitive wrapped in an HTTP envelope. On WS, the envelope
|
||
is unnecessary — there is no HTTP request/response cycle to fit into. A
|
||
gateway-on-WS would be a translation layer translating the call protocol to
|
||
itself, losing bidirectionality in the process.
|
||
|
||
2. **It loses the per-caller filtering property the native session already
|
||
has.** The gateway's `/search` exists to give HTTP clients the
|
||
`AccessControl::check(identity)`-filtered discovery that the call protocol
|
||
provides natively via `services/list`. On WS, `services/list` is already a
|
||
call-protocol op the browser can call directly — the filtering is already
|
||
there. A gateway-on-WS re-implements a filtering property the native session
|
||
already provides.
|
||
|
||
3. **It breaks the symmetry with the QUIC path.** The `alk/call` QUIC path
|
||
(alkcall ADR-015, alkcall ADR-022) is the native `EventEnvelope` session over
|
||
QUIC bidirectional streams. WS is the same session over WS messages (via
|
||
the channels session's channel 0 — alkcall ADR-036). Making the
|
||
WS path different from the QUIC path (by putting the gateway on WS) creates
|
||
two browser-reachable invoke contracts for no architectural reason — the
|
||
QUIC path is the reference, and WS should mirror it, not diverge from it.
|
||
|
||
### The prior art is already native-session-shaped
|
||
|
||
The `@alkdev/pubsub` WebSocket client/server (`event-target-websocket-client.ts`,
|
||
`event-target-websocket-server.ts`) — the working prior art alkhttp ADR-044 cites as
|
||
the reason the WS path is cheap — already carries the `EventEnvelope { type, id,
|
||
payload }` shape over WS binary messages, with no gateway-style wrapping. The
|
||
call protocol's `EventEnvelope` was derived from the pubsub envelope
|
||
(refined with typed event names and structured payloads); the delta is small
|
||
and well-defined (see the alkcall crate docs, §"Transport agnosticism").
|
||
A browser/Node WS client derived from the pubsub
|
||
prior art speaks the native session shape, not a gateway shape. The
|
||
gateway-on-WS variant would require *un-translating* the pubsub client's
|
||
native-session shape into the gateway's `{ operation, input }` shape — work
|
||
that has no payoff because the native shape is what both the QUIC path and the
|
||
pubsub prior art use. *(In the current wire model, the client additionally
|
||
speaks the channels chunk framing around those envelopes — alkcall ADR-034/035
|
||
via alkhttp ADR-067.)*
|
||
|
||
## Decision
|
||
|
||
### 1. A WebSocket connection is a native `EventEnvelope` call-protocol session, not the HTTP gateway shape
|
||
|
||
The WS handler on `HttpAdapter` hands the WS message stream to the call
|
||
protocol's shared `Dispatcher` — the same dispatch loop the call adapter
|
||
uses for `alk/call` QUIC connections (alkcall ADR-015, stream-agnostic
|
||
correlation; a WS message stream is another `BiStream`-satisfying
|
||
transport). Concretely in alkhttp (per alkhttp ADR-067): the WS handler
|
||
demultiplexes the 8-byte chunk framing of the channels protocol (alkcall
|
||
ADR-034/ADR-035); **channel 0 is pre-negotiated as `alk/call`**
|
||
(alkcall ADR-036) and its chunk payloads are `EventEnvelope` frames in
|
||
alkcall's length-prefixed JSON frame format (alkcall ADR-014). The
|
||
browser writes `EventEnvelope` frames as channel-0 chunks; the handler
|
||
reads them and dispatches via `OperationRegistry::invoke()`. Responses
|
||
(`call.responded`, `call.error`, `call.completed`, `call.aborted`) are
|
||
written back as channel-0 chunks.
|
||
|
||
The `to_openapi` gateway endpoints (`/search`, `/schema`, `/call`, `/batch`,
|
||
`/subscribe` — alkhttp ADR-042, alkhttp ADR-047) **do not appear on the
|
||
WebSocket path**. They are the HTTP one-directional projection's invoke
|
||
contract; WS carries the call protocol's own native session, which is a
|
||
different (and richer) thing.
|
||
|
||
### 2. Discovery and schema are call-protocol ops, not WS-specific endpoints
|
||
|
||
The browser calls `services/list` and `services/schema` as ordinary
|
||
`call.requested` events over the WS connection (channel 0). They are
|
||
call-protocol operations, not WS endpoints. There is no `/search` or
|
||
`/schema` on WS — those are the HTTP gateway's names for the same
|
||
discovery primitives. The filtering the gateway provides via
|
||
`AccessControl::check(identity)`-filtered `/search` (alkhttp ADR-042 §3)
|
||
is provided on the WS path by the same mechanism the call protocol uses
|
||
everywhere: `services/list` is `AccessControl`-filtered natively (see
|
||
the alkcall crate docs, client-and-adapters, §"services/list"). No
|
||
WS-specific discovery surface exists or is needed.
|
||
|
||
### 3. Subscriptions project as native `call.responded` events, not SSE
|
||
|
||
A `Subscription` operation invoked over WS streams `call.responded`
|
||
events as channel-0 frames directly — no SSE `data:` framing (that is
|
||
the `h2`/`http/1.1` projection for `/subscribe`, per the streaming
|
||
handler decision, alkhttp ADR-049; on WS it is unnecessary because WS is
|
||
already a framed full-duplex channel). `call.completed` closes the
|
||
stream; `call.aborted` closes it with an error frame. This is the
|
||
native streaming projection for the WS path, mirroring how subscriptions
|
||
work on the QUIC path.
|
||
|
||
### 4. Bidirectionality is native and unchanged from the QUIC path
|
||
|
||
The WS call-protocol session inherits the call protocol's native
|
||
bidirectionality (alknet ADR-043 §2, transferred to WebSocket per
|
||
alkhttp ADR-044 §3): both sides can send `call.requested` frames. The
|
||
browser calls operations on the hub; the hub can call operations
|
||
registered on the browser's side, over the same session, using the same
|
||
`PendingRequestMap` and `EventEnvelope` framing as `alk/call`. The
|
||
browser case where the client registers no operations of its own is the
|
||
common case — the server→client call direction is unused because the
|
||
browser has nothing to call. That is a use-case scoping, not an
|
||
architectural limitation.
|
||
|
||
### 5. This is a clarifying decision, not a new one
|
||
|
||
alkhttp ADR-044 §1 commits the native-session shape ("the call
|
||
protocol's framing fits the WebSocket path cleanly ... the same
|
||
`Dispatcher`, the same `PendingRequestMap`, the same correlation by
|
||
request ID"). This ADR does not change that decision; it makes the
|
||
implication an explicit, implementer-visible rule: **the WS path is the
|
||
native session, and the gateway shape is deliberately not applied to
|
||
it.** An implementer reading alkhttp ADR-044 alone could plausibly ask
|
||
"should the WS path expose the gateway endpoints too?" — this ADR's job
|
||
is to make the answer discoverable as a decision record, not implicit in
|
||
framing.
|
||
|
||
## Consequences
|
||
|
||
**Positive:**
|
||
- One invoke model for the call protocol, regardless of transport. The
|
||
QUIC path and the WS path run the same `EventEnvelope` session through
|
||
the same `Dispatcher` (on the WS path, via channel 0 of the channels
|
||
session — alkhttp ADR-067); the HTTP gateway is the one-directional
|
||
projection for clients that only speak HTTP. An implementer building
|
||
the WS handler reuses the `Dispatcher` and `OperationRegistry::invoke()`
|
||
dispatch path verbatim — no WS-specific routing, no WS-specific
|
||
discovery surface, no second invoke contract to design or maintain.
|
||
- The `@alkdev/pubsub`/`@alkdev/operations` TypeScript clients sync to
|
||
the call protocol with no translation layer: their `EventEnvelope`
|
||
shape is already the native session shape, and the call protocol's
|
||
envelope is a refined superset of the pubsub envelope (alkhttp ADR-044
|
||
§"Concrete prior art"). The gateway-on-WS variant would have required
|
||
un-translating the pubsub client's native-session shape; this decision
|
||
avoids that un-translation entirely. *(The clients also speak the
|
||
channels chunk layer around those envelopes — alkcall ADR-034/035.)*
|
||
- Per-caller `AccessControl`-filtered discovery is already a property of
|
||
the native session (`services/list`). No WS-specific filtering surface
|
||
to build or document; the call protocol's authorization model applies
|
||
unchanged.
|
||
- The browser is a bidirectional call target during a live session, not
|
||
a peer-graph member (alkhttp ADR-044 §5, alkhttp ADR-034 §4). The
|
||
native session shape is what makes this clean: the browser gets
|
||
bidirectional call capability through the connection-local Layer 2
|
||
overlay (alkcall ADR-019) without peer-graph membership, and the
|
||
gateway shape would not have changed this — but it would have made the
|
||
WS path diverge from the QUIC path for no benefit.
|
||
|
||
**Negative:**
|
||
- A WS client cannot use the gateway's `{ "operation": ..., "input": ... }`
|
||
body shape — it must speak the call protocol's native `call.requested`
|
||
event (inside the channels chunk framing). This is honest (the WS path
|
||
*is* the call protocol), but a developer who learned the gateway shape
|
||
from the HTTP surface must learn the `EventEnvelope` shape (plus the
|
||
chunk framing) for WS. The pubsub prior art and the
|
||
`@alkdev/operations` TypeScript client already speak the envelope
|
||
shape, so the delta is small for the primary consumer — but it is a
|
||
real difference from the HTTP gateway's simpler `{ operation, input }`
|
||
invoke body.
|
||
- The 5 gateway endpoint names (`/search`, `/schema`, `/call`, `/batch`,
|
||
`/subscribe`) are HTTP-specific and do not carry over to WS. A deployment
|
||
documenting its surface for both HTTP and WS clients documents two invoke
|
||
shapes (the gateway for HTTP; the native session for WS). This is the cost
|
||
of using the right tool for each transport instead of forcing one shape onto
|
||
both.
|
||
|
||
## Reversal
|
||
|
||
This ADR clarifies a decision alkhttp ADR-044 already committed (§1
|
||
describes the native session; this ADR makes that the explicit,
|
||
implementer-visible rule). The reversal posture is therefore alkhttp
|
||
ADR-044's, not a separate one: the WS path itself is not deferred (it is
|
||
the browser path), and the native-session-not-gateway choice is a
|
||
clarification of what that path carries — reversing it would mean
|
||
adopting the gateway shape on WS, which would re-introduce the
|
||
one-directional limitation WS exists to fix (§Context reason 1). The
|
||
original text's realistic reversal path — "WebTransport revives and adds
|
||
a second browser bidirectional path" — is an alknet posture and does not
|
||
apply to this crate: WebTransport is removed from alkhttp scope entirely
|
||
(alkhttp ADR-069), and the ALPN-stream-proxy (alknet ADR-040) is an
|
||
alknet record, not ported here. If a second browser bidirectional
|
||
transport ever exists, it is an alknet transport concern fronting the
|
||
stable HTTP surface this crate publishes; this ADR's rule (WS = native
|
||
session on the channels path, not gateway) is unaffected — the gateway
|
||
shape stays HTTP-only regardless of how many browser bidirectional
|
||
transports exist.
|
||
|
||
## Assumptions
|
||
|
||
1. **The call protocol's `EventEnvelope` framing fits the WebSocket path
|
||
cleanly.** In the original 2026 framing: an `EventEnvelope` is a
|
||
self-delimited JSON object; one envelope per WS binary message. In
|
||
the current wire model (alkhttp ADR-067): the WS message boundary
|
||
carries channels chunks (8-byte header, alkcall ADR-034/ADR-035), and
|
||
channel 0 — pre-negotiated as `alk/call` (alkcall ADR-036) — carries
|
||
`EventEnvelope` frames as length-prefixed JSON (alkcall ADR-014's
|
||
frame format) inside the chunk payload. The load-bearing property —
|
||
self-delimited frames, no streaming deserializer across frame
|
||
boundaries — is unchanged. This is verified by prior art: the
|
||
`@alkdev/pubsub` WebSocket client/server carries the same
|
||
`{ type, id, payload }` envelope over WS binary messages.
|
||
|
||
2. **The shared `Dispatcher` runs over the WS path unchanged.** alkcall
|
||
ADR-015 commits stream-agnostic correlation; a WS message stream is
|
||
another `BiStream`-satisfying transport (per alkcall ADR-038, the
|
||
channels `ChannelConnection` is itself a `BiStreamSource`). The
|
||
`Dispatcher` and `PendingRequestMap` are transport-agnostic; only the
|
||
connection-establishment half differs (WS upgrade handler vs QUIC
|
||
accept/dial).
|
||
|
||
3. **The primary WS consumer is a browser or Node client derived from
|
||
the `@alkdev/pubsub`/`@alkdev/operations` prior art.** That client
|
||
already speaks the native `EventEnvelope` shape (now wrapped in the
|
||
channels chunk framing). The gateway's simpler `{ operation, input }`
|
||
body shape is the HTTP path's affordance for clients that only speak
|
||
HTTP; a client that has chosen WS has already opted into the call
|
||
protocol's native framing.
|
||
|
||
4. **`services/list` and `services/schema` are sufficient discovery for
|
||
the WS path.** They are `AccessControl`-filtered (per-caller) and
|
||
return the full `OperationSpec` respectively. The gateway's `/search`
|
||
and `/schema` are HTTP-shaped names for these same primitives; on WS
|
||
the primitives apply directly. No WS-specific discovery surface is
|
||
needed.
|
||
|
||
## References
|
||
|
||
- alkhttp ADR-067 — **the amendment that defines the current WS wire
|
||
model**: the WS session carries the channels protocol (8-byte chunk
|
||
multiplexing); channel 0 is pre-negotiated `alk/call` and carries the
|
||
native call-protocol session described here.
|
||
- alkcall ADR-034 / alkcall ADR-035 — the channels wire format (8-byte
|
||
chunk header; pure channel multiplexing).
|
||
- alkcall ADR-036 — channel 0 is pre-negotiated `alk/call`.
|
||
- alkcall ADR-014 — the call protocol's hand-rolled `EventEnvelope`
|
||
framing (length-prefixed JSON); the frame format carried in channel-0
|
||
chunk payloads.
|
||
- alkcall ADR-015 — call-protocol stream model; stream-agnostic
|
||
correlation (`Dispatcher`/`PendingRequestMap`); a WS message stream is
|
||
another `BiStream`-satisfying transport.
|
||
- alkcall ADR-038 — `ChannelConnection` as a `BiStreamSource`; the
|
||
channels-session side of the stream-agnostic claim.
|
||
- alkcall ADR-022 §5 — `to_*` adapters are projections that consume the
|
||
registry; WS is not a `to_*` adapter (it carries the native session,
|
||
it doesn't project it).
|
||
- alkcall ADR-019 — Layer 2 per-connection overlay where
|
||
browser-registered ops (if any) land.
|
||
- alkhttp [ADR-034](034-outgoing-only-x509-and-three-peer-roles.md) §4
|
||
(amended by alkhttp ADR-044 §5) — browsers are not peers; the
|
||
connection-local overlay gives the browser bidirectional-call
|
||
capability without peer-graph membership.
|
||
- alkhttp ADR-049 — the SSE projection for `/subscribe` (the HTTP
|
||
one-directional streaming path; on WS, subscriptions project as native
|
||
`call.responded` events, no SSE).
|
||
- alkhttp ADR-042 — the gateway pattern this ADR clarifies is HTTP-only.
|
||
- alknet ADR-043 §2/§3 — bidirectionality and the no-`PeerId`
|
||
connection-local overlay, transferred to WebSocket per alkhttp ADR-044
|
||
§3 *(alknet record; not ported to alkhttp)*.
|
||
- alkhttp [ADR-044](044-defer-webtransport-browsers-use-websocket.md) —
|
||
the ADR that committed WS as the browser path; this ADR clarifies the
|
||
shape of what it committed (§1 implies the native session; this ADR
|
||
makes it an explicit rule).
|
||
- alkhttp ADR-047 — the gateway as the sole HTTP invoke path (the
|
||
HTTP-only contract this ADR clarifies does not extend to WS).
|
||
- alkhttp ADR-001 / ADR-002 — ALPN-based dispatch; `HttpAdapter` as the
|
||
`ProtocolHandler` for `h2`/`http/1.1`; the WS upgrade rides the HTTP
|
||
surface.
|
||
- alkhttp ADR-069 — WebTransport removed from alkhttp scope (context for
|
||
the reversal posture above).
|
||
- The alkcall crate docs — call-protocol spec (§"Transport
|
||
agnosticism") and client-and-adapters spec (§"services/list"); the old
|
||
relative links into the alknet spec tree became textual references to
|
||
the alkcall crate's own documentation.
|