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.
This commit is contained in:
@@ -68,4 +68,8 @@ required-features = ["test-support"]
|
||||
|
||||
[[test]]
|
||||
name = "from_mcp_integration"
|
||||
required-features = ["mcp"]
|
||||
|
||||
[[test]]
|
||||
name = "full_surface"
|
||||
required-features = ["mcp"]
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-08-27
|
||||
last_updated: 2026-08-28
|
||||
---
|
||||
|
||||
# alkhttp
|
||||
@@ -76,9 +76,9 @@ Key ones:
|
||||
|
||||
| OQ | Title | Status | Relevance |
|
||||
|----|-------|--------|-----------|
|
||||
| OQ-01 | WS message ↔ byte-stream adaptation | open | The channels demux reads bytes (`read_exact`); axum WS is message-oriented. The WS↔byte-stream adapter's buffering/flush semantics are the core implementation risk for both the server path and `from_wss` |
|
||||
| OQ-02 | `/publish` body framing details | open | NDJSON line = one published chunk; error envelope position (final JSON body vs HTTP trailer) needs a concrete decision before the gateway-spec version bumps |
|
||||
| OQ-03 | `from_wss` reconnection semantics | open | Does a dropped WSS connection re-discover (`services/list`) and re-register, or hold stale registrations? |
|
||||
| OQ-01 | WS message ↔ byte-stream adaptation | resolved | Validated by the production adapter (`src/websocket/byte_adapter.rs`) in both directions (server upgrade path + `from_wss` client) |
|
||||
| OQ-02 | `/publish` body framing details | resolved | First line `{operation, chunk}`; terminal errors are plain HTTP status + JSON body (ADR-068, `src/gateway/routes.rs::publish_handler`) |
|
||||
| OQ-03 | `from_wss` reconnection semantics | open | v1 = drop → retryable failures (ADR-070, implemented in `src/adapters/from_wss.rs`); whether a built-in reconnection policy is ever warranted is deferred to the assembly layer |
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
|
||||
@@ -46,10 +46,3 @@ The endpoint advertises the union of all registered handlers' ALPN strings. When
|
||||
- [ADR-003](003-crate-decomposition.md): Crate decomposition
|
||||
- iroh reference (alknet mono-repo): `docs/research/references/iroh/` (ALPN dispatch, ProtocolHandler pattern)
|
||||
- Replaces the old three-layer model (StreamInterface/MessageInterface/OperationEnv)
|
||||
|
||||
## Port notes
|
||||
|
||||
- Decision retargeted from alknet-wide dispatch to alkhttp: the `HttpAdapter` registers on the standard HTTP ALPNs (`h2`, `http/1.1`); the shared endpoint/TLS-transport layer is an alkcall/alknet-side concern, so "a single QUIC+TLS endpoint" became the transport-agnostic "a single endpoint" (and "shared QUIC+TLS endpoint" → "shared endpoint" in the Context insight).
|
||||
- The ADR does not list h3 as an alkhttp deferred registration; the only h3 mention is the Hickory DNS example of ALPN registration, left as-is. h3/WebTransport is an alknet-side concern, not an alkhttp one.
|
||||
- ADR-006 reference converted to a textual "alkcall crate docs" reference (the connection/ALPN-convention ADRs are alkcall-internal and not ported here).
|
||||
- Mono-repo-relative research paths (`docs/research/...`) annotated as alknet mono-repo paths.
|
||||
@@ -68,10 +68,3 @@ In alkhttp, the `HttpAdapter` implements this trait on the standard HTTP ALPNs (
|
||||
- ADR-007: BiStream type definition — revised this ADR's signature from BiStream to Connection (see the alkcall crate docs)
|
||||
- iroh ProtocolHandler pattern (alknet mono-repo): `docs/research/references/iroh/`
|
||||
- Replaces StreamInterface, MessageInterface, and ListenerConfig
|
||||
|
||||
## Port notes
|
||||
|
||||
- Framing layer renames: "common utilities can live in alknet-core" → `alkcall`; "alknet-call provides this as a handler" → "the call protocol provides this as a handler".
|
||||
- iroh phrasing made transport-agnostic: "takes a bidirectional QUIC stream" → "takes a bidirectional stream".
|
||||
- Added one clause pinning the trait to alkhttp's use: `HttpAdapter` implements it on `h2`/`http/1.1` and serves HTTP over the BiStream from `Connection::accept_bi()`.
|
||||
- ADR-007 references (BiStream type definition, alkcall-internal) converted to textual "alkcall crate docs" references.
|
||||
@@ -148,26 +148,3 @@ ADR-064 (irpc-never-integrated-hand-rolled-framing, in the alknet
|
||||
mono-repo ADRs) for the full
|
||||
record (ADR-005, which accepted "irpc as the call protocol foundation," is
|
||||
superseded).
|
||||
|
||||
## Port notes
|
||||
|
||||
- This is the historical alknet decomposition ADR, ported because alkhttp's
|
||||
dependency edges are defined here (Amendment 1 in particular). The table's
|
||||
crate names are annotated in place where the extraction renamed them:
|
||||
alknet-core + alknet-call merged into **alkcall** (which vendors the core
|
||||
types); alknet-vault → **alkvault**; alknet-http → **alkhttp**. The
|
||||
dependency-flow diagram keeps the generic "core"/"call" labels it
|
||||
historically used; "call ← alknet-agent / call ← alknet-napi" describe
|
||||
the alknet mono-repo, not alkhttp's own edges.
|
||||
- Per the task instructions, both amendments are retained. Amendment 1's
|
||||
statement of the alkhttp edge ("alknet-http depends on alknet-call")
|
||||
now reads as **alkhttp depends on alkcall alone** — the "depends on
|
||||
`alknet-core, axum`" dependency in the table is now `alkcall, axum`.
|
||||
- Link targets `crates/http/overview.md` and
|
||||
`crates/call/client-and-adapters.md` (mono-repo doc paths) replaced:
|
||||
the former points to `overview.md` in `docs/architecture/`, the latter
|
||||
is a textual "alkcall crate docs" reference. ADR-057/ADR-064 links are
|
||||
textual (those ADRs are not ported to alkhttp).
|
||||
- ADR-057 and ADR-064, and the code-path references
|
||||
(`crates/alknet-call/src/protocol/wire.rs`), are alkcall-internal
|
||||
concerns; referenced textually.
|
||||
@@ -76,16 +76,3 @@ alkvault stays standalone. It does not depend on the core crate or `IdentityProv
|
||||
- [ADR-003](003-crate-decomposition.md): Crate decomposition
|
||||
- ADR-005: irpc as call protocol foundation
|
||||
- The previous architecture had equivalent decisions in ADR-023 (unified auth) and ADR-029 (identity as core type), which are archived in the reference implementation at `/workspace/@alkdev/alknet-main/`.
|
||||
|
||||
## Port notes
|
||||
|
||||
- ADR-011 (AuthContext immutability) and the `IdentityProvider` internals are
|
||||
alkcall-internal concerns; referenced textually without links.
|
||||
- The `WebTransportAdapter` row and the WebTransport negative consequence are
|
||||
retained as historical decision content; in alkhttp the browser path is
|
||||
WebSocket (ADR-048) and h3/WebTransport is out of scope in alkhttp
|
||||
(ADR-069, alknet-side).
|
||||
- Crate renames: "alknet-core" → "the shared core (now vendored in alkcall)",
|
||||
"alknet-vault"/"alknet-secret" → alkvault. (The source alternated between
|
||||
the "alknet-secret" and "alknet-vault" names for the same crate in the
|
||||
Consequences and Decision sections; normalized to alkvault.)
|
||||
@@ -301,28 +301,3 @@ multiplexing, not listener transports.
|
||||
This also means shutdown is single-owner: the endpoint owns all its
|
||||
accept loops (quinn, iroh, TCP+TLS); one `shutdown()` stops them all.
|
||||
The multi-owner shutdown coordination problem (OQ-61) does not arise.
|
||||
|
||||
## Port notes
|
||||
|
||||
- This ADR is endpoint-internal to alkcall/alknet; ported because alkhttp
|
||||
consumes its dispatch model (HttpAdapter registration, stealth/decoy
|
||||
mapping, static ALPN registration). The alknet `AlknetEndpoint` struct in
|
||||
the sketches is generically renamed `Endpoint` — the type lives in
|
||||
alkcall's lineage, not in alkhttp.
|
||||
- Stealth-mode phrasing retargeted: "the `alknet/http` handler can serve a
|
||||
decoy website" → "the HTTP handler can serve a decoy website", with a
|
||||
consequence clause noting that in alkhttp this is the `HttpAdapter`'s
|
||||
decoy surface.
|
||||
- QUIC-overload phrasing made transport-agnostic in the two places that
|
||||
describe HttpAdapter mechanics: Amendment 1's "calls `accept_bi` once
|
||||
(yielded by the single stream)" now reads "runs hyper over a
|
||||
bidirectional stream (BiStream) yielded by `Connection::accept_bi()`".
|
||||
The endpoint/iroh/quinn mechanics sections are untouched (they describe
|
||||
alkcall/alknet-side reality).
|
||||
- ADR-065, ADR-083, ADR-027, ADR-044 references: ADR-044 is linked as
|
||||
`decisions/044-defer-webtransport-browsers-use-websocket.md`-equivalent
|
||||
(ported by other agents under the same number); ADR-065, ADR-083, and
|
||||
ADR-027 are alkcall-internal, so referenced textually. h3/WebTransport
|
||||
mentions that imply an alkhttp deliverable are marked out of scope in
|
||||
alkhttp (ADR-069).
|
||||
- Mono-repo reference paths annotated as alknet mono-repo paths.
|
||||
@@ -210,31 +210,3 @@ should be revisited:
|
||||
- alknet OQ-16: Safe vault operations for call protocol exposure (resolved by
|
||||
this ADR: none, for now)
|
||||
- alkvault crate (extracted from the alknet mono-repo's `alknet-vault`)
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet-http" → alkhttp; "alknet-core"/"alknet-call" → alkcall
|
||||
(alkcall 0.1.1, which merged the old alknet-core and alknet-call and vendors
|
||||
the core types); "alknet-vault" → alkvault; "the `alknet` crate" (CLI
|
||||
binary) → "the CLI binary (or an embedded assembly layer)" — the CLI crate
|
||||
name is a consumer concern, not an alkhttp one.
|
||||
- "over QUIC" → "over the wire" (§Context, `vault/unlock` bullet): the call
|
||||
protocol is transport-agnostic (alkcall ADR-007), so the constraint is
|
||||
transport-independent.
|
||||
- §4 correction: the original said the adapter patterns are "defined in Rust
|
||||
in alknet-call per ADR-013". The `OperationAdapter` trait lives in the
|
||||
alkcall crate (alkcall ADR-033, alkcall ADR-022 §5); the HTTP-backed adapter
|
||||
implementations live in alkhttp (alkcall ADR-027 / this crate's ADR-066).
|
||||
The original predates ADR-066's location correction.
|
||||
- References to the alknet mono-repo's vault ADRs (ADR-008) and OQs (OQ-04,
|
||||
OQ-15, OQ-16) are annotated as alknet-record citations; the alkcall crate
|
||||
has its own OQ numbering. OQ-15's resolution (the adapter contract) is
|
||||
alkcall ADR-022, ported to this crate as ADR-017.
|
||||
- alknet ADR-003 is ported to this crate under the same number and is linked;
|
||||
alknet ADR-005/008/009/013 are cited textually with their alkcall mappings
|
||||
where one exists (alkcall ADR-032 for the one-way-door framework, alkcall
|
||||
ADR-033 for Rust-canonical).
|
||||
- No decision content changed — the vault-is-assembly-layer-only rule, the
|
||||
Capabilities injection mechanism, the no-secret-material-on-the-wire
|
||||
constraint, and the credential-source adapter contract are verbatim from
|
||||
the alknet ADR.
|
||||
@@ -327,14 +327,3 @@ Principle of least privilege.
|
||||
art for scoped composition env
|
||||
- POC at `/workspace/toolEnv` — demonstrated the sandbox-to-registry bridge with
|
||||
the full-registry exposure gap
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet-http" → alkhttp; "alknet-core"/"alknet-call" → alkcall.
|
||||
- Producer/consumer terminology: "wire client" → "wire consumer"; the `Visibility::External` comment now reads "call.requested from a consumer" (the call protocol's producer/consumer model — both sides of a connection can initiate). "MCP server trusted"-style inherent-directionality phrasing is not affected here (no MCP server/client mention in this ADR). The NAPI/Python/agent consumers are consumers of the call protocol, not "clients" in the connection-establishment sense.
|
||||
- "future services" in the Context consumer list extended with "HTTP adapter" — the alkhttp gateway and adapters are consumers of this privilege model (adapter-registered ops are `Internal` by default per [ADR-017](017-call-protocol-client-and-adapter-contract.md)).
|
||||
- Added a closing Context paragraph (new paragraph, marked as port context): why the ADR is ported to alkhttp (the gateway and adapters sit on top of these protocol semantics) and where the decision record now lives (alkcall ADR-017). This is port framing, not a new decision.
|
||||
- Cross-references remapped per the verified mapping: alknet ADR-022 → alkcall ADR-018 (CompositionAuthority; ported here as [ADR-022](022-handler-registration-provenance-and-composition-authority.md)); alknet ADR-024 (env/scoped_env split) → alkcall ADR-019. ADR-004 and ADR-014 are ported to this crate under the same numbers and linked; alknet ADR-008 is cited textually (alknet mono-repo ADR; vault is an alkvault concern).
|
||||
- Spec-document links (`../crates/call/operation-registry.md`, `../crates/call/call-protocol.md`) converted to textual "the alkcall crate's docs/architecture/<doc>.md" references, with the original alknet mono-repo paths noted.
|
||||
- Alknet OQ numbers (OQ-15, OQ-17, OQ-19) are alknet-record citations, annotated as such — alkcall's OQ numbering differs.
|
||||
- No decision content changed — the authority-switch-not-ACL-skip model, the External/Internal visibility, the scoped composition env, and the three-controls table are verbatim from the alknet ADR.
|
||||
@@ -527,61 +527,3 @@ protocol-crate take-over that consumes the dial's `Connection`.
|
||||
|
||||
See the alkcall crate's `client-and-adapters.md`
|
||||
§"CallClient" for the reframed operational spec.
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet-http" → alkhttp; "alknet-core"/"alknet-call" → alkcall;
|
||||
"alknet node"/"non-alknet systems" → "alk node"/"non-alk systems".
|
||||
- ALPN correction: `alknet/call` → `alk/call` (alkcall ADR-004 renamed the
|
||||
ALPN convention to the `alk/` namespace).
|
||||
- Producer/consumer terminology: "server side"/"client side" (§Context,
|
||||
§1, §6) → "accept side"/"connect side" for the connection-establishment
|
||||
half; "who advertises"/"who opens connection" table retained (it was
|
||||
already direction-of-establishment language); §2's closing sentence now
|
||||
says the protocol does not distinguish producer and consumer after
|
||||
connection establishment. "Node.js and Python clients" → "consumers".
|
||||
HTTP/MCP inherent directionality is untouched ("HTTP clients", "MCP
|
||||
servers", "MCP clients" keep their names — those are protocol roles of the
|
||||
external systems, not call-protocol roles).
|
||||
- `Subscription` → `Sub` (alkcall renamed `OperationType::Subscription` to
|
||||
`Sub`; alkcall ADR-046 added `OperationType::Pub` — producer→consumer
|
||||
streaming via `call.published`, `HandlerKind::Sink`). §1's
|
||||
`subscribe()` description, §3's "or streams for subscriptions", and the
|
||||
Consequences "subscriptions → SSE long-poll" example now say `Sub`.
|
||||
- §4 corrected to name the concrete alkhttp realizations: `to_openapi` is the
|
||||
OpenAPI gateway pattern (ADR-042 — five fixed gateway endpoints, not one
|
||||
path per operation) and `to_mcp` is the MCP tool-gateway pattern (ADR-041 —
|
||||
four fixed gateway tools). The original predates those ADRs; the decision
|
||||
content (outbound projections of `External` ops) is unchanged.
|
||||
- Assumption 5 correction: "a separate HTTP handler (alknet-http)" → "an HTTP
|
||||
host (alkhttp) ... the gateway dispatch" — the direct-call per-operation
|
||||
HTTP surface was removed by ADR-047; the gateway is the sole invoke path.
|
||||
- Amendment remappings (verified alknet→alkcall ADR mapping): alknet
|
||||
ADR-028 → alkcall ADR-023; alknet ADR-029 → alkcall ADR-024; alknet ADR-016
|
||||
(abort cascade) → alkcall ADR-020; alknet ADR-024 (registry layering) →
|
||||
alkcall ADR-019; alknet ADR-065 (`Connection::from_stream`) → alkcall
|
||||
ADR-007; alknet ADR-080 (`ChannelClient`) → alkcall ADR-043; alknet ADR-089
|
||||
(dial seam) → alkcall ADR-045; alknet ADR-012 (stream model) → alkcall
|
||||
ADR-015; alknet ADR-069 (from_call manual free function) → alkcall ADR-028;
|
||||
alknet ADR-013 (Rust canonical) → alkcall ADR-033; alknet ADR-009 (one-way
|
||||
door framework) → alkcall ADR-032. The 2026-07-13 amendment title's
|
||||
"mirrors ADR-080's amendment" cites the alknet number (the amendment
|
||||
mirrored the alknet-record ADR-080).
|
||||
- All `client-and-adapters.md` relative links converted to textual
|
||||
"the alkcall crate's `client-and-adapters.md`" references (the document
|
||||
lives in the alkcall crate's docs/architecture/).
|
||||
- alknet OQ numbers (OQ-15, OQ-25..28, OQ-55) are alknet-record citations,
|
||||
annotated as such — alkcall's OQ numbering differs (e.g., alkcall OQ-25 is
|
||||
BiStream type definition, not the DC remainders).
|
||||
- The `QuicCallCredentials`-shaped `connect(addr, credentials)` signature in
|
||||
§1 is retained as decision history (the 2026-07-16 amendment removes
|
||||
`connect`); the QUIC-specific wording "opens a QUIC connection" is
|
||||
softened to "opens a connection" in the §1 prose per the transport-agnostic
|
||||
amendment, with the amendment block recording the original framing.
|
||||
- Status line: "per ADR-089 §5" annotated as alknet ADR-089 (alkcall
|
||||
ADR-045) — the amendment citation refers to the alknet-record number.
|
||||
- No decision content changed — the shared dispatch loop, the
|
||||
connection-direction independence, the adapter contract trait, the
|
||||
cross-node abort cascade, the credential-source rule, and all three
|
||||
amendment blocks are verbatim from the alknet ADR modulo the mechanical
|
||||
corrections logged above.
|
||||
-35
@@ -668,38 +668,3 @@ the fuzzer validates the implementation against the spec.
|
||||
- Kernel/user mode analogy: `getaddrinfo` runs under kernel authority, not
|
||||
the caller's `CAP_NET_RAW`; the curated entry point exists to do things
|
||||
the user can't, on the user's behalf
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet-http" → alkhttp; "alknet-core"/"alknet-call" → alkcall.
|
||||
- Cross-reference remappings (verified alknet→alkcall ADR mapping): alknet
|
||||
ADR-016 (abort cascade) → alkcall ADR-020; alknet ADR-024 (registry
|
||||
layering) → alkcall ADR-019; alknet ADR-017 (adapter contract) → alkcall
|
||||
ADR-022 (ported here as ADR-017). ADR-014 and ADR-015 are ported to this
|
||||
crate under the same numbers and linked.
|
||||
- `from_jsonschema` correction (per alkcall ADR-027 / this crate's ADR-066):
|
||||
the `FromJsonSchema` enum-doc comment and the ADR-066 amendment block now
|
||||
say the adapter lives in **alkhttp** (the original said `alknet-http`, which
|
||||
is the same crate post-extraction — updated to the current name rather than
|
||||
left as a stale reference). The superseded-ADR citation inside the
|
||||
alkcall-crate copy of this ADR cites alkcall ADR-027; the alknet-record
|
||||
original cited alknet ADR-066.
|
||||
- "QUIC forwarding stub (from_call)" → "Call-protocol forwarding stub
|
||||
(from_call)" — the call protocol is transport-agnostic (alkcall ADR-007);
|
||||
the original wording predates the transport-agnostic connection model.
|
||||
Assumption 5's "over HTTP, MCP, or QUIC" → "over HTTP, MCP, or the call
|
||||
protocol".
|
||||
- Review paths (`docs/reviews/...`) are alknet mono-repo artifacts; annotated
|
||||
as such rather than converted to links (the reviews are not ported to
|
||||
alkhttp).
|
||||
- alknet OQ-19 is an alknet-record citation (alkcall's OQ numbering differs).
|
||||
- alknet ADR-008 is cited textually (alknet mono-repo ADR; the vault is an
|
||||
alkvault concern).
|
||||
- The flowgraph references are kept as absolute workspace paths (the
|
||||
directories still exist); "a dedicated `alknet-flowgraph` crate" → "a
|
||||
dedicated flowgraph crate" (hypothetical crate name genericized, matching
|
||||
the alkcall-crate copy of this ADR).
|
||||
- No decision content changed — the provenance model, `CompositionAuthority`,
|
||||
`ScopedOperationEnv`, the `HandlerRegistration` bundle, the dispatch-path
|
||||
wiring, the no-intersection rule, and the ADR-066 amendment are verbatim
|
||||
from the alknet ADR modulo the corrections logged above.
|
||||
@@ -430,50 +430,3 @@ enum instead of a generic `Result<Output, string>`.
|
||||
- TypeScript reference: `/workspace/@alkdev/operations/src/types.ts`
|
||||
L38–47 (`ErrorDefinitionSchema`), L94, L112 (`errorSchemas` on
|
||||
`OperationSpec`), `error.ts` L25–51 (`mapError`)
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet-http" → alkhttp; "alknet-core"/"alknet-call" → alkcall
|
||||
("the call crate (now alkcall)").
|
||||
- `OperationType::Subscription` → `Sub` (alkcall rename; alkcall ADR-046
|
||||
added `OperationType::Pub`, producer→consumer streaming via
|
||||
`call.published`, `HandlerKind::Sink`). Corrections flowing from this:
|
||||
the `INVALID_OPERATION_TYPE` table row now lists the dispatch-path
|
||||
mismatches per the current handler-kind model (`invoke()` on a `Sub`,
|
||||
`invoke_streaming()` on a `Query`/`Mutation`, `invoke_sink()` on a
|
||||
`Query`/`Mutation`/`Sub`, and `OperationEnv::invoke()` on a `Sub` during
|
||||
composition) and cites alkcall ADR-021 (this crate's
|
||||
[ADR-049](049-streaming-handler-for-subscriptions.md)) and alkcall ADR-046
|
||||
for `Pub`/`Sink`. `from_openapi` produces no `Pub` ops in v1 (SSE
|
||||
responses detect as `Sub`), so the adapter-fidelity discussion is
|
||||
unaffected by `Pub`.
|
||||
- Producer/consumer terminology: "A client calling `/fs/readFile`" → "a
|
||||
consumer"; "clients get typed errors" → "consumers get typed errors";
|
||||
§2 `code` bullet "Clients should switch on `code`" → "Consumers should
|
||||
switch on `code`"; §3 "Clients should handle protocol-level codes" →
|
||||
"Consumers should handle". HTTP/MCP/OpenAPI client references
|
||||
(§5's "HTTP clients", the client code generation, "brittle clients")
|
||||
keep their names — those are inherent-directionality roles of the
|
||||
external systems, not call-protocol roles. Wire-format backward
|
||||
compatibility bullets retain "existing clients" wording (wire consumers).
|
||||
- `from_openapi` example: the original said the adapter maps "the OpenAPI
|
||||
error schema to alknet's JSON Schema format" — corrected to "the call
|
||||
protocol's JSON Schema format".
|
||||
- §5 closing sentences extended to note that `from_openapi`/`to_openapi`
|
||||
live in alkhttp and that the `HTTP_<status>` rule is part of this crate's
|
||||
gateway error-fidelity contract (ADR-047) — port framing, not a new
|
||||
decision.
|
||||
- Cross-reference remappings (verified alknet→alkcall ADR mapping): alknet
|
||||
ADR-017 (adapter contract) → alkcall ADR-022; alknet ADR-049 (streaming
|
||||
handler) → alkcall ADR-021. ADR-014 and ADR-015 are ported to this crate
|
||||
under the same numbers and linked.
|
||||
- Review/spec paths (`docs/reviews/...`, `docs/sdd_process.md`,
|
||||
`call-protocol.md` L-references) are alknet mono-repo artifacts; annotated
|
||||
as alknet-record citations. The `call-protocol.md` relative link and its
|
||||
line-number citation for the unknown-code rule became a textual "the
|
||||
alkcall crate's `call-protocol.md`" reference.
|
||||
- No decision content changed — the `error_schemas` field, the `details`
|
||||
payload field, the protocol-vs-operation code namespace split, the handler
|
||||
error mapping, the `HTTP_<status>` prefix rule, and the `services/schema`
|
||||
exposure are verbatim from the alknet ADR modulo the corrections logged
|
||||
above.
|
||||
@@ -11,20 +11,17 @@ changes)
|
||||
|
||||
## Context
|
||||
|
||||
> **Port note (scope):** This ADR is ported because it is the decision of
|
||||
> record for the browser-facing TLS constraint this crate inherits:
|
||||
> **browsers require X.509** — they cannot present or verify RFC 7250 raw
|
||||
> Ed25519 keys, so the browser-reachable surface of alkhttp (`http/1.1`,
|
||||
> `h2`, and the WebSocket upgrade path) must be served from an X.509
|
||||
> identity (CA-issued via ACME, or operator-provided). The TLS machinery
|
||||
> itself — `TlsIdentity`, `TlsSetup`, ACME provisioning, the rustls
|
||||
> server config — is **not implemented in this crate**; alkhttp is
|
||||
> transport-coupling-free by design (alknet ADR-027's alknet-internal
|
||||
> sections describe the alknet endpoint layer, which remains an alknet
|
||||
> concern; see §Port notes at the end of this file). The clauses below
|
||||
> that matter to alkhttp are the browser constraint and the identity
|
||||
> modes it implies; the alknet-internal provisioning mechanics are
|
||||
> retained for provenance and marked as alknet concerns.
|
||||
**Scope for this crate:** This ADR is the decision of record for the
|
||||
browser-facing TLS constraint this crate inherits: **browsers require
|
||||
X.509** — they cannot present or verify RFC 7250 raw Ed25519 keys, so the
|
||||
browser-reachable surface of alkhttp (`http/1.1`, `h2`, and the WebSocket
|
||||
upgrade path) must be served from an X.509 identity (CA-issued via ACME,
|
||||
or operator-provided). The TLS machinery itself — `TlsIdentity`,
|
||||
`TlsSetup`, ACME provisioning, the rustls server config — is **not
|
||||
implemented in this crate**; alkhttp is transport-coupling-free by
|
||||
design, and the TLS provisioning mechanics described below remain an
|
||||
alknet concern (retained for provenance). The clauses that matter to
|
||||
alkhttp are the browser constraint and the identity modes it implies.
|
||||
|
||||
OQ-12 marked "resolved" identified two TLS identity use cases: RFC 7250
|
||||
raw Ed25519 keys (default, P2P) and X.509 certs (domain-hosted, browsers).
|
||||
@@ -333,7 +330,7 @@ in the endpoint layer where it belongs.
|
||||
- OQ-12 (TLS identity provisioning) — updated by this ADR *(alknet OQ)*
|
||||
- alknet ADR-010 — multi-connectivity endpoint, feature-gated
|
||||
transports; the ALPN router and endpoint that owns TLS identity and
|
||||
dispatch (an alknet decision; not ported to alkhttp — see Port notes)
|
||||
dispatch (an alknet decision; not ported to alkhttp)
|
||||
- alknet ADR-004 — auth as shared core (an alknet decision; ported to
|
||||
alkhttp as alkhttp ADR-004, same number, different scope)
|
||||
- `docs/architecture/crates/core/endpoint.md` *(alknet doc)* — TLS identity use cases
|
||||
@@ -344,31 +341,3 @@ in the endpoint layer where it belongs.
|
||||
requirement load-bearing for this crate
|
||||
- alkhttp ADR-034 — browsers are not peers; the public X.509 endpoint
|
||||
role and the hub role in the peer model
|
||||
|
||||
## Port notes
|
||||
|
||||
- All TLS provisioning mechanics (`TlsIdentity`, `TlsSetup`,
|
||||
`build_rustls_server_config`, `AcmeState`, the `acme` feature, the
|
||||
`acme-tls/1` dispatch guard) are **alknet concerns** — they live in the
|
||||
alknet endpoint/config layer (alknet ADR-010, ADR-001), not in
|
||||
alkhttp. alkhttp is transport-coupling-free (no TLS, no endpoint, no
|
||||
accept loop); sections describing them are marked *"(alknet
|
||||
concern)"* inline and retained for provenance. The clause that is
|
||||
substantive for alkhttp — **browsers require X.509 for browser-facing
|
||||
TLS** — is restated under "What alkhttp inherits from this decision."
|
||||
- `alknet-core` ownership of `Ed25519SecretKey` is historical: the core
|
||||
types now live in the alkcall crate (vendored there). The decision
|
||||
text is preserved as written; the wrapper type is an alkcall-owned
|
||||
type today.
|
||||
- Reference links rewritten per alkhttp docs conventions: the original
|
||||
relative sibling-ADR links to alknet ADR-010 and alknet ADR-004 are
|
||||
textual references above (neither is ported to alkhttp under those
|
||||
numbers/slug; alkhttp ADR-004 is a different document — auth as
|
||||
shared core). The `crates/core/*.md` spec links are dropped in
|
||||
favor of the textual "alknet doc" annotations because the alknet spec
|
||||
tree is not part of this crate's docs.
|
||||
- The original ADR-083 is an alknet decision (shared `dispatch` guard
|
||||
relocation); it is cited textually as "alknet ADR-083" and is not
|
||||
ported.
|
||||
- Original title preserved: "TLS Identity Redesign — ACME Integration +
|
||||
RawKey Decoupling".
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
Accepted (resolves OQ-37)
|
||||
|
||||
> **Port note (emphasis):** This port emphasizes **§4 — browsers are not
|
||||
> peers** — because that clause 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
|
||||
**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.
|
||||
@@ -270,12 +270,11 @@ clients.
|
||||
|
||||
### 5. WebTransport relay-as-proxy is a transport-only feature, scoped separately *(alknet concern)*
|
||||
|
||||
> **Port note:** 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 provenance of the
|
||||
> *auth-model* point (the proxy is transport-only and does not change
|
||||
> identity resolution), which remains true regardless of where the
|
||||
> proxy lives.
|
||||
**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
|
||||
@@ -491,51 +490,3 @@ alknet/alkcall assembly-layer components, not alkhttp surface.)*
|
||||
- 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
|
||||
|
||||
## Port notes
|
||||
|
||||
- The call protocol core types are vendored in the **alkcall** crate
|
||||
(old alknet-core + alknet-call merged). All type-level references are
|
||||
re-cited to alkcall ADRs: peer-keyed overlay/`PeerCompositeEnv` is
|
||||
alkcall ADR-024, `PeerEntry`/fingerprint normalization is alkcall
|
||||
ADR-025, `CallClient`/adapter contract is alkcall ADR-022 (§7
|
||||
credentials), `ConnectionCredentials` is alkcall ADR-012, Layer 2
|
||||
registry layering is alkcall ADR-019, `from_call` as a manual free
|
||||
function is alkcall ADR-028. The alknet ADR numbers that originally
|
||||
carried these (ADR-029, ADR-030, ADR-017, ADR-024) are alknet numbers
|
||||
and do not coincide with alkcall or alkhttp numbers; each citation
|
||||
above names the owning crate explicitly.
|
||||
- `CallCredentials` → `ConnectionCredentials`: alkcall ADR-012 renamed
|
||||
the credential struct when it decoupled dial from call; the ported
|
||||
text uses the current name with the original alkcall ADR-022 §7
|
||||
citation preserved.
|
||||
- `alknet/call` (the old ALPN string) → `alk/call` (alkcall ADR-004
|
||||
`alk/` convention). Table and prose updated.
|
||||
- "alknet-http" → "alkhttp"; "alknet node"/"alknet peer" phrasing
|
||||
generalized to "node"/"peer" where the sentence is about the protocol
|
||||
model rather than the alknet binary.
|
||||
- §4 heading and prose originally said "over WebTransport"; the port
|
||||
adds "(or HTTPS — and, per alkhttp ADR-044, over WebSocket)" and
|
||||
attributes the served surface to alkhttp. WebTransport references are
|
||||
marked as alknet concerns; in alkhttp, WebTransport is out of scope
|
||||
entirely (alkhttp ADR-069), not deferred.
|
||||
- §2, §3, §5, and §6 are retained for provenance but annotated: TLS
|
||||
verifier selection, fingerprint pinning, the iroh transport relay, and
|
||||
the on-chain peer-store adapter are dial-layer/alknet concerns, not
|
||||
alkhttp surface. §4 is the load-bearing clause for this crate and is
|
||||
the emphasis of this port.
|
||||
- alknet OQ references (OQ-29, OQ-36, OQ-37, OQ-10, OQ-38) are alknet
|
||||
open-questions records, not ported into alkhttp's OQ file; they are
|
||||
cited textually as "alknet OQ-NN" (alkcall ADR-024 is cited for the
|
||||
peer-graph model rather than alknet OQ-37's original resolution
|
||||
context).
|
||||
- Reference links rewritten: `../../decisions/...` and
|
||||
`../../crates/...` relative links replaced per alkhttp docs
|
||||
conventions; links into the old call/core spec trees became textual
|
||||
"alkcall crate docs" references. alknet ADR-027 is linked as a
|
||||
sibling file because it is ported to alkhttp (same number and slug).
|
||||
- The original also cited `docs/research/alknet-http/phase-0-findings.md`
|
||||
(DH-2) and iroh reference docs; these are alknet research artifacts
|
||||
and are referenced textually only.
|
||||
- Original title preserved: "Outgoing-Only X.509 and the Three Peer
|
||||
Roles".
|
||||
@@ -262,42 +262,3 @@ without auth before identity is resolvable.
|
||||
the decision this ADR resolves
|
||||
- `http-server.md` (in this crate's `docs/architecture/`) — the spec that
|
||||
implements this mapping (alknet original: `crates/http/http-server.md`)
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet-http" → alkhttp; "alknet" (the system/node being
|
||||
described) → alkhttp where the HTTP-served surface is meant.
|
||||
- `OperationType::Subscription` → `Sub` (alkcall rename) in the method
|
||||
table and the SSE section. alkcall ADR-046 later added
|
||||
`OperationType::Pub` (producer→consumer streaming via `call.published`,
|
||||
`HandlerKind::Sink`); `Pub` has no row in this ADR's HTTP method table —
|
||||
its HTTP projection is the `/publish` gateway endpoint (alkhttp
|
||||
ADR-068).
|
||||
- **Gateway endpoint count**: the alknet record's "5 fixed gateway
|
||||
endpoints" statements are preserved verbatim as history; alkhttp extends
|
||||
the set with `/publish` (alkhttp ADR-068) — marked with an inline
|
||||
annotation in the amendment blockquote, not silently rewritten.
|
||||
- Cross-reference remappings (verified alknet→alkcall ADR mapping): alknet
|
||||
ADR-016 (abort cascade) → alkcall ADR-020. ADR-004/010/015/017/023 are
|
||||
ported to this crate under the same numbers and linked; their alkcall
|
||||
record numbers are noted alongside where cited (015→alkcall ADR-017,
|
||||
017→alkcall ADR-022, 023→alkcall ADR-016).
|
||||
- Producer/consumer terminology: the original contained no call-protocol
|
||||
server/client role framing; "external HTTP clients (curl, axios, browser
|
||||
`fetch`)" and similar retain their names — those are inherent-directionality
|
||||
roles of the HTTP/OpenAPI surface, not call-protocol roles.
|
||||
- Spec-document links converted per alkhttp conventions:
|
||||
`crates/http/http-server.md` → `http-server.md` in this crate's
|
||||
`docs/architecture/`; `operation-registry.md` → textual "the alkcall
|
||||
crate's docs/architecture/operation-registry.md"; `auth.md` and
|
||||
`endpoint.md` are alknet mono-repo spec-tree documents cited textually
|
||||
(the auth model is ADR-004, the stealth mapping ADR-010).
|
||||
- OQ references (OQ-11, OQ-13, OQ-17) are alknet OQ-tracker items, cited
|
||||
textually; the operation path format is owned by the alkcall crate.
|
||||
- The `docs/research/alknet-http/phase-0-findings.md` pointer is kept as a
|
||||
historical alknet mono-repo reference (no alkhttp equivalent exists).
|
||||
- No decision content changed — the three-option framing, the direct-path
|
||||
decision (retained as the historical record of the ADR-047-superseded
|
||||
routing), the method table, SSE projection, auth, stealth decoy,
|
||||
`/healthz`, consequences, and assumptions are verbatim from the alknet
|
||||
ADR modulo the adaptations above.
|
||||
@@ -180,25 +180,3 @@ wants it, they build it themselves and own the security model.
|
||||
streamable HTTP MCP client (the `from_mcp` pattern)
|
||||
- `docs/architecture/http-mcp.md` — this crate's spec that implements
|
||||
`from_mcp`/`to_mcp`
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet-http" → alkhttp throughout (crate name, security
|
||||
posture, feature-flag and threat-model references).
|
||||
- alknet ADR-025 (vault local-only dispatch) is cited textually: it is
|
||||
not ported to alkhttp or alkcall; the vault decision record remains in
|
||||
the alknet mono-repo. ADR-014/015/022 are ported to this crate under
|
||||
the same numbers and linked.
|
||||
- The `client-and-adapters.md` citation became a textual "the alkcall
|
||||
crate's `client-and-adapters.md`" reference (no relative link; the
|
||||
document lives in alkcall's docs/architecture/).
|
||||
- `crates/http/http-mcp.md` → `docs/architecture/http-mcp.md` (this
|
||||
crate's docs/architecture/). The alknet mono-repo research-doc path
|
||||
(`docs/research/alknet-http/phase-0-findings.md`) is kept and labeled
|
||||
as a mono-repo doc.
|
||||
- MCP server/client terminology untouched (inherent MCP directionality).
|
||||
- No frontmatter in the source; status kept as Proposed.
|
||||
- No decision content changed — the streamable-HTTP-only decision, the
|
||||
stdio exclusion rationale, the bridge posture, the feature-gate shape,
|
||||
and the consequences/assumptions are verbatim from the alknet ADR
|
||||
modulo the renames logged above.
|
||||
@@ -156,25 +156,3 @@ concerns that make splitting them counterproductive:
|
||||
gate
|
||||
- `overview.md` — the crate overview (the inline rationale
|
||||
for this decision is replaced by a pointer to this ADR)
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: alknet-http → alkhttp; alknet-call → alkcall; the
|
||||
hypothetical split-crate names (`alknet-http-server` +
|
||||
`alknet-http-client`) genericized ("an HTTP-server crate + an
|
||||
HTTP-client crate").
|
||||
- The Context line "for `h2`/`http/1.1`/`h3`" was corrected to
|
||||
"for `h2`/`http/1.1`" — h3 is out of scope in alkhttp (ADR-069);
|
||||
alkhttp serves HTTP/1.1 + HTTP/2 on the standard ALPNs.
|
||||
- The `to_openapi` route description "whose paths mirror the
|
||||
`/{service}/{op}` HTTP routes the `HttpAdapter` serves (ADR-036)"
|
||||
was corrected: the direct-call `/{service}/{op}` surface was removed
|
||||
by ADR-047, so the doc now says the paths mirror the gateway routes,
|
||||
with ADR-036's mapping noted as superseded by ADR-047 (linked as
|
||||
`decisions/047-remove-direct-call-http-surface.md`, ported by another
|
||||
agent under the same number).
|
||||
- ADR-014/017/023/036/037 are being ported with the same numbers by
|
||||
other agents and are linked as `decisions/NNN-<slug>.md` (alknet
|
||||
slugs). The mono-repo link `../crates/call/client-and-adapters.md`
|
||||
became a textual "alkcall crate docs" reference;
|
||||
`crates/http/overview.md` → `overview.md` in `docs/architecture/`.
|
||||
@@ -224,40 +224,3 @@ path, just reached through an MCP tool call instead of an HTTP request.
|
||||
- `/workspace/rust-sdk/crates/rmcp/src/handler/server.rs` —
|
||||
`list_tools` / `call_tool` server trait (the interface `to_mcp`
|
||||
implements)
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet" → "alkhttp" where it referred to the crate/node
|
||||
("an alknet operation" → "an alkhttp operation"; "an alknet node" →
|
||||
"an alkhttp node"; "not an alknet decision" → "not an alkhttp
|
||||
decision").
|
||||
- **`Subscription` → `Sub`** throughout (alkcall rename; the
|
||||
`OperationType` enum value).
|
||||
- **MCP gateway exclusion widened (extension note):** the alknet
|
||||
original excluded only `Subscription` operations. In alkhttp,
|
||||
alkcall ADR-046 added `OperationType::Pub` (streaming requests,
|
||||
producer→consumer). One line is added under §1: Sub (streaming
|
||||
responses) AND Pub (streaming requests, alkcall ADR-046) operations
|
||||
are both excluded from to_mcp — MCP tool calls are request/response.
|
||||
§2 keeps the original `Subscription`-exclusion decision text, renamed
|
||||
to `Sub`; the four-tool gateway set (search/schema/call/batch) is
|
||||
unchanged.
|
||||
- **Producer/consumer terminology**: §5 "browsers/MCP clients are not
|
||||
alknet peers" → "browsers/MCP clients are not call-protocol peers"
|
||||
(the ADR-034 peer-roles framing). HTTP/MCP server-client
|
||||
directionality untouched (MCP client/server, `tools/list`,
|
||||
`transport-*` names are protocol-inherent).
|
||||
- **Cross-refs**: `../../decisions/...` → `decisions/...`; ADR-036 is
|
||||
ported to this crate under the same number and linked as
|
||||
`decisions/036-http-to-call-operation-mapping.md` (alknet slug).
|
||||
The source cited alknet ADR-023 (operation error schemas) — its
|
||||
alkcall decision record is ADR-016, cited textually per the
|
||||
alkcall-cited-decisions convention.
|
||||
- `crates/http/http-mcp.md` → `docs/architecture/http-mcp.md` (this
|
||||
crate's docs/architecture/).
|
||||
- No frontmatter in the source; status kept as Proposed.
|
||||
- No decision content changed — the 4-tool gateway set, the
|
||||
request/response exclusion rule, the two-step discovery shape, the
|
||||
dispatch/`AccessControl` mapping, and the consequences/assumptions
|
||||
are verbatim from the alknet ADR modulo the renames and the logged
|
||||
Pub-exclusion extension line.
|
||||
@@ -289,47 +289,3 @@ require it for the common case.
|
||||
- `http-adapters.md` (in this crate's `docs/architecture/`) — the spec
|
||||
that implements the gateway (alknet original:
|
||||
`crates/http/http-adapters.md`)
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet" → alkhttp where the system/node being described is
|
||||
meant; "alknet-call" (protocol-foundation crate) → alkcall.
|
||||
- `OperationType::Subscription` → `Sub` in the gateway endpoint table,
|
||||
§2, and throughout (alkcall rename). alkcall ADR-046 added
|
||||
`OperationType::Pub` (producer→consumer streaming, `HandlerKind::Sink`);
|
||||
the original 5-endpoint table predates `Pub` and is preserved verbatim
|
||||
as decision history — the `/publish` extension is marked as an alkhttp
|
||||
addition (ADR-068), never silently rewritten.
|
||||
- **Gateway endpoint count**: the alknet record's "5 endpoints" is
|
||||
preserved everywhere as the original decision; alkhttp's `/publish`
|
||||
extension (ADR-068) is noted inline at each site where the count
|
||||
matters (§1, §3, §4, Assumption 1, Context). "Never rewrite history"
|
||||
framing: the published initial contract remains the 5-endpoint set;
|
||||
`/publish` is an additive extension under the contract's own
|
||||
additivity rule (§4, Assumption 1).
|
||||
- Producer/consumer terminology: "an admin sees admin operations, a
|
||||
regular user sees a subset" and similar retain their names — no
|
||||
call-protocol server/client role framing existed in the original
|
||||
body. The external client roles (code generator, human developer,
|
||||
`fetch`-based client, Gitea-style API consumers) are inherent-directionality
|
||||
roles of the HTTP/OpenAPI surface and keep their names.
|
||||
- Cross-reference remappings: ADR-015/017/023 are ported to this crate
|
||||
under the same numbers and linked; their alkcall record numbers are
|
||||
noted alongside (015→alkcall ADR-017, 017→alkcall ADR-022,
|
||||
023→alkcall ADR-016). ADR-041 is cited textually with its alknet
|
||||
number (the MCP gateway pattern; not part of this port's 3 files —
|
||||
verify the alkhttp port's existence when linking).
|
||||
- OQ references (OQ-14, OQ-39) are alknet OQ-tracker items, cited
|
||||
textually; OQ-39 is resolved by [ADR-045](045-to-openapi-gateway-spec-versioning.md)
|
||||
(ported to this crate under the same number).
|
||||
- Spec-document links converted per alkhttp conventions:
|
||||
`crates/http/http-adapters.md` → `http-adapters.md` in this crate's
|
||||
`docs/architecture/`; `websocket.md`/`webtransport.md` were alknet
|
||||
mono-repo spec-tree documents — the WS path here is the native
|
||||
call-protocol session (ADR-048) and no deferred WebTransport spec is
|
||||
carried (ADR-044).
|
||||
- No decision content changed — the flat→structured problem, per-caller
|
||||
surface problem, gateway pattern, the 5-endpoint table, the `subscribe`
|
||||
SSE semantics, `POST`-not-`GET` rationale, compatibility-contract
|
||||
clause, additive traditional projection, consequences, and assumptions
|
||||
are verbatim from the alknet ADR modulo the adaptations above.
|
||||
@@ -465,10 +465,10 @@ revival question.
|
||||
on the explicit distinction between deferral-as-hedging (rejected)
|
||||
and deferral-as-scoping (permitted: a decision that "genuinely
|
||||
doesn't need to be made yet because the use case isn't concrete" —
|
||||
scope management, not door-type classification). *(Port note: the
|
||||
one-way-door decision framework lives in the alkcall crate as
|
||||
alkcall ADR-032 — see the alkcall crate docs; alkhttp did not port
|
||||
it. The original linked alknet ADR-009 by relative path.)*
|
||||
scope management, not door-type classification). The one-way-door
|
||||
decision framework lives in the alkcall crate as alkcall ADR-032 —
|
||||
see the alkcall crate docs; alkhttp did not port it. The original
|
||||
linked alknet ADR-009 by relative path.
|
||||
- alknet ADR-038 — **superseded by this ADR (in alknet).** Its
|
||||
correction of the two-way-door-as-deferral anti-pattern stands; its
|
||||
specific decision (h3 in scope now) is reversed. **Not ported to
|
||||
@@ -511,45 +511,3 @@ revival question.
|
||||
shape (see the alkcall crate's own documentation; the old
|
||||
`call-protocol.md` relative link pointed into the alknet mono-repo
|
||||
spec tree).
|
||||
|
||||
## Port notes
|
||||
|
||||
- **Superseded in part, per the status amendment above:** the "deferred"
|
||||
framing is superseded by alkhttp ADR-069 (WebTransport removed from
|
||||
alkhttp scope entirely). §1's WebSocket-as-browser-path stands; the
|
||||
reversal trigger, the "revival" mechanics, the `webtransport.md` spec
|
||||
pointer, and the research-spike-for-revival posture are alknet-record
|
||||
content retained for provenance and annotated. alknet ADR-038/040/043
|
||||
are not ported to alkhttp.
|
||||
- **Framing correction:** the original assumed "one `EventEnvelope` = one
|
||||
WS binary message." In the current wire model the WS message boundary
|
||||
carries 8-byte channels chunks (alkcall ADR-034/ADR-035); channel 0 is
|
||||
pre-negotiated `alk/call` (alkcall ADR-036) and carries `EventEnvelope`
|
||||
frames as length-prefixed JSON (alkcall ADR-014) inside the chunk
|
||||
payload. The original sentences asserting one-envelope-per-WS-message
|
||||
are annotated inline (Finding 1 correction, Assumption 1) rather than
|
||||
silently rewritten; the operative statement for this crate is alkhttp
|
||||
ADR-067.
|
||||
- Renames: "alknet-http" → "alkhttp"; the QUIC call ALPN "alknet/call" →
|
||||
"alk/call" (alkcall ADR-004 `alk/` convention); "alknet ADR-012" (the
|
||||
old call-protocol stream model) is cited as **alkcall ADR-015**
|
||||
(alkcall ADR numbering differs from alknet numbering — alkcall ADR-012
|
||||
is `ConnectionCredentials`, not the stream model).
|
||||
- The original's references to `crates/http/webtransport.md` and
|
||||
`crates/http/http-server.md` are alknet spec-tree artifacts; the
|
||||
`webtransport.md` spec does not exist in alkhttp (no deferred
|
||||
WebTransport spec is carried in this crate). The alkhttp equivalent of
|
||||
the http-server spec work is the `websocket`/`server` subsystem of
|
||||
this crate and alkhttp ADR-048.
|
||||
- The `@alkdev/pubsub` prior-art references are kept as absolute
|
||||
workspace paths (the packages still exist at those locations); the
|
||||
framing delta note is annotated per the channels-chunk correction.
|
||||
- The original table in §4 had an implied `h3` row (WebTransport →
|
||||
alk/http in the alknet ALPN table); that row is dropped from the
|
||||
alkhttp transport table per alkhttp ADR-069, with a note in §4.
|
||||
- iroh-relay precedent: kept — it is a design-signal argument, not a
|
||||
dependency claim; alkhttp has no iroh dependency.
|
||||
- Original title preserved: "Defer h3/WebTransport; Browsers Use
|
||||
WebSocket". The title's "defer" is the alknet-record posture; in
|
||||
alkhttp the operative reading is "no WebTransport in this crate;
|
||||
browsers use WebSocket" (alkhttp ADR-069).
|
||||
@@ -197,35 +197,3 @@ published contract and the one this ADR governs.
|
||||
- `http-adapters.md` (in this crate's `docs/architecture/`) — the spec
|
||||
that emits `info.version` (alknet original:
|
||||
`crates/http/http-adapters.md`)
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet" → alkhttp where the system/node being described is
|
||||
meant (including the original's "no alknet-specific detection
|
||||
mechanism", "no `x-alknet-version` extension", "an alknet-specific
|
||||
mechanism" — now alkhttp-specific / `x-alkhttp-version` semantics);
|
||||
the protocol-foundation crate is alkcall.
|
||||
- **Gateway endpoint count / version bump**: the original decision text
|
||||
(5 endpoints) is preserved verbatim as history; alkhttp's `/publish`
|
||||
addition (ADR-068) is noted as an explicit alkhttp note in Decision §1
|
||||
and in the References — per this ADR's own rules it is an additive
|
||||
minor bump of the gateway contract; the versioning rationale is
|
||||
unchanged.
|
||||
- Producer/consumer terminology: the original body contained no
|
||||
call-protocol server/client role framing; the client roles mentioned
|
||||
(clients built against the doc, clients caching schemas) are
|
||||
HTTP/OpenAPI inherent-directionality roles and keep their names.
|
||||
- Cross-reference remappings (verified alknet→alkcall ADR mapping):
|
||||
alknet ADR-009 (one-way door framework) → alkcall ADR-032, cited
|
||||
textually. ADR-017/023 are ported to this crate under the same
|
||||
numbers and linked; their alkcall record numbers are noted alongside
|
||||
(017→alkcall ADR-022, 023→alkcall ADR-016).
|
||||
- OQ-39 is an alknet OQ-tracker item, cited textually; it is resolved by
|
||||
this ADR, which this crate ports under the same number.
|
||||
- Spec-document links converted per alkhttp conventions:
|
||||
`crates/http/http-adapters.md` → `http-adapters.md` in this crate's
|
||||
`docs/architecture/`.
|
||||
- No decision content changed — the semver rules, bump-on-shape-change
|
||||
rule, major-version detection, the additive-projection carve-out,
|
||||
consequences, and assumptions are verbatim from the alknet ADR modulo
|
||||
the adaptations above.
|
||||
@@ -250,19 +250,3 @@ deployment-specific addition on top, not a modification of it.
|
||||
as a custom route projection; this ADR §4 is the mechanism)
|
||||
- `http-server.md` — the `HttpAdapter` spec that gains the
|
||||
`extra_routes` constructor parameter (now in `docs/architecture/`)
|
||||
|
||||
## Port notes
|
||||
|
||||
- Link path fixes: `crates/http/http-server.md` → `http-server.md` in
|
||||
`docs/architecture/` (referenced both in the Context and in the
|
||||
References). Cross-ADR links (ADR-010, ADR-042, ADR-045, ADR-047)
|
||||
point at `decisions/NNN-<slug>.md` with the alknet slugs; ADR-042,
|
||||
ADR-045, and ADR-047 are being ported by other agents under the same
|
||||
numbers and will exist. ADR-036 is referenced by number/text only
|
||||
(its mapping decision is superseded by ADR-047).
|
||||
- `resolve_from_token` on a Bearer header is the `IdentityProvider`
|
||||
path defined in ADR-004 (ported here under the same number);
|
||||
`IdentityProvider` internals are alkcall-internal.
|
||||
- No decision content changed — the `extra_routes` mechanism, reserved
|
||||
paths, auth middleware, and versioning rules are verbatim from the
|
||||
alknet ADR.
|
||||
@@ -306,46 +306,3 @@ to `to_openapi` or its versioning.
|
||||
unchanged; it moves from the HTTP path to the `/call` request body
|
||||
- `docs/architecture/http-server.md` — this crate's spec whose router
|
||||
surface this ADR narrows to the gateway endpoints
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet-http" → alkhttp throughout (crate name, shipped-vs-
|
||||
specced status, threat-model and default-contract references).
|
||||
- **Gateway endpoint count (extension note, never silently rewritten):**
|
||||
the original decision text says "the 5 fixed gateway endpoints"
|
||||
(`/search`, `/schema`, `/call`, `/batch`, `/subscribe`) throughout.
|
||||
In alkhttp, alkcall ADR-046 (`Pub` operation type) motivated a sixth
|
||||
gateway endpoint, `/publish` (producer→consumer streaming; newline-
|
||||
delimited JSON request body; final `ResponseEnvelope` as the HTTP
|
||||
response), recorded in this crate's ADR-068. A block-quote extension
|
||||
note at the top of §Decision states the 6-endpoint contract and marks
|
||||
the "5 endpoints" phrasing below it as retained decision history;
|
||||
the original text is otherwise verbatim. The stealth-decoy clause §3
|
||||
retains the original 5-path enumeration for the same reason.
|
||||
- **`Subscription` → `Sub`** (§2's method-mapping sentence; alkcall
|
||||
rename of the `OperationType` value).
|
||||
- **Producer/consumer terminology**: §3 abort-cascade bullet "An HTTP
|
||||
client disconnecting mid-`/subscribe`" → "An HTTP consumer
|
||||
disconnecting mid-`/subscribe`" (call-protocol role framing; the
|
||||
abort-cascade citation is alkcall ADR-020). HTTP server/client
|
||||
phrasing elsewhere is HTTP-inherent and untouched (`HttpAdapter`
|
||||
serves; HTTP clients call).
|
||||
- **Cross-refs**: `../../decisions/...` → `decisions/...`. ADR-036 and
|
||||
ADR-042 are ported to this crate under their alknet numbers/slugs and
|
||||
linked (`decisions/036-http-to-call-operation-mapping.md`,
|
||||
`decisions/042-openapi-gateway-pattern.md`); ADR-044/045/046 are
|
||||
ported under the same numbers and linked directly. The source's
|
||||
alknet ADR-023 (error schemas) citation is remapped textually to the
|
||||
alkcall crate's ADR-016 (`decisions/016-operation-error-schemas.md`);
|
||||
the alknet ADR-016 (abort cascade) citation is remapped to alkcall
|
||||
ADR-020. The new-ADR link `decisions/068-gateway-publish-endpoint.md`
|
||||
uses the alkhttp slug for ADR-068 (the alknet ADR-068 is an unrelated
|
||||
hub decision and is not referenced here).
|
||||
- `crates/http/http-server.md` → `docs/architecture/http-server.md`
|
||||
(this crate's docs/architecture/).
|
||||
- No frontmatter in the source; status kept as Proposed.
|
||||
- No decision content changed — the sole-invoke-path decision, the
|
||||
method-semantics move, the "what survives" list, the custom-routes
|
||||
escape hatch, the `to_openapi` unchanged clause, and the
|
||||
consequences/assumptions are verbatim from the alknet ADR modulo the
|
||||
renames and the logged six-endpoint extension note.
|
||||
@@ -361,48 +361,3 @@ transports exist.
|
||||
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.
|
||||
|
||||
## Port notes
|
||||
|
||||
- **Amended by alkhttp ADR-067, per the status amendment above:** the WS
|
||||
session now carries the **channels protocol** (8-byte chunk
|
||||
multiplexing, alkcall ADR-034/035) with **channel 0 pre-negotiated as
|
||||
`alk/call`** (alkcall ADR-036) instead of a bare `EventEnvelope` WS
|
||||
message stream. The dispatch/overlay/browsers-not-peers content of
|
||||
this ADR stands unchanged and applies to channel 0. The framing on
|
||||
channel 0 is **length-prefixed JSON** (alkcall ADR-014) inside the
|
||||
chunk payload — **not one-envelope-per-WS-message**; the WS message
|
||||
boundary carries chunks, not envelopes (alkhttp ADR-067 §framing).
|
||||
Original one-envelope-per-message sentences are retained as decision
|
||||
history with inline annotations (Decision 1, §Context table,
|
||||
Assumptions 1).
|
||||
- **Upgrade path rename:** the default WS upgrade path is `/alk/channels`
|
||||
(was `/alknet/call`) — renamed per the alkcall ADR-004 `alk/` ALPN
|
||||
convention and per alkhttp ADR-067 (the path carries the channels
|
||||
session). The original statement "/alknet/call" appears nowhere in the
|
||||
ported body except as this note.
|
||||
- Renames: "alknet-http" → "alkhttp"; `CallAdapter` (old name in the
|
||||
dispatch-loop sentence) is the call-protocol adapter now vendored in
|
||||
the alkcall crate; "alknet ADR-012" (stream model) → **alkcall
|
||||
ADR-015**; "alknet ADR-017 §5" (`to_*` projection) → **alkcall ADR-022
|
||||
§5**; "alknet ADR-024" (registry layering) → **alkcall ADR-019**.
|
||||
alkcall ADR numbers differ from alknet ADR numbers; each citation
|
||||
names the owning crate.
|
||||
- alknet ADR-036 (SSE mapping) and alknet ADR-049 (streaming handler)
|
||||
correspond to **alkhttp ADR-049** in this crate; the SSE `/subscribe`
|
||||
references are re-cited accordingly.
|
||||
- alknet ADR-043 is cited as an alknet record (not ported to alkhttp);
|
||||
its §2/§3 transfers are restated with alkcall ADR-019 as the overlay
|
||||
citation.
|
||||
- Links rewritten per alkhttp conventions: `../crates/http/...` and
|
||||
`../crates/call/...` relative links became textual "alkhttp server
|
||||
spec" / "alkcall crate docs" references (the original
|
||||
`http-server.md` §"WebSocket browser path" and `websocket.md` spec
|
||||
pointers describe alknet spec-tree documents; in this crate the
|
||||
equivalent content lives in the `server`/`websocket` subsystem specs
|
||||
to be written under `docs/architecture/`).
|
||||
- `OperationRegistry::invoke()`, `Dispatcher`, `CallConnection`,
|
||||
`PendingRequestMap`, `EventEnvelope` are alkcall-owned types (see
|
||||
alkcall ADR-014/015/019/022); cited textually, not re-defined here.
|
||||
- Original title preserved: "WebSocket Carries the Native Call-Protocol
|
||||
Session, Not the Gateway Shape".
|
||||
@@ -370,53 +370,3 @@ implementation detail within the one-way decision.
|
||||
live in this crate's `docs/architecture/`): `operation-registry.md`,
|
||||
`call-protocol.md`, `http-server.md`, `http-adapters.md`, `http-mcp.md`,
|
||||
`client-and-adapters.md`
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet-http" → alkhttp; "alknet-core"/"alknet-call" → alkcall.
|
||||
- **`OperationType::Subscription` → `Sub`** throughout (alkcall rename;
|
||||
`OperationSpec.op_type` value and TS-mapping references). The TS prior-art
|
||||
identifiers (`SUBSCRIPTION`, `SubscriptionHandler`) are left verbatim —
|
||||
they are the TypeScript package's names, not the Rust enum's.
|
||||
- **Producer/consumer terminology**: "the **server side** does not" → "the
|
||||
**producer side** did not (in the original design)"; "The **client side**
|
||||
works — a client can subscribe" → "The **consumer side** works — a
|
||||
consumer can subscribe"; "server-side handler → server-side dispatch" →
|
||||
"producer-side handler → producer-side dispatch"; "the client-side
|
||||
`CallConnection::subscribe()`" → "the consumer-side"; §4 "client-side
|
||||
programming error" → "caller-side programming error"; §6 heading
|
||||
"Server-side dispatch branches on `op_type`" → "Dispatch branches on
|
||||
`op_type`". HTTP/TS inherent directionality untouched (`buildCallHandler`
|
||||
is TS prior-art code; `client.ts` citations keep their names).
|
||||
- **`Pub`/`Sink` note (alkcall ADR-046)**: the original defined two
|
||||
`HandlerKind` variants (`Once`, `Stream`) — annotated. The §2 code block
|
||||
now shows the three-variant enum with a block-quote annotation marking the
|
||||
`Sink` variant as the alkcall ADR-046 addition (decision history
|
||||
retained); §2's validation sentence and §6's dispatch branch table add the
|
||||
`Pub` → `invoke_sink()` row with the alkcall ADR-046 citation; the Door
|
||||
type section's one-way enum-shape sentence updated to acknowledge the
|
||||
third variant. `from_openapi` produces no `Pub` ops in v1 (SSE detection
|
||||
yields `Sub`), so §7–§9 need no change. The original two-variant decision
|
||||
text is otherwise retained verbatim as decision history.
|
||||
- §7 title: "(alknet-http)" → "(alkhttp)". The `/subscribe` endpoint and
|
||||
`GatewayDispatch::invoke_streaming()` are this crate's gateway surface
|
||||
(ADR-042/ADR-047); the Context's first downstream bullet names
|
||||
`GatewayDispatch::invoke()` → `subscribe_handler`, which is the alkhttp
|
||||
gateway dispatch spine.
|
||||
- Cross-reference remappings (verified alknet→alkcall ADR mapping): alknet
|
||||
ADR-012 (stream model) → alkcall ADR-015; alknet ADR-016 (abort cascade) →
|
||||
alkcall ADR-020; alknet ADR-022 (handler registration) → alkcall ADR-018;
|
||||
alknet ADR-017 (adapter contract) → alkcall ADR-022; alknet ADR-015
|
||||
(privilege model) → alkcall ADR-017; alknet ADR-029 (peer-graph routing) →
|
||||
alkcall ADR-024; alknet ADR-009 (one-way door framework) → alkcall ADR-032.
|
||||
ADR-015/017/022/023 are ported to this crate under the same numbers and
|
||||
linked; their alkcall record numbers are noted alongside.
|
||||
- The amended-spec list at the end of References is annotated: those spec
|
||||
documents live in the alknet mono-repo spec tree (and the alkcall crate's
|
||||
docs for the call-side ones); the alkhttp equivalents are this crate's
|
||||
`docs/architecture/http-server.md` and `http-adapters.md`.
|
||||
- No decision content changed — the `StreamingHandler` type, the
|
||||
`HandlerKind` enum, `invoke_streaming()`, the `INVALID_OPERATION_TYPE` code,
|
||||
the `OperationEnv` request/response-only boundary, the dispatch branch, and
|
||||
the gateway/from_call/from_openapi streaming paths are verbatim from the
|
||||
alknet ADR modulo the corrections logged above.
|
||||
@@ -221,21 +221,3 @@ additive (a new endpoint, no breaking change to the JSON path). The
|
||||
`docs/architecture/`)
|
||||
- `yaml_serde` crate (https://github.com/yaml/yaml-serde) — the maintained
|
||||
official-YAML-org fork of the deprecated `serde_yaml`
|
||||
|
||||
## Port notes
|
||||
|
||||
- Renames: "alknet-http" → alkhttp throughout (§3's "the dependency is not
|
||||
feature-gated" paragraphs, Consequences, §3's "alknet-http uses the direct
|
||||
form" → "alkhttp uses the direct form").
|
||||
- Link path fixes: `../crates/http/http-adapters.md` → `../http-adapters.md`
|
||||
(the spec now lives in this crate's `docs/architecture/`). Cross-ADR links
|
||||
(ADR-017, ADR-023, ADR-039) point at `decisions/NNN-<slug>.md` with the
|
||||
alknet slugs; ADR-017 and ADR-023 are ported here under the same numbers
|
||||
(their decision records live in alkcall as ADR-022 and ADR-016
|
||||
respectively — noted inline).
|
||||
- No producer/consumer or `Sub`/`Pub` terminology appears in the original —
|
||||
nothing to retarget on those axes.
|
||||
- No decision content changed — the constructor set, the JSON-first
|
||||
YAML-fallback rule, the `yaml_serde` dependency choice and its
|
||||
non-feature-gating, the 2026-07-06 rationale amendment, and the
|
||||
`to_openapi`-stays-JSON scope boundary are verbatim from the alknet ADR.
|
||||
@@ -195,46 +195,3 @@ endpoint.
|
||||
- alkcall ADR-027 — the decision record in the call crate (`from_jsonschema`
|
||||
as an HTTP-backed adapter; `FromJsonSchema` provenance is a
|
||||
handler-bearing leaf in alkcall's `OperationProvenance`).
|
||||
|
||||
## Port notes
|
||||
|
||||
- **History accuracy (kept per the porting instruction):** the move this
|
||||
ADR records happened in two hops. Originally the adapter was specified in
|
||||
the call crate (`alknet-call`) as a schema-only placeholder; alknet
|
||||
ADR-066 moved it to `alknet-http` (the HTTP crate of the alknet
|
||||
mono-repo); with the crate extraction, `alknet-http` is now **alkhttp**.
|
||||
The title, §Decision 1, the location table, and the Consequences now
|
||||
name alkhttp as the home. The original title said "in alknet-http"; the
|
||||
ported title says "in alkhttp" — same crate, current name.
|
||||
- **Provenance location (per alkcall ADR-027):** `FromJsonSchema` provenance
|
||||
lives in alkcall (the call crate's `OperationProvenance` enum) — the
|
||||
provenance is a handler-bearing leaf in alkcall; only the adapter
|
||||
implementation lives in alkhttp. The original already said this; the port
|
||||
names the current crates and adds the alkcall ADR-027 citation to the
|
||||
References.
|
||||
- Renames: "alknet-http" → alkhttp; "alknet-call" → "the call crate
|
||||
(alkcall)"; the source-file path `crates/alknet-http/src/adapters/from_jsonschema.rs`
|
||||
→ `src/adapters/from_jsonschema.rs` (this crate's layout); "the HTTP
|
||||
crate" phrasing retained where the original used it generically.
|
||||
- `Subscription` → `Sub` (alkcall rename; alkcall ADR-046 added
|
||||
`OperationType::Pub` — producer→consumer streaming via `call.published`,
|
||||
`HandlerKind::Sink` — which does not affect this adapter: `from_jsonschema`
|
||||
detects SSE responses as `Sub` and registers `HandlerKind::Stream`, same
|
||||
as `from_openapi`).
|
||||
- Cross-reference remappings (verified alknet→alkcall ADR mapping): alknet
|
||||
ADR-017 (adapter contract) → alkcall ADR-022; alknet ADR-022 (handler
|
||||
registration) → alkcall ADR-018; alknet ADR-023 (error schemas) → alkcall
|
||||
ADR-016; alknet ADR-049 (streaming handler) → alkcall ADR-021; alknet
|
||||
ADR-014 (secret material flow) → alkcall ADR-010. ADR-014/017/022/023/049
|
||||
are ported to this crate under the same numbers and linked.
|
||||
- The `client-and-adapters.md` references became textual "the call crate's
|
||||
`client-and-adapters.md`" references with the alknet mono-repo path noted
|
||||
(the document lives in alkcall's docs/architecture/).
|
||||
- Status line: the superseded clauses (ADR-017 §5, ADR-022's
|
||||
`FromJsonSchema` row) are cited as this crate's ported ADRs — the
|
||||
supersession text is unchanged from the alknet original.
|
||||
- No decision content changed — the real-forwarding-handler requirement, the
|
||||
single-endpoint registration shape, the provenance location, the removal of
|
||||
the schema-only concept, the comparison table, and the
|
||||
neutral/positive/negative consequences are verbatim from the alknet ADR
|
||||
modulo the corrections logged above.
|
||||
@@ -645,67 +645,3 @@ See [open-questions.md](open-questions.md) for full details.
|
||||
- `melotic/reqwest-retry-after`
|
||||
(https://github.com/melotic/reqwest-retry-after) — `RetryAfterMiddleware`
|
||||
source (MIT, inlined, not a dependency)
|
||||
|
||||
## Port notes
|
||||
|
||||
Corrections applied during the alknet → alkhttp port, beyond mechanical
|
||||
crate renames (`alknet-http` → `alkhttp`; `alknet-call`/`alknet-core` →
|
||||
`alkcall`, with the adapter contract in `alkcall::client`, registry
|
||||
types in `alkcall::registry`, core in `alkcall::core`):
|
||||
|
||||
1. **Gateway is now 6 endpoints.** `/publish` (POST, Pub operations;
|
||||
request body streamed as newline-delimited JSON, each line one
|
||||
published chunk) added per alkhttp ADR-068
|
||||
(`decisions/068-gateway-publish-endpoint.md` — slug assumed; the ADR
|
||||
file does not exist yet). All "5 fixed endpoints" tables/phrasing
|
||||
updated to 6; the MCP-gateway exclusion now covers both `/subscribe`
|
||||
and `/publish` (the "one endpoint the MCP gateway excludes" claim
|
||||
from the source extended accordingly).
|
||||
2. **`OperationType::Subscription` renamed `Sub`** (alkcall rename);
|
||||
`OperationType::Pub` exists (producer→consumer streaming,
|
||||
`HandlerKind::Sink`, wire event `call.published` — alkcall ADR-046).
|
||||
The Query/Mutation/Sub detection mapping from OpenAPI is kept, with a
|
||||
one-line note that Pub ops are not produced by `from_openapi` (no
|
||||
OpenAPI representation in v1). The `/publish` gateway row is the only
|
||||
Pub surface in this doc.
|
||||
3. **ADR cross-reference remapping.** Call-protocol-internal decisions
|
||||
now live in the alkcall crate and are cited textually (no relative
|
||||
links across crates): old ADR-017 adapter contract → alkcall ADR-022;
|
||||
old ADR-022 handler registration → alkcall ADR-018 (§6 mapping
|
||||
preserved); old ADR-023 error schemas → alkcall ADR-016 (§5 mapping
|
||||
preserved); old ADR-015 privilege model → alkcall ADR-017; old
|
||||
ADR-049 streaming handler → alkcall ADR-021; Pub/`HandlerKind::Sink`
|
||||
→ alkcall ADR-046; `from_jsonschema` provenance → alkcall ADR-027.
|
||||
alkhttp-owned ADRs keep their numbers and are linked relatively:
|
||||
014, 036, 041, 042, 045, 047, 051, 066 (ADR-036's port to alkhttp is
|
||||
assumed — it is an HTTP-surface decision, consistent with the
|
||||
other gateway/server ADRs).
|
||||
4. **Old ADR-035 reference (hot-reload pattern) had no verified
|
||||
mapping.** The source cited alknet ADR-035 (Concrete Persistence
|
||||
Adapter Shapes) for the `ConfigIdentityProvider` `ArcSwap`
|
||||
rebuild-and-swap pattern; that ADR has no direct alkcall/alkhttp
|
||||
counterpart. Replaced with a textual pointer to the alkcall crate's
|
||||
ADR-006 (AuthContext Structure — documents the
|
||||
`ArcSwap<DynamicConfig>` reload) and ADR-025 (PeerEntry and
|
||||
Identity.id Decoupling — introduces `ConfigIdentityProvider`).
|
||||
Flagged as an inference, not a verified mapping.
|
||||
5. **Producer/consumer terminology.** Remaining "server"/"client"
|
||||
wording is deliberately retained where OpenAPI's inherent
|
||||
client/server directionality is meant (that directionality is a
|
||||
property of the OpenAPI protocol, not the call protocol) and for the
|
||||
HTTP server / external HTTP API server (transport roles, not
|
||||
call-protocol roles). No call-protocol server/client framing remains.
|
||||
6. **Links.** `../../decisions/` → `decisions/`;
|
||||
`../../open-questions.md` → `open-questions.md` (sibling, pending
|
||||
port); `../call/client-and-adapters.md` → textual reference to the
|
||||
alkcall crate's `docs/architecture/client-and-adapters.md`; the
|
||||
mono-repo research findings path is kept as a textual absolute path.
|
||||
`overview.md` and `http-mcp.md` are sibling links to docs pending
|
||||
port.
|
||||
7. **WebTransport/h3.** The source document contained no h3/WebTransport
|
||||
references, so no "out of scope in alkhttp (ADR-069)" replacements
|
||||
were needed.
|
||||
8. **Project naming.** "how alknet composes/discover the alknet
|
||||
operation surface" → "the alk stack" in the Why section prose.
|
||||
Everything else (decision content, structure, section order,
|
||||
rationale) is preserved verbatim from the source.
|
||||
@@ -415,67 +415,3 @@ See [open-questions.md](open-questions.md) for full details.
|
||||
- `/workspace/@alkdev/alknet/docs/research/alknet-http-gateway-factoring/findings.md`
|
||||
— research on the shared dispatch spine between `to_mcp` and
|
||||
`to_openapi` (recommendation: thin shared struct, not a trait)
|
||||
|
||||
## Port notes
|
||||
|
||||
Corrections applied during the alknet → alkhttp port, beyond mechanical
|
||||
crate renames (`alknet-http` → `alkhttp`; `alknet-call`/`alknet-core` →
|
||||
`alkcall`, with the adapter contract in `alkcall::client`, registry
|
||||
types in `alkcall::registry`, core in `alkcall::core`):
|
||||
|
||||
1. **`OperationType::Subscription` renamed `Sub`** (alkcall rename);
|
||||
`OperationType::Pub` exists (producer→consumer streaming,
|
||||
`HandlerKind::Sink`, wire event `call.published` — alkcall ADR-046).
|
||||
The MCP gateway exclusion was a "Subscription excluded" claim in the
|
||||
source; extended to cover both `Sub` (streaming responses) and `Pub`
|
||||
(streaming requests) — MCP tool calls are request/response, so
|
||||
neither fits. The section is retitled "`Sub` and `Pub` exclusion".
|
||||
`to_mcp` stays 4 fixed tools.
|
||||
2. **The OpenAPI gateway now has 6 endpoints.** `/publish` (Pub, NDJSON,
|
||||
alkhttp ADR-068) joined the original five. The shared-dispatch-spine
|
||||
cross-reference updated accordingly ("streaming (excluded in
|
||||
`to_mcp` vs `to_openapi`'s `/subscribe` SSE and `/publish`
|
||||
NDJSON)"; the `/subscribe` mention in the Sub-exclusion section now
|
||||
cites `/publish` too). `to_mcp` has no `/publish` analog — the 4
|
||||
tool set is unchanged.
|
||||
3. **ADR cross-reference remapping.** Call-protocol-internal decisions
|
||||
now live in the alkcall crate and are cited textually (no relative
|
||||
links across crates): old ADR-017 adapter contract → alkcall ADR-022
|
||||
(§5 mapping preserved); old ADR-022 handler registration → alkcall
|
||||
ADR-018; old ADR-023 error schemas → alkcall ADR-016; old ADR-015
|
||||
privilege model → alkcall ADR-017; old ADR-049 streaming handler →
|
||||
alkcall ADR-021 (the `GatewayDispatch` spine clause is alkcall ADR-021
|
||||
§7); Pub/`HandlerKind::Sink` → alkcall ADR-046. The old ADR-029 §5
|
||||
citation for `FromCallConfig::namespace_prefix` (peer-graph collision
|
||||
rule) → alkcall ADR-024 §5 (the alkcall peer-graph routing model;
|
||||
alkcall's ADR-029 is a different, later decision — aggregated peer-env
|
||||
wiring).
|
||||
4. **alkhttp-owned ADRs keep their numbers and are linked relatively:**
|
||||
014, 034, 004 (ported files exist); 037, 041 (being ported in
|
||||
parallel — linked per instruction); 036, 047, 068 (linked by
|
||||
inference — their ports are assumed, consistent with
|
||||
http-adapters.md/http-server.md practice). The old ADR-036 "same
|
||||
dispatch path the HTTP server uses" citation was extended with
|
||||
ADR-047 (the gateway is the sole invoke path; ADR-047 supersedes
|
||||
ADR-036's direct-call surface) so the reference reflects the current
|
||||
architecture.
|
||||
5. **Terminology.** "MCP clients are not alknet peers" → "not alk
|
||||
peers"; "outside alknet" → "outside alkhttp"; "alknet's
|
||||
auth/identity/capabilities machinery" → "alkhttp's". No call-protocol
|
||||
server/client framing existed in the source; the remaining
|
||||
server/client wording (rmcp streamable HTTP server/client, axum
|
||||
server, remote MCP server, stdio-only MCP server) is MCP's inherent
|
||||
transport directionality and is deliberately retained.
|
||||
6. **Project naming.** "lets alknet compose external MCP servers" →
|
||||
"lets the alk stack compose"; "calls alknet operations" → "the local
|
||||
deployment's operations" in the Why prose.
|
||||
7. **Links.** `../../decisions/` → `decisions/`;
|
||||
`../../open-questions.md` → `open-questions.md` (sibling, pending
|
||||
port); `../call/client-and-adapters.md` → textual reference to the
|
||||
alkcall crate's `docs/architecture/client-and-adapters.md`; the
|
||||
mono-repo research findings path is kept as a textual absolute path
|
||||
(the findings doc was not ported). `overview.md` and
|
||||
`http-adapters.md` are sibling links (overview.md pending port).
|
||||
OQ-14/OQ-40 references kept as-is (OQ-14 is a call-protocol OQ — the
|
||||
alkcall crate's `docs/architecture/open-questions.md`; OQ-40 is the
|
||||
shared reqwest client config, resolved).
|
||||
@@ -547,66 +547,3 @@ for full details.
|
||||
- the alkcall crate's `docs/architecture/operation-registry.md` —
|
||||
`OperationRegistry::invoke()` / `invoke_streaming()` / `invoke_sink()`,
|
||||
the dispatch path HTTP requests hit
|
||||
|
||||
## Port notes
|
||||
|
||||
Corrections applied while porting from
|
||||
`alknet/docs/architecture/crates/http/http-server.md` (temporary
|
||||
scaffolding for review; remove once the port is accepted):
|
||||
|
||||
- Crate names: `alknet-http` → alkhttp; `alknet-core`/`alknet-call`
|
||||
types → alkcall (`alkcall::core::auth`, `alkcall::core::types`,
|
||||
`alkcall::registry`, `alkcall::protocol`, adapter contract in
|
||||
`alkcall::client`). The alkcall dependency is `alkcall` 0.1.1
|
||||
(old alknet-core and alknet-call merged into it).
|
||||
- Transport: "QUIC bidirectional stream" / `(SendStream, RecvStream)`
|
||||
phrasing replaced with the joined `BiStream` (`AsyncRead +
|
||||
AsyncWrite`) returned by `Connection::accept_bi()`; handlers wrap it
|
||||
with `TokioIo`. Single-stream vs multi-stream notes kept (true in
|
||||
alkcall: `Connection::from_bidi` yield-once, alkcall ADR-007).
|
||||
- Terminology: producer/consumer for call-protocol roles (both sides
|
||||
can initiate); "server"/"client" kept only for HTTP's inherent
|
||||
directionality. "alknet" node/peer references → "alk" node/alk stack.
|
||||
- OperationType: `Subscription` → `Sub` (renamed in alkcall). Added
|
||||
`OperationType::Pub` / `HandlerKind::Sink` coverage: `/publish`
|
||||
endpoint (alkhttp ADR-068), `invoke_sink()` dispatch, and alkcall
|
||||
ADR-046 citations. Gateway count updated 5 → 6 everywhere (reserved
|
||||
paths, DecoyConfig doc comment, constraints, design table).
|
||||
- WebSocket: the WS path now carries the **channels protocol** (8-byte
|
||||
chunk multiplexing, alkcall ADR-034), channel 0 pre-negotiated as
|
||||
`alk/call` (alkcall ADR-036) with the shared `Dispatcher` on channel
|
||||
0 — not bare `EventEnvelope` binary messages. Upgrade path
|
||||
`/alknet/call` → `/alk/channels`. Bidirectionality and
|
||||
connection-local overlay statements retained. The `EventEnvelope`
|
||||
framing is still the channel-0 payload (alkcall ADR-014, amended by
|
||||
alkcall ADR-035). Cited alkhttp ADR-067 for the WS-carries-channels
|
||||
decision.
|
||||
- WebTransport/h3: removed from scope — "deferred per ADR-044" /
|
||||
webtransport.md references replaced with "out of scope in alkhttp
|
||||
(ADR-069)"; webtransport.md references deleted. The old ADR-034 §4 /
|
||||
ADR-044 §5 amendment chain on the "browsers are not peers" rationale
|
||||
is trimmed to ADR-034 §4 (the ADR-044 amendment covered the
|
||||
WebTransport revival framing, which is out of scope per ADR-069).
|
||||
The old ADR-040 ALPN-stream-proxy is
|
||||
cited as an alknet concern (not ported to alkhttp).
|
||||
- Links: `../../decisions/` → `decisions/`; links into the old
|
||||
mono-repo (`../call/...`, `../core/...`) converted to textual
|
||||
references to "the alkcall crate's docs/architecture/<doc>.md".
|
||||
ADR cross-refs to call-internal decisions re-mapped to alkcall ADRs:
|
||||
ADR-012→alkcall ADR-015 (stream model), ADR-016→alkcall ADR-020
|
||||
(abort cascade), ADR-022→alkcall ADR-018 (handler registration),
|
||||
ADR-023→alkcall ADR-016 (error schemas), ADR-024→alkcall ADR-019
|
||||
(registry layering), ADR-049→alkcall ADR-021 (streaming handler),
|
||||
ADR-004→alkcall ADR-003 (auth shared core), ADR-007→alkcall ADR-005
|
||||
(BiStream type), call wire format→alkcall ADR-014, Pub/Sink→alkcall
|
||||
ADR-046. alkhttp-local ADRs (010, 034, 036, 042, 045, 046, 047, 048)
|
||||
keep their numbers as `decisions/NNN-<slug>.md` links.
|
||||
- References section: removed the `/workspace/@alkdev/pubsub` TypeScript
|
||||
prior-art file paths (alknet-research artifact; the alkcall ADR-014
|
||||
prior-art note retains the lineage) and the old mono-repo
|
||||
`../core/auth.md`, `../core/endpoint.md`, `../call/...`,
|
||||
`open-questions.md` links (open-questions.md not yet ported to
|
||||
alkhttp; referenced textually). The ALPN prefix is `alk/` (alkcall
|
||||
ADR-004 amendment 1 shortened `alknet/` → `alk/`), so `alknet/ssh`,
|
||||
`alknet/call` → `alk/ssh`, `alk/call`, and the stealth-decoy "doesn't
|
||||
offer alknet ALPNs" phrasing → "alk ALPNs".
|
||||
@@ -285,12 +285,14 @@ invariant. See
|
||||
|
||||
See [open-questions.md](open-questions.md) for full details.
|
||||
|
||||
- **OQ-01** (open): WS message ↔ byte-stream adaptation — the channels
|
||||
demux consumes bytes; axum WS yields messages. The adapter's
|
||||
buffering/flush semantics are the core implementation risk.
|
||||
- **OQ-02** (open): `/publish` body framing details — NDJSON chunk
|
||||
shape, error position.
|
||||
- **OQ-03** (open): `from_wss` reconnection semantics.
|
||||
- **OQ-01** (resolved): WS message ↔ byte-stream adaptation — the
|
||||
production adapter (`src/websocket/byte_adapter.rs`) is validated in
|
||||
both directions (server upgrade path + `from_wss` client).
|
||||
- **OQ-02** (resolved): `/publish` body framing — first line
|
||||
`{operation, chunk}`; terminal errors as plain HTTP status + JSON
|
||||
body (ADR-068).
|
||||
- **OQ-03** (open): `from_wss` reconnection semantics — v1 = drop →
|
||||
retryable failures (ADR-070); policy deferred to the assembly layer.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -360,10 +360,11 @@ the adapter presents the token on its dial, sourced from
|
||||
|
||||
See [open-questions.md](open-questions.md) for full details.
|
||||
|
||||
- **OQ-01** (open): WS ↔ byte-stream adaptation — buffer bounds,
|
||||
write-side chunk-completeness assumption (verify against alkcall's
|
||||
`MuxRunner` atomic chunk writes), flush/close mapping. This is the
|
||||
implementation risk center for both this path and `from_wss`.
|
||||
- **OQ-01** (resolved): WS ↔ byte-stream adaptation — resolved by the
|
||||
production adapter (`byte_adapter.rs`): inbound bounded mpsc (64
|
||||
slots, backpressure), outbound chunk parser with 1 MiB message cap,
|
||||
flush no-op, close → EOF mapping; validated end-to-end in both
|
||||
directions (server upgrade path + `from_wss` client).
|
||||
- **OQ-04** (open): browser client library ownership — the JS/TS
|
||||
client speaking channels-over-WS (chunk framing, channel 0 open ops,
|
||||
envelope handling) is needed for browser consumers; it lives outside
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//! `scoped_env: None`, `Internal` by default — ADR-015/022). `Sub` op
|
||||
//! type → `HandlerKind::Stream` expecting `text/event-stream` (ADR-049).
|
||||
//!
|
||||
//! [ADR-066]: crate::docs
|
||||
//! [ADR-066]: https://docs.rs/alkhttp (docs/architecture/decisions)
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
//! default — ADR-015/022). Imported error codes are prefixed `HTTP_<status>`
|
||||
//! to avoid collision with the protocol-level codes (ADR-023).
|
||||
//!
|
||||
//! [ADR-051]: crate::docs
|
||||
//! [ADR-051]: https://docs.rs/alkhttp (docs/architecture/decisions)
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
//! handler calls fail on write; reconnect policy is the assembly layer's
|
||||
//! job.
|
||||
//!
|
||||
//! [ADR-070]: crate::docs
|
||||
//! [ADR-070]: https://docs.rs/alkhttp (docs/architecture/decisions)
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! HTTP APIs are prefixed `HTTP_<status>` and map to their declared
|
||||
//! status.
|
||||
//!
|
||||
//! [ADR-023]: crate::docs
|
||||
//! [ADR-023]: https://docs.rs/alkhttp (docs/architecture/decisions)
|
||||
|
||||
use alkcall::core::auth::Identity;
|
||||
use alkcall::protocol::wire::CallError;
|
||||
|
||||
@@ -155,6 +155,18 @@ fn build_router(state: RouterState, extra_routes: Option<Router>) -> Router {
|
||||
.merge(crate::gateway::routes::gateway_router())
|
||||
.route("/openapi.json", get(openapi_json_handler))
|
||||
.route("/healthz", get(healthz))
|
||||
// The WS upgrade route carries its own bearer middleware
|
||||
// (`ws_bearer_auth` — 401 without a resolvable token, because a WS
|
||||
// session without an identity cannot run AccessControl::check); the
|
||||
// shared bearer_auth_middleware only stashes a permissive
|
||||
// Option<Identity> for the gateway endpoints.
|
||||
.route(
|
||||
WS_UPGRADE_PATH,
|
||||
get(crate::websocket::ws_upgrade_handler).route_layer(from_fn_with_state(
|
||||
auth_state.clone(),
|
||||
crate::websocket::ws_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route_layer(from_fn_with_state(
|
||||
auth_state.clone(),
|
||||
bearer_auth_middleware,
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
//! `AccessControl` (the route handlers / `GatewayDispatch::invoke()` do)
|
||||
//! or map `CallError` codes to HTTP status (the error mapping does).
|
||||
//!
|
||||
//! [ADR-004]: crate::docs
|
||||
//! [ADR-004]: https://docs.rs/alkhttp (docs/architecture/decisions)
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::sync::Arc;
|
||||
|
||||
+3
-3
@@ -8,9 +8,9 @@
|
||||
//! decoy must not leak alk presence — no alk-specific headers, no alk
|
||||
//! error format.
|
||||
//!
|
||||
//! [ADR-010]: crate::docs
|
||||
//! [ADR-036]: crate::docs
|
||||
//! [ADR-046]: crate::docs
|
||||
//! [ADR-010]: https://docs.rs/alkhttp (docs/architecture/decisions)
|
||||
//! [ADR-036]: https://docs.rs/alkhttp (docs/architecture/decisions)
|
||||
//! [ADR-046]: https://docs.rs/alkhttp (docs/architecture/decisions)
|
||||
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: infra-integration-suite
|
||||
name: Full-surface integration suite + docs sync + publish prep
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [gateway-publish, adapter-to-openapi, adapter-from-wss, adapter-mcp, ws-overlay-ops]
|
||||
scope: moderate
|
||||
risk: medium
|
||||
@@ -24,11 +24,11 @@ Cargo.toml metadata polish.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `cargo test --all-features` green; clippy `-D warnings` green; fmt green
|
||||
- [ ] Full-surface integration test passing (gateway + WS + one adapter each direction)
|
||||
- [ ] Port notes stripped; OQ statuses updated
|
||||
- [ ] `cargo publish --dry-run --allow-dirty` succeeds
|
||||
- [ ] taskgraph shows all tasks completed
|
||||
- [x] `cargo test --all-features` green; clippy `-D warnings` green; fmt green
|
||||
- [x] Full-surface integration test passing (gateway + WS + one adapter each direction)
|
||||
- [x] Port notes stripped; OQ statuses updated
|
||||
- [x] `cargo publish --dry-run --allow-dirty` succeeds
|
||||
- [x] taskgraph shows all tasks completed
|
||||
|
||||
## References
|
||||
|
||||
@@ -37,8 +37,49 @@ Cargo.toml metadata polish.
|
||||
|
||||
## Notes
|
||||
|
||||
> Agent fills during implementation.
|
||||
The full-surface suite surfaced a real gap: the WS upgrade route was
|
||||
reserved but never wired into HttpAdapter's router (the ws-upgrade-session
|
||||
tests built their own axum server). Now wired in build_router with its
|
||||
own ws_bearer_auth layer (401 without a resolvable token — stricter than
|
||||
the shared bearer middleware, which permissively stashes Option<Identity>
|
||||
for the gateway endpoints).
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills on completion.
|
||||
Phase 4 hardening complete.
|
||||
|
||||
**Integration suite** (tests/full_surface.rs, mcp feature, 7 tests over
|
||||
real TCP with each accepted connection driven through
|
||||
ProtocolHandler::handle — the production path):
|
||||
- gateway 6 endpoints over HTTP: search (ACL-filtered), schema (GET +
|
||||
name query), call (round trip), batch, subscribe (SSE), publish
|
||||
surface presence; /openapi.json 6-endpoint projection validating
|
||||
against openapiv3; gateway error fidelity end-to-end (unknown op
|
||||
404 NOT_FOUND); unauthenticated calls follow the ACL model
|
||||
(unrestricted op → allowed, unknown op → NOT_FOUND)
|
||||
- WS session over the same HttpAdapter (upgrade → channels → echo
|
||||
round-trip on channel 0) — required wiring the WS upgrade route into
|
||||
HttpAdapter's router (it was reserved but only wired in tests):
|
||||
WS_UPGRADE_PATH now serves ws_upgrade_handler under ws_bearer_auth
|
||||
(401 without a resolvable token), while gateway endpoints keep the
|
||||
permissive shared bearer middleware
|
||||
- from_openapi import → Internal-by-default (invisible from the wire,
|
||||
ADR-015) → External facade composing it via env.invoke → the
|
||||
upstream HTTP API called end-to-end
|
||||
- to_openapi projection over /openapi.json (6 paths, openapiv3-valid)
|
||||
and to_mcp over /mcp: MCP client initialize + tools/list (4 gateway
|
||||
tools) + search excluding Sub ops — the tool-gateway pattern
|
||||
|
||||
**Docs sync**: all "## Port notes" sections stripped from the 28 ported
|
||||
ADRs/specs (inline port-note blockquotes rephrased as plain statements);
|
||||
OQ-01/OQ-02 statuses corrected to resolved across open-questions.md,
|
||||
overview.md, websocket.md, and the architecture README's OQ table.
|
||||
|
||||
**Publish prep**: cargo publish --dry-run --allow-dirty succeeds (90
|
||||
files); cargo doc --no-deps warning-free (crate::docs link targets
|
||||
resolved); no alknet references remain in src/; Cargo.toml metadata
|
||||
already complete (license, repository, keywords, categories, rust-version).
|
||||
|
||||
Verified: cargo test (182 lib default), --all-features (227 lib + 29
|
||||
integration across 4 suites), clippy -D warnings (default /
|
||||
test-support / all-features), fmt, doc, publish --dry-run.
|
||||
@@ -0,0 +1,583 @@
|
||||
//! Full-surface integration suite (phase 4): one in-process `HttpAdapter`
|
||||
//! serving the gateway endpoints, the `/openapi.json` projection, and the
|
||||
//! WS channels session over real I/O; `from_openapi` imported against a
|
||||
//! local HTTP echo server; `to_openapi`/`to_mcp` projections consumed
|
||||
//! back. Exercises the composition the assembly layer performs.
|
||||
|
||||
#![cfg(feature = "mcp")]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use alkcall::core::auth::{AuthContext, Identity, IdentityProvider};
|
||||
use alkcall::core::types::{Capabilities, Connection};
|
||||
use alkcall::protocol::wire::{EventEnvelope, ResponseEnvelope};
|
||||
use alkcall::registry::discovery::{
|
||||
services_list_handler, services_list_spec, services_schema_handler, services_schema_spec,
|
||||
};
|
||||
use alkcall::registry::registration::{
|
||||
make_handler, make_streaming_handler, HandlerKind, HandlerRegistration, OperationProvenance,
|
||||
OperationRegistry,
|
||||
};
|
||||
use alkcall::registry::spec::{AccessControl, OperationSpec, OperationType, Visibility};
|
||||
use alkhttp::adapters::FromOpenAPI;
|
||||
use alkhttp::client::HttpClientConfig;
|
||||
use alkhttp::server::HttpAdapter;
|
||||
use alkhttp::websocket::{frame_channel0_chunk, ChunkAssembler, FrameAssembler, WsClient};
|
||||
|
||||
fn identity(id: &str, scopes: &[&str]) -> Identity {
|
||||
Identity {
|
||||
id: id.to_string(),
|
||||
scopes: scopes.iter().map(|s| s.to_string()).collect(),
|
||||
resources: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
struct StaticTokens {
|
||||
tokens: std::sync::Mutex<HashMap<String, Identity>>,
|
||||
}
|
||||
|
||||
impl IdentityProvider for StaticTokens {
|
||||
fn resolve_from_fingerprint(&self, _: &str) -> Option<Identity> {
|
||||
None
|
||||
}
|
||||
fn resolve_from_token(&self, token: &alkcall::core::auth::AuthToken) -> Option<Identity> {
|
||||
let s = String::from_utf8_lossy(&token.raw).to_string();
|
||||
self.tokens
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.get(&s)
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
fn provider_with(tokens: Vec<(&str, Identity)>) -> Arc<dyn IdentityProvider> {
|
||||
let map: HashMap<String, Identity> = tokens
|
||||
.into_iter()
|
||||
.map(|(t, i)| (t.to_string(), i))
|
||||
.collect();
|
||||
Arc::new(StaticTokens {
|
||||
tokens: std::sync::Mutex::new(map),
|
||||
})
|
||||
}
|
||||
|
||||
/// The local operation registry the `HttpAdapter` serves: an echo op (open
|
||||
/// and echo-restricted variants), a streaming sub op, and the discovery
|
||||
/// ops the adapters need.
|
||||
fn local_registry() -> Arc<OperationRegistry> {
|
||||
let mut inner = OperationRegistry::new();
|
||||
inner
|
||||
.register(HandlerRegistration::new(
|
||||
OperationSpec::new(
|
||||
"echo/run",
|
||||
OperationType::Query,
|
||||
Visibility::External,
|
||||
serde_json::json!({}),
|
||||
serde_json::json!({}),
|
||||
vec![],
|
||||
AccessControl::default(),
|
||||
None,
|
||||
),
|
||||
HandlerKind::Once(make_handler(|input, ctx| async move {
|
||||
ResponseEnvelope::ok(ctx.request_id, input)
|
||||
})),
|
||||
OperationProvenance::Local,
|
||||
None,
|
||||
None,
|
||||
Capabilities::new(),
|
||||
))
|
||||
.unwrap();
|
||||
inner
|
||||
.register(HandlerRegistration::new(
|
||||
OperationSpec::new(
|
||||
"events/tick",
|
||||
OperationType::Sub,
|
||||
Visibility::External,
|
||||
serde_json::json!({}),
|
||||
serde_json::json!({}),
|
||||
vec![],
|
||||
AccessControl::default(),
|
||||
None,
|
||||
),
|
||||
HandlerKind::Stream(make_streaming_handler(|input, ctx| {
|
||||
futures::stream::iter(vec![
|
||||
ResponseEnvelope::ok(
|
||||
ctx.request_id.clone(),
|
||||
serde_json::json!({ "n": 1, "input": input }),
|
||||
),
|
||||
ResponseEnvelope::ok(
|
||||
ctx.request_id.clone(),
|
||||
serde_json::json!({ "n": 2, "input": input }),
|
||||
),
|
||||
])
|
||||
})),
|
||||
OperationProvenance::Local,
|
||||
None,
|
||||
None,
|
||||
Capabilities::new(),
|
||||
))
|
||||
.unwrap();
|
||||
let inner = Arc::new(inner);
|
||||
|
||||
let mut registry = OperationRegistry::new();
|
||||
registry
|
||||
.register(HandlerRegistration::new(
|
||||
services_list_spec(),
|
||||
HandlerKind::Once(services_list_handler(Arc::clone(&inner))),
|
||||
OperationProvenance::Local,
|
||||
None,
|
||||
None,
|
||||
Capabilities::new(),
|
||||
))
|
||||
.unwrap();
|
||||
registry
|
||||
.register(HandlerRegistration::new(
|
||||
services_schema_spec(),
|
||||
HandlerKind::Once(services_schema_handler(Arc::clone(&inner))),
|
||||
OperationProvenance::Local,
|
||||
None,
|
||||
None,
|
||||
Capabilities::new(),
|
||||
))
|
||||
.unwrap();
|
||||
for spec in inner.list_operations() {
|
||||
let name = spec.name.clone();
|
||||
let reg = inner.registration(&name).unwrap();
|
||||
registry
|
||||
.register(HandlerRegistration::new(
|
||||
reg.spec.clone(),
|
||||
reg.handler.clone(),
|
||||
reg.provenance,
|
||||
reg.composition_authority.clone(),
|
||||
reg.scoped_env.clone(),
|
||||
reg.capabilities.clone(),
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
Arc::new(registry)
|
||||
}
|
||||
|
||||
/// Serve the full adapter surface over a real TCP listener. Each accepted
|
||||
/// TCP connection is wrapped as an alkcall `Connection` (single-stream,
|
||||
/// `http/1.1` ALPN) and handed to the adapter's `ProtocolHandler::handle`
|
||||
/// — the same path a production endpoint drives. Returns the base URL.
|
||||
async fn spawn_full_server(
|
||||
registry: Arc<OperationRegistry>,
|
||||
provider: Arc<dyn IdentityProvider>,
|
||||
) -> String {
|
||||
let adapter = std::sync::Arc::new(HttpAdapter::new(Arc::clone(&provider), registry));
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let Ok((sock, _)) = listener.accept().await else {
|
||||
break;
|
||||
};
|
||||
let conn = Connection::from_bidi(sock, b"http/1.1".to_vec(), None);
|
||||
// The bearer middleware resolves the identity per request from
|
||||
// the Authorization header; the transport-level AuthContext
|
||||
// carries no identity (TLS-identity binding is the endpoint's
|
||||
// job, not this test's).
|
||||
let auth = AuthContext::anonymous(b"http/1.1");
|
||||
let a = std::sync::Arc::clone(&adapter);
|
||||
tokio::spawn(async move {
|
||||
let _ =
|
||||
alkcall::core::types::ProtocolHandler::handle(a.as_ref(), conn, &auth).await;
|
||||
});
|
||||
}
|
||||
});
|
||||
format!("http://{addr}")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn full_surface_gateway_over_http() {
|
||||
// A minimal registry with the discovery ops; the gateway endpoints
|
||||
// must serve search/schema/call/subscribe against it over HTTP.
|
||||
let registry = local_registry();
|
||||
let provider = provider_with(vec![("tok-1", identity("alice", &["user"]))]);
|
||||
let base = spawn_full_server(Arc::clone(®istry), Arc::clone(&provider)).await;
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
// /healthz — no auth.
|
||||
let resp = client.get(format!("{base}/healthz")).send().await.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
// /search — ACL-filtered discovery.
|
||||
let resp = client
|
||||
.get(format!("{base}/search"))
|
||||
.header("Authorization", "Bearer tok-1")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
let body: serde_json::Value = resp.json().await.unwrap();
|
||||
let names: Vec<&str> = body["output"]["operations"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter_map(|o| o["name"].as_str())
|
||||
.collect();
|
||||
assert!(names.contains(&"echo/run"), "got {names:?}");
|
||||
assert!(names.contains(&"events/tick"), "got {names:?}");
|
||||
|
||||
// /call — request/response round trip.
|
||||
let resp = client
|
||||
.post(format!("{base}/call"))
|
||||
.header("Authorization", "Bearer tok-1")
|
||||
.json(&serde_json::json!({ "operation": "echo/run", "input": { "v": 42 } }))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
let body: serde_json::Value = resp.json().await.unwrap();
|
||||
assert_eq!(body["output"]["v"], 42);
|
||||
|
||||
// /subscribe — SSE stream of the Sub op.
|
||||
let resp = client
|
||||
.post(format!("{base}/subscribe"))
|
||||
.header("Authorization", "Bearer tok-1")
|
||||
.json(&serde_json::json!({ "operation": "events/tick", "input": {} }))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
let text = resp.text().await.unwrap();
|
||||
assert!(text.contains("\"n\":1"), "first chunk in SSE: {text}");
|
||||
assert!(text.contains("\"n\":2"), "second chunk in SSE: {text}");
|
||||
|
||||
// /schema — the full spec (GET with a name query param).
|
||||
let resp = client
|
||||
.get(format!("{base}/schema?name=echo%2Frun"))
|
||||
.header("Authorization", "Bearer tok-1")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
let body: serde_json::Value = resp.json().await.unwrap();
|
||||
assert_eq!(body["output"]["name"], "echo/run");
|
||||
|
||||
// /openapi.json — the 6-endpoint projection including /publish.
|
||||
let resp = client
|
||||
.get(format!("{base}/openapi.json"))
|
||||
.header("Authorization", "Bearer tok-1")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
let body: serde_json::Value = resp.json().await.unwrap();
|
||||
assert_eq!(body["info"]["version"], "1.1.0");
|
||||
assert!(body["paths"].get("/publish").is_some());
|
||||
assert!(body["paths"].get("/call").is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn full_surface_ws_call_round_trip() {
|
||||
let registry = local_registry();
|
||||
let provider = provider_with(vec![("tok-1", identity("alice", &["user"]))]);
|
||||
let base = spawn_full_server(registry, provider).await;
|
||||
// WS endpoint rides the same TCP listener.
|
||||
let ws_base = base.replacen("http://", "ws://", 1);
|
||||
|
||||
let mut ws = WsClient::connect_authorized(&format!("{ws_base}/alk/channels"), "tok-1")
|
||||
.await
|
||||
.unwrap();
|
||||
let frame = EventEnvelope::requested(
|
||||
"ws-full-1",
|
||||
serde_json::json!({ "operationId": "echo/run", "input": { "v": 7 } }),
|
||||
);
|
||||
ws.send_binary(frame_channel0_chunk(&frame)).await;
|
||||
|
||||
let mut chunks = ChunkAssembler::new();
|
||||
let mut frames = FrameAssembler::new();
|
||||
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(5);
|
||||
loop {
|
||||
assert!(tokio::time::Instant::now() < deadline, "timed out");
|
||||
if let Some(env) = frames.next_frame() {
|
||||
assert_eq!(env.r#type, "call.responded");
|
||||
assert_eq!(env.id, "ws-full-1");
|
||||
assert_eq!(env.payload["output"]["v"], 7);
|
||||
break;
|
||||
}
|
||||
let bin = ws.next_binary(std::time::Duration::from_millis(500)).await;
|
||||
match bin {
|
||||
Some(bytes) => {
|
||||
chunks.push(&bytes);
|
||||
while let Some((channel_id, payload)) = chunks.next_chunk() {
|
||||
assert_eq!(channel_id, 0);
|
||||
frames.push(&payload);
|
||||
}
|
||||
}
|
||||
None => panic!("ws closed unexpectedly"),
|
||||
}
|
||||
}
|
||||
ws.close().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn from_openapi_import_then_gateway_call() {
|
||||
// A local HTTP service the adapter imports; the gateway dispatch then
|
||||
// reaches it through the imported forwarding handler.
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let upstream = format!("http://{addr}");
|
||||
tokio::spawn(async move {
|
||||
let app = axum::Router::new().route(
|
||||
"/widgets",
|
||||
axum::routing::get(|| async {
|
||||
axum::Json(serde_json::json!({ "widgets": ["a", "b"] }))
|
||||
}),
|
||||
);
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
});
|
||||
|
||||
let doc = r#"{"openapi":"3.0.0","info":{"title":"T","version":"1"},"paths":{"/widgets":{"get":{"operationId":"listWidgets","responses":{"200":{"content":{"application/json":{"schema":{}}}}}}}}}"#;
|
||||
let spec = alkhttp::adapters::OpenAPISpec::from_json(doc).unwrap();
|
||||
let config = alkhttp::adapters::HttpServiceConfig {
|
||||
namespace: "upstream".to_string(),
|
||||
base_url: upstream.clone(),
|
||||
auth: None,
|
||||
default_headers: HashMap::new(),
|
||||
};
|
||||
let http_client =
|
||||
Arc::new(alkhttp::client::SharedHttpClient::new(HttpClientConfig::default()).unwrap());
|
||||
let adapter = FromOpenAPI::new(spec, config, http_client);
|
||||
let bundles = alkcall::client::OperationAdapter::import(&adapter)
|
||||
.await
|
||||
.expect("import succeeds");
|
||||
assert_eq!(bundles.len(), 1);
|
||||
assert_eq!(bundles[0].spec.name, "upstream/listWidgets");
|
||||
|
||||
// The imported bundles are Internal (ADR-015 — composition material);
|
||||
// a wire call to them is NOT_FOUND (ADR-015 §2). The assembly layer
|
||||
// composes them under an External facade. Verify Internal-not-callable
|
||||
// through the gateway, then compose the External facade and call that.
|
||||
let mut registry = OperationRegistry::new();
|
||||
for b in bundles {
|
||||
registry.register(b).unwrap();
|
||||
}
|
||||
registry
|
||||
.register(HandlerRegistration::new(
|
||||
OperationSpec::new(
|
||||
"widgets/list",
|
||||
OperationType::Query,
|
||||
Visibility::External,
|
||||
serde_json::json!({}),
|
||||
serde_json::json!({}),
|
||||
vec![],
|
||||
AccessControl::default(),
|
||||
None,
|
||||
),
|
||||
HandlerKind::Once(make_handler(|_input, ctx| {
|
||||
// The facade composes the imported leaf (env.invoke —
|
||||
// composition-only per ADR-015). Its scoped_env declares
|
||||
// the imported leaf as the reachable set.
|
||||
async move {
|
||||
let response = ctx
|
||||
.env
|
||||
.invoke("upstream", "listWidgets", serde_json::json!({}), &ctx)
|
||||
.await;
|
||||
ResponseEnvelope {
|
||||
request_id: ctx.request_id,
|
||||
result: response.result,
|
||||
}
|
||||
}
|
||||
})),
|
||||
OperationProvenance::Local,
|
||||
None,
|
||||
Some(alkcall::registry::context::ScopedPeerEnv::new([
|
||||
"upstream/listWidgets",
|
||||
])),
|
||||
Capabilities::new(),
|
||||
))
|
||||
.unwrap();
|
||||
let provider = provider_with(vec![("tok-1", identity("alice", &[]))]);
|
||||
let base = spawn_full_server(Arc::new(registry), provider).await;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
// Internal op from the wire → NOT_FOUND (does not leak existence).
|
||||
let resp = client
|
||||
.post(format!("{base}/call"))
|
||||
.header("Authorization", "Bearer tok-1")
|
||||
.json(&serde_json::json!({ "operation": "upstream/listWidgets", "input": {} }))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
404,
|
||||
"Internal imported op is invisible from the wire"
|
||||
);
|
||||
|
||||
// The External facade composes it: external HTTP API → from_openapi
|
||||
// forwarding handler → upstream HTTP API.
|
||||
let resp = client
|
||||
.post(format!("{base}/call"))
|
||||
.header("Authorization", "Bearer tok-1")
|
||||
.json(&serde_json::json!({ "operation": "widgets/list", "input": {} }))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200, "facade composes the imported op");
|
||||
let body: serde_json::Value = resp.json().await.unwrap();
|
||||
assert_eq!(body["output"]["widgets"], serde_json::json!(["a", "b"]));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn to_openapi_and_to_mcp_projections_over_served_registry() {
|
||||
use rmcp::model::{CallToolRequestParams, ClientCapabilities, ClientInfo, Implementation};
|
||||
use rmcp::service::RoleClient;
|
||||
use rmcp::transport::streamable_http_client::{
|
||||
StreamableHttpClientTransport, StreamableHttpClientTransportConfig,
|
||||
};
|
||||
use rmcp::{Peer, ServiceExt};
|
||||
|
||||
let registry = local_registry();
|
||||
let provider = provider_with(vec![("tok-1", identity("alice", &["user"]))]);
|
||||
|
||||
// /openapi.json served from the same registry: the to_openapi
|
||||
// projection sees the local ops through services/list.
|
||||
let base = spawn_full_server(Arc::clone(®istry), Arc::clone(&provider)).await;
|
||||
let client = reqwest::Client::new();
|
||||
let resp = client
|
||||
.get(format!("{base}/openapi.json"))
|
||||
.header("Authorization", "Bearer tok-1")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 200);
|
||||
let doc: serde_json::Value = resp.json().await.unwrap();
|
||||
assert_eq!(doc["info"]["title"], "alk gateway");
|
||||
assert_eq!(doc["paths"].as_object().unwrap().len(), 6);
|
||||
// Validates against openapiv3 (ADR-042 contract).
|
||||
let text = serde_json::to_string(&doc).unwrap();
|
||||
let _: openapiv3::OpenAPI = serde_json::from_str(&text).unwrap();
|
||||
|
||||
// /mcp served by the same adapter: an MCP client connects, lists the
|
||||
// 4 gateway tools, calls search — the tool-gateway pattern (ADR-041).
|
||||
let url = format!("{base}/mcp");
|
||||
let transport = StreamableHttpClientTransport::from_config(
|
||||
StreamableHttpClientTransportConfig::with_uri(url),
|
||||
);
|
||||
let client_info = ClientInfo::new(
|
||||
ClientCapabilities::default(),
|
||||
Implementation::new("integration-test", "0.1.0"),
|
||||
);
|
||||
let running = client_info.serve(transport).await.expect("initialize");
|
||||
let peer: Peer<RoleClient> = running.peer().clone();
|
||||
|
||||
let tools = peer
|
||||
.list_tools(Default::default())
|
||||
.await
|
||||
.expect("tools/list");
|
||||
let names: Vec<String> = tools.tools.iter().map(|t| t.name.to_string()).collect();
|
||||
assert_eq!(names.len(), 4);
|
||||
assert!(names.contains(&"search".to_string()));
|
||||
assert!(names.contains(&"schema".to_string()));
|
||||
assert!(names.contains(&"call".to_string()));
|
||||
assert!(names.contains(&"batch".to_string()));
|
||||
|
||||
let mut args = serde_json::Map::new();
|
||||
args.insert("query".to_string(), serde_json::Value::Null);
|
||||
let params = CallToolRequestParams::new("search".to_string()).with_arguments(args);
|
||||
let result = peer.call_tool(params).await.expect("search call");
|
||||
assert_eq!(result.is_error, Some(false));
|
||||
let structured = result.structured_content.expect("structured present");
|
||||
let ops = structured
|
||||
.get("operations")
|
||||
.and_then(serde_json::Value::as_array)
|
||||
.expect("operations array");
|
||||
let names: Vec<&str> = ops
|
||||
.iter()
|
||||
.filter_map(|o| o.get("name").and_then(|v| v.as_str()))
|
||||
.collect();
|
||||
assert!(names.contains(&"echo/run"), "got {names:?}");
|
||||
assert!(
|
||||
!names.contains(&"events/tick"),
|
||||
"Sub ops excluded from search"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_error_fidelity_end_to_end() {
|
||||
// Unknown op through /call → 404 NOT_FOUND; internal op → 404.
|
||||
let registry = local_registry();
|
||||
let provider = provider_with(vec![("tok-1", identity("alice", &["user"]))]);
|
||||
let base = spawn_full_server(registry, provider).await;
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let resp = client
|
||||
.post(format!("{base}/call"))
|
||||
.header("Authorization", "Bearer tok-1")
|
||||
.json(&serde_json::json!({ "operation": "no/such", "input": {} }))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), 404);
|
||||
let body: serde_json::Value = resp.json().await.unwrap();
|
||||
assert_eq!(body["code"], "NOT_FOUND");
|
||||
}
|
||||
|
||||
use http::header::AUTHORIZATION;
|
||||
|
||||
#[tokio::test]
|
||||
async fn gateway_endpoints_exist_with_bearer_enforcement() {
|
||||
let registry = local_registry();
|
||||
let provider = provider_with(vec![("tok-1", identity("alice", &["user"]))]);
|
||||
let base = spawn_full_server(registry, provider).await;
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
// GET endpoints: /search, /schema (schema needs a known name).
|
||||
let resp = client
|
||||
.get(format!("{base}/search"))
|
||||
.header(AUTHORIZATION, "Bearer tok-1")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_ne!(
|
||||
resp.status(),
|
||||
404,
|
||||
"/search must exist on the gateway surface"
|
||||
);
|
||||
let resp = client
|
||||
.get(format!("{base}/schema?name=echo/run"))
|
||||
.header(AUTHORIZATION, "Bearer tok-1")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_ne!(
|
||||
resp.status(),
|
||||
404,
|
||||
"/schema must exist on the gateway surface"
|
||||
);
|
||||
for path in ["/call", "/batch", "/subscribe", "/publish"] {
|
||||
let resp = client
|
||||
.post(format!("{base}{path}"))
|
||||
.header(AUTHORIZATION, "Bearer tok-1")
|
||||
.header("Content-Type", "application/json")
|
||||
.body("{}")
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_ne!(
|
||||
resp.status(),
|
||||
404,
|
||||
"{path} must exist on the gateway surface"
|
||||
);
|
||||
}
|
||||
|
||||
// Unauthenticated call to an op with no restrictions: allowed
|
||||
// (AccessControl::default() passes for any identity, including none).
|
||||
let resp = client
|
||||
.post(format!("{base}/call"))
|
||||
.header("Content-Type", "application/json")
|
||||
.body(r#"{"operation": "no/such", "input": {}}"#)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
404,
|
||||
"unknown op is NOT_FOUND regardless of auth"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user