docs: re-key architecture docs off the alknet mono-repo — speak as alkcall

The ported docs still framed alknet siblings (alknet-core/-call/-channels/
-http/-tls/-client) as live load-bearing context of this crate. With the
extraction complete and alknet headed for glue-level replacement, the docs
now speak as alkcall and cite the real sibling crates (alkhttp, alktls,
alktty, alktunnels, alktrader).

- ADR-054 (new): re-own the verifier-selection rule in-crate — three
  remote roles (public X.509 endpoint / transport relay / hub),
  ServerCertVerifier by PeerEntry presence, RemoteIdentity None
  load-bearing. Provenance: alknet ADR-034 (never ported), demoted to
  footnote. Resolves OQ-21's cross-repo authority gap.
- OQ-43 (new, deferred(scope)): composition-layer ownership and shape
  after the alknet replacement — the named receiver for the
  downstream-side deferrals (OQ-33 endpoint layer, OQ-41 QUIC-native
  substrate, OQ-42 hub broker, OQ-19 persistence adapters, ADR-045 dial
  seam). README layering diagram redrawn with the composition layer as
  TBD-ownership instead of "alknet / alknode" in concrete.
- Spec docs reframed: titles, self-references, dial/TLS/from_jsonschema
  homes, malformed legacy cite rows removed; dead relative research
  paths qualified as source-repo paths.
- ADR amendments (append-only): 027 (alknet-http -> alkhttp), 031 (post-
  extraction state), 044 (split landed as modules in alkcall), 045
  (name normalization; citation-space note).
- Citation hygiene: unprefixed/colliding source-repo cites prefixed
  ("alknet ADR-NNN (provenance)") across 009/011/022/034/035/038/040/
  043/044/045; stale OQ cites corrected (session registries OQ-04, batch
  semantics OQ-03, verifier rule ADR-054); stale anchors fixed.
- README provenance note consolidated; citation conventions documented.

Verification: markdown link check (all local links resolve, all in-crate
ADR cites point at existing decisions/ files); table integrity check
(uniform row widths across open-questions.md and edited tables); residual
alknet-mention sweep (only provenance/historical/intentional framing
remains); architecture review subagent pass with zero remaining criticals.
Docs-only change — no code paths touched.
This commit is contained in:
glm-5.3-flash committed 2026-10-13 10:53:47 +00:00
1 parent 7b8a3e7558
commit 5f019c02f1
26 files changed
+670 -383

No files matched your search

+124 -105
View File
@@ -1,9 +1,9 @@
---
status: draft
last_updated: 2026-07-17
last_updated: 2026-10-13 (alkcall reframing)
---
# alknet-call — Client and Adapters
# alkcall — Client and Adapters
The outbound half of the call protocol: opening connections, importing remote
operations, and the adapter contract that ties import-style adapters together.
@@ -11,14 +11,15 @@ This document covers what ADR-022 specced but the server-side implementation
(`call-protocol.md`, `operation-registry.md`) did not include — the `CallClient`
that *opens* a connection, the `from_call` adapter, and the
`OperationAdapter` trait. (`from_jsonschema` was originally specced here
too, but ADR-027 moved it to `alknet-http` — see §"from_jsonschema" below.)
too, but ADR-027 moved it to the HTTP host crate — today `alkhttp`
(then named `alknet-http`) — see §"from_jsonschema" below.)
The server-side `CallAdapter` and `CallConnection`
dispatch loop are covered in `call-protocol.md`; this document covers the
client-side connection-establishment half and the adapter surface.
## What
This document specifies three components, all in `alknet-call`:
This document specifies three components, all in alkcall:
1. **`CallClient`** — takes over an established transport `Connection`
on ALPN `alk/call`, spawns the shared dispatch loop, and produces
@@ -35,20 +36,21 @@ This document specifies three components, all in `alknet-call`:
`from_openapi`, `from_mcp`, and `from_jsonschema` all implement.
> **`from_jsonschema` moved.** ADR-027 moved `from_jsonschema` from
> `alknet-call` to `alknet-http` and gave it a real reqwest-backed
> the call protocol to the HTTP host crate and gave it a real reqwest-backed
> forwarding handler (it was a broken schema-only placeholder before).
> It is now an HTTP-backed single-endpoint adapter for non-standard /
> non-OpenAPI / basic REST endpoints, functionally similar to
> `from_openapi` but one endpoint at a time. See
> [`crates/http/http-adapters.md`](/workspace/@alkdev/alknet/docs/architecture/crates/http/http-adapters.md) §"from_jsonschema".
> The `FromJsonSchema` provenance variant stays in `alknet-call`
> `from_openapi` but one endpoint at a time. It lives in `alkhttp`; its
> full spec is `alkhttp`'s `http-adapters.md` (original source-repo copy
> at `/workspace/@alkdev/alknet/docs/architecture/crates/http/http-adapters.md`,
> provenance). The `FromJsonSchema` provenance variant stays in alkcall
> (`OperationProvenance`); only the adapter implementation moved.
It also records two cross-cutting architectural mechanisms that the adapter
surface rests on:
- The **adapter location map** — which adapters live in `alknet-call` vs
`alknet-http`, and why.
- The **adapter location map** — which adapters live in alkcall vs
`alkhttp`, and why.
- The **no-env-vars invariant** — the architectural mechanism by which
downstream consumers' `std::env::var` credential reads are made unreachable.
@@ -65,16 +67,19 @@ downstream consumer: the runner pattern (a process that connects outward to a
hub and exposes local ops), the container-service rewrite, the bilateral
exchange, the NAPI projection, and the agent's cross-node tool dispatch all
require a `CallClient`. `from_call` is the #2 gap; the `OperationAdapter`
trait is the enabling gap for `alknet-http`'s `from_openapi`/`from_mcp`.
trait is the enabling gap for `alkhttp`'s `from_openapi`/`from_mcp`.
ADR-022 specced this surface. This document is the spec that operationally
fills the gap ADR-022 left to implementation: the `CallClient` API, the
`from_call` flow, the trait signature, the adapter location, the credential
invariant, and the bilateral pattern. The gap
analysis (`docs/research/alknet-call-completion/gap-analysis.md`) identified
analysis (source-repo research, now at
`/workspace/@alkdev/alknet/docs/research/alknet-call-completion/gap-analysis.md`)
identified
four decisions (DC-1..4) needed before implementation. DC-1 was initially
resolved by ADR-023 (`remote_safe`/`trusted_peer`), but a subsequent research
pass (`docs/research/alknet-call-peer-routing/findings.md`) found that
pass (source-repo research, now at
`/workspace/@alkdev/alknet/docs/research/alknet-call-peer-routing/findings.md`) found that
ADR-023's model was structurally broken for the head→N-workers pattern (the
primary use case) and that its parallel `remote_safe`/`trusted_peer`
authorization system duplicated the existing `AccessControl`/`Identity`
@@ -101,7 +106,7 @@ ordered, reliable bidirectional stream — QUIC, TCP+TLS, WebTransport,
SSH `direct-tcpip`, a WebSocket (ADR-007 `Connection::from_stream` /
`from_bidi`). The primary constructor (`spawn_dispatch`) takes a
pre-established `Connection` from any transport; the dial lives in
`AlknetClient` (`alknet-client`, ADR-045). This mirrors
the composition layer's dial helper, `AlknetClient` (ADR-045). This mirrors
`ChannelClient::from_connection` (ADR-043) and is the client-side
analogue of the server-side generalization ADR-007 made.
@@ -176,14 +181,15 @@ authorization machinery that gates every other call. No `RemoteFilter`, no
primary constructor — it takes a pre-established `Connection`,
constructs a `CallConnection`, builds a `Dispatcher`, spawns the
dispatch task, and returns the live `CallConnection`. The dial lives in
`AlknetClient` (`alknet-client`, ADR-045): keeping a QUIC convenience
constructor on `CallClient` would make `alknet-call` depend on
`alknet-client`, contradicting the dep graph (the protocol crates are
the composition layer's dial helper, `AlknetClient` (ADR-045): keeping a QUIC convenience
constructor on `CallClient` would make alkcall depend on
the dial crate, contradicting the dep graph (the protocol crates are
parallel to the dial, not downstream of it). Callers compose
`AlknetClient::dial_quic` + `spawn_dispatch` — two lines, the dial then
the take-over. Tests use `spawn_dispatch` directly to wire mock/loopback
connections. The one-way-door surface is `spawn_dispatch`; the dial
lives in `alknet-client`.
lives in the composition layer (`AlknetClient` today — future owner
OQ-43).
This mirrors `ChannelClient::from_connection` (ADR-043) and is the
client-side analogue of the server-side generalization ADR-007 made.
@@ -232,7 +238,7 @@ authorization). See [ADR-024](decisions/024-peer-graph-routing-model.md) §6.
The credential dimensions are split across two layers (ADR-012, amended
2026-07-17):
- **`ConnectionCredentials`** (in `alknet-core`, per ADR-012) — the
- **`ConnectionCredentials`** (vendored core types in alkcall, per ADR-012) — the
**transport-level** credential bundle, consumed by the dial
(`AlknetClient`). Carries the two transport-identity dimensions:
`local_identity` (the local node's `TlsIdentity`) and `remote_identity`
@@ -250,7 +256,7 @@ Credentials come from `Capabilities` (ADR-010), never from environment
variables. The transport-identity dimensions (ADR-022 §7):
```rust
// Transport-level (alknet-core, consumed by the dial — ADR-012)
// Transport-level (vendored core in alkcall, consumed by the dial — ADR-012)
pub struct ConnectionCredentials {
pub local_identity: Option<TlsIdentity>, // RFC 7250 raw key or X.509
pub remote_identity: Option<RemoteIdentity>, // expected fingerprint (None = CA path / fail-closed)
@@ -262,14 +268,15 @@ pub struct ConnectionCredentials {
// Dispatcher::resolve_identity reads payload.get("auth_token").
```
`RemoteIdentity` (ADR-022 §7, extended by ADR-034 §2) carries a
`RemoteIdentity` (ADR-022 §7, extended by alknet ADR-034 §2 — the
verifier-rule ADR, re-owned as ADR-054) carries a
fingerprint string the assembly layer derives from `Capabilities` when
the local node has a `PeerEntry` for the remote (the known-peer case →
fingerprint pin). `remote_identity: None` is the **public X.509
endpoint** case: the local node has no `PeerEntry` for the remote, so
there is no fingerprint to pin. Combined with an X.509 transport, `None`
selects CA verification (`WebPkiServerVerifier`) per the
verifier-selection rule in ADR-034 §3. Combined with an Ed25519
verifier-selection rule in ADR-054. Combined with an Ed25519
raw-key transport, `None` fails closed (raw-key remotes are always
known peers — no CA to fall back to). The `Option` is load-bearing, not
cosmetic: `Some(fingerprint)` means "pin this" (known peer), `None`
@@ -282,25 +289,27 @@ pub struct RemoteIdentity { pub fingerprint: String }
```
There is no call-protocol credential bundle. The transport dimensions
(`local_identity`, `remote_identity`) are in `ConnectionCredentials` in
`alknet-core` per ADR-012.
(`local_identity`, `remote_identity`) are in `ConnectionCredentials` with
the vendored core types in alkcall, per ADR-012.
- **TLS identity** — the local node's Ed25519 raw key (RFC 7250) or X.509 cert,
derived from the vault at startup (alknet ADR-020, ADR-026, ADR-027).
derived from the vault at startup (vault ADRs live in the source repo —
alknet ADR-020/026/027, provenance).
- **Auth token** — an opaque call-protocol-level token, decrypted from the
vault or derived from a shared secret.
- **Remote identity verification** — the expected fingerprint/cert of the
remote node, stored as a capability. `Some` → fingerprint pin (known
peer with a `PeerEntry`); `None` → CA verification for X.509 remotes,
fail-closed for Ed25519 raw-key remotes (ADR-034 §2/§3). The `None`
fail-closed for Ed25519 raw-key remotes (ADR-054). The `None`
case is the public-X.509-endpoint path, not a missing field.
These are populated by the assembly layer at `CallClient` construction time
from vault-derived `Capabilities`. The credential path is the no-env-vars
invariant (below). The concrete shapes of `TlsIdentity`, `AuthToken`, and
`RemoteIdentity` are implementation-detail two-way doors; the one-way
constraints are that they come from `Capabilities`, not env vars (ADR-010).
constraints are that they come from `Capabilities`, not env vars (ADR-010),
and that `ConnectionCredentials` lives with the vendored core types in
alkcall (ADR-012).
**TLS client-auth presentation** (OQ-14 #1, wired): the client presents
its Ed25519 key as an RFC 7250 raw public key client cert — the client-side
equivalent of the server's `RawKeyCertResolver`. This is **wired now**, not
@@ -324,7 +333,8 @@ start as "accept any, extract fingerprint" and add fingerprint-pinning later.
This is a two-way-door remainder; the one-way constraint (credentials from
`Capabilities`, not env vars) is unaffected.
**Server cert verifier selection** (OQ-14 #2 + alknet ADR-034 §3): the client-side
**Server cert verifier selection** (OQ-14 #2 + ADR-054 §2, re-owned
from the source repo's ADR-034): the client-side
`ServerCertVerifier` is selected by whether the local node has a `PeerEntry`
for the remote, not by key type alone. A pure-client
connection to a **public X.509 endpoint** (no `PeerEntry` on the local
@@ -339,7 +349,7 @@ with mixed Ed25519 + X.509 fingerprints) uses fingerprint pinning on
both cert paths and does enter the peer graph. An unknown Ed25519
raw-key remote fails closed (no CA to fall back to — raw-key remotes
are always known peers). See
[ADR-034](/workspace/@alkdev/alknet/docs/architecture/decisions/034-outgoing-only-x509-and-three-peer-roles.md)
[ADR-054](decisions/054-outgoing-only-x509-three-remote-roles.md)
for the verifier selection rule and the three-role naming.
### from_call
@@ -419,7 +429,7 @@ OQ-37: there is no `from_call` relay wrapper for marked ops — the relay
composition is the sanctioned path.
**Re-import on reconnection** (DC-2, OQ-12): `from_call` is a free function;
the assembly layer calls it after the dial (in `AlknetClient`). The overlay
the assembly layer calls it after the dial (in the composition layer's dial helper, `AlknetClient` — today the alknet glue; OQ-43). The overlay
is per-connection (Layer 2, ADR-019), so a stale overlay dies with the
connection; re-import on reconnect is naturally scoped to the new
connection. A `CallConnection::refresh()` method for mid-connection
@@ -456,35 +466,38 @@ want to disclose the originator. See [ADR-026](decisions/026-forwarded-for-ident
### from_jsonschema
`from_jsonschema` was originally specified here (ADR-022 §5) as a
schema-only adapter in `alknet-call` — a placeholder handler returning
schema-only adapter in the call protocol — a placeholder handler returning
`NOT_FOUND`. That was broken: an op in the registry needs a real handler,
and the "schema-only, no handler" concept conflated schema validation
(a planning activity that doesn't need a registry entry) with operation
registration (which always needs a handler).
[ADR-027](decisions/027-from-jsonschema-as-http-adapter.md) moved
`from_jsonschema` to `alknet-http` as an HTTP-backed single-endpoint
`from_jsonschema` to the HTTP host crate — today `alkhttp`
(ex-`alknet-http`) — as an HTTP-backed single-endpoint
adapter: the caller supplies an `OperationSpec` + `HttpServiceConfig` +
path template + method, and the adapter builds one
`HandlerRegistration` with a real reqwest forwarding handler and
`FromJsonSchema` provenance. It is functionally similar to `from_openapi`
but one endpoint at a time, for non-standard / non-OpenAPI / basic REST
endpoints that don't have a full OpenAPI document. See
[`crates/http/http-adapters.md`](/workspace/@alkdev/alknet/docs/architecture/crates/http/http-adapters.md) §"from_jsonschema".
endpoints that don't have a full OpenAPI document. See `alkhttp`'s
`http-adapters.md` §"from_jsonschema" (its original source-repo copy at
`/workspace/@alkdev/alknet/docs/architecture/crates/http/http-adapters.md`,
provenance).
The schema-validation-without-a-handler use case (the original stated
purpose) is served by consuming `OperationSpec` directly — the spec
already carries the input/output JSON Schemas. No adapter, no registry
entry, no handler is needed for that.
The `FromJsonSchema` provenance variant stays in `alknet-call`
The `FromJsonSchema` provenance variant stays in alkcall
(`OperationProvenance` in `registry/registration.rs`); only the adapter
implementation moved.
### OperationAdapter trait
The shared shape across import-style adapters. The trait lives in
`alknet-call` (where the types live); the implementations live where their
alkcall (where the types live); the implementations live where their
transport dependencies live (see "Adapter Location Map" below).
```rust
@@ -514,13 +527,13 @@ door, recorded here.
Implementations:
- `FromCall` — call-protocol-backed, transport-agnostic (in
`alknet-call`). `from_call` discovers ops over a `CallConnection`,
alkcall). `from_call` discovers ops over a `CallConnection`,
which may be QUIC, TCP+TLS, or any transport `Connection::from_stream`
supports (ADR-007).
- `FromOpenAPI` — HTTP-backed (in `alknet-http`).
- `FromJsonSchema` — HTTP-backed, single-endpoint (in `alknet-http` per
ADR-027; was a broken schema-only placeholder in `alknet-call`).
- `FromMCP` — MCP streamable-HTTP-backed (in `alknet-http`, feature-gated).
- `FromOpenAPI` — HTTP-backed (in `alkhttp`).
- `FromJsonSchema` — HTTP-backed, single-endpoint (in `alkhttp` per
ADR-027; was a broken schema-only placeholder in the call protocol).
- `FromMCP` — MCP streamable-HTTP-backed (in `alkhttp`, feature-gated).
The `to_*` adapters (`to_openapi`, `to_mcp`) are outbound projections, not
`OperationAdapter` implementations — they consume the registry, they don't
@@ -529,18 +542,19 @@ produce entries for it (ADR-022 §5).
### Adapter Location Map
The decomposition principle: **the adapter trait lives where the types live
(`alknet-call`); the adapter implementations live where their transport
(alkcall); the adapter implementations live where their transport
dependencies live.**
```
alknet-call (lean — no HTTP client, no HTTP server)
alkcall (lean — no HTTP client, no HTTP server)
├── OperationAdapter trait (the contract — async, per ADR-022 §5)
├── from_call (transport-agnostic — discovers remote ops via
│ call protocol over any Connection)
└── CallClient (outbound connection take-over —
spawn_dispatch, transport-agnostic; dial in AlknetClient)
spawn_dispatch, transport-agnostic; dial in
the composition layer's helper, AlknetClient)
alknet-http (owns HTTP server + HTTP client — separate crate, separate Phase 0)
alkhttp (owns HTTP server + HTTP client — sibling host crate)
├── ProtocolHandler for h2/http1.1/h3 (axum server — inbound HTTP)
├── from_openapi (parse OpenAPI doc + reqwest forwarding handler)
├── from_jsonschema (single-endpoint reqwest forwarding handler — ADR-027)
@@ -550,28 +564,26 @@ alknet-http (owns HTTP server + HTTP client — separate crate, separate Phase 0
Not built: MCP stdio transport
— stdio = spawn arbitrary executable = built-in RCE ("download untrusted MCP servers")
— streamable HTTP is the only supported MCP transport in alknet
— streamable HTTP is the only supported MCP transport in the alk stack
— recorded as an explicit security position, not a feature gap
```
`alknet-call` never sees the HTTP client. The `from_openapi`/`from_mcp`
alkcall never sees the HTTP client. The `from_openapi`/`from_mcp`
forwarding handlers are opaque `Arc<dyn Handler>` from the registry's
perspective — constructed by `alknet_http::from_openapi()` at registration
perspective — constructed by `alkhttp::from_openapi()` at registration
time, stored in `HandlerRegistration`, dispatched by the `CallAdapter` which
doesn't know reqwest is involved. `alknet-call` stays lean (no reqwest, no
axum); `alknet-http` owns both HTTP directions.
doesn't know reqwest is involved. alkcall stays lean (no reqwest, no
axum); `alkhttp` owns both HTTP directions.
**ADR-031 dependency note**: `alknet-http` implementing `from_openapi`/
`from_mcp` means `alknet-http` depends on `alknet-call` (for `OperationSpec`,
**ADR-031 dependency note**: `alkhttp` implementing `from_openapi`/
`from_mcp` means `alkhttp` depends on alkcall (for `OperationSpec`,
`Handler`, `HandlerRegistration`, `OperationAdapter`). ADR-031's rule is "no
handler crate depends on another handler crate" — but `alknet-call` is both
a handler *and* the protocol foundation that `alknet-agent` and `alknet-napi`
already consume. `alknet-http` depending on `alknet-call` is "HTTP uses the
call protocol types," not "HTTP depends on SSH." This is within the spirit of
ADR-031 (`alknet-call` is protocol-foundation, not a peer handler). The
`alknet-http` spec should note this explicitly; a one-line amendment to
ADR-031 clarifying that `alknet-call` is a protocol-foundation crate is
deferred to the `alknet-http` Phase 0.
handler crate depends on another handler crate" — but alkcall is the
protocol foundation that every downstream consumer
(including the HTTP host and any agent crate) builds on.
`alkhttp` depending on alkcall is "HTTP uses the
call protocol types," not "HTTP depends on a sibling handler." This is within the spirit of
ADR-031 (alkcall is protocol-foundation, not a peer handler).
### No-Env-Vars Invariant
@@ -595,7 +607,7 @@ vault (seed)
→ reqwest request goes out with vault-derived credential
```
The `from_openapi`/`from_mcp` forwarding handlers (in `alknet-http`) are the
The `from_openapi`/`from_mcp` forwarding handlers (in `alkhttp`) are the
credential injection point. They read from `context.capabilities`, not from
`std::env::var`. The downstream consumers' `Default` impls reading env vars
are simply never called — the assembly layer constructs providers with
@@ -603,14 +615,14 @@ vault-derived credentials through the builder API, or the provider's HTTP
calls are routed through `from_openapi` operations that carry the credential
in `Capabilities`.
**This is a spec-level invariant in `alknet-call`, not a runtime convention.**
**This is a spec-level invariant in alkcall, not a runtime convention.**
The dispatch path (`build_root_context` and `OperationEnv::invoke()` per
ADR-018 §6) populates `OperationContext.capabilities` from the registration
bundle. The invariant is: *no handler reads outbound credentials from any
source other than `OperationContext.capabilities`.* This is already the
architectural intent of ADR-010; this document records it as an explicit
invariant that the `from_openapi`/`from_mcp` handler implementations (in
`alknet-http`) are verified against.
`alkhttp`) are verified against.
### Exchange-of-Operations Pattern (Runner / Container Service)
@@ -649,20 +661,21 @@ Bilateral: the container service ALSO runs from_call against the hub,
the other side's `External` ops. The #2 gap.
3. `OperationAdapter` trait — `from_call` implements it. The #3 gap (enabling,
not blocking — `from_call` can be built as a free function before the trait
exists, but the trait is needed for `alknet-http`'s adapters).
exists, but the trait is needed for `alkhttp`'s adapters).
**Why the container service doesn't need alknet-ssh**: under the call
**Why the container service doesn't need SSH**: under the call
protocol, the container service is a `CallClient` that dials the hub's
`alk/call` ALPN (over QUIC, TCP+TLS, or any transport) — no SSH in
the loop. SSH port
forwarding becomes the *transitional* mechanism for targets that can't run a
call-protocol client (the `alknet-ssh` phase-0 findings document this
call-protocol client (the source repo's SSH phase-0 findings — alknet,
provenance — document this
transition). Once the container service runs a `CallClient`, SSH is out of
the path entirely.
This is the "dev runner" pattern: a call-protocol client that connects back
to a hub and exposes core dev tools (bash, fs, etc.) as operations. The agent
service (`alknet-agent`, downstream) is the consumer that orchestrates these
crate (downstream) is the consumer that orchestrates these
via `env.invoke()`.
## Implementation Priority Order
@@ -673,13 +686,13 @@ Based on the gap analysis and the downstream unblock chain:
runner, no container service, no bilateral exchange. Reuses the existing
`CallConnection` for the dispatch loop; adds only the
connection-establishment + credential-handling half. The single
highest-value piece of work in the entire `alknet-call` completion.
highest-value piece of work in the entire call-completion effort.
2. **`from_call`** (critical, depends on `CallClient`) — consumes the
already-implemented `services/list` + `services/schema` discovery API.
3. **`OperationAdapter` trait** (enabling) — the async trait. Small,
standalone, unblocks `alknet-http` Phase 1 (including `from_jsonschema`
standalone, unblocks `alkhttp`'s adapter work (including `from_jsonschema`
per ADR-027).
4. **DC-1 resolution** (peer-graph routing model, ADR-024) — the
@@ -692,23 +705,27 @@ Based on the gap analysis and the downstream unblock chain:
## What This Completion Unblocks
| Downstream crate | What it needs from alknet-call | Status without completion |
Historical planning table (source-repo era; crate names as they were).
The completion has since landed in this crate — `alknet-http` is now
`alkhttp`.
| Downstream crate | What it needs from alkcall | Status without completion |
|-------------------|-------------------------------|--------------------------|
| alknet-http | `OperationAdapter` trait (to implement `from_openapi`/`from_mcp`) | Blocked — can't define HTTP-backed adapters without the trait |
| alknet-ssh | Stable alknet-call types (no adapter dependency) | Not blocked — ssh depends on alknet-core, not alknet-call's adapters. Proceeds in parallel. |
| alknet-agent | `CallClient` (tool dispatch), `from_call` (remote tool import), `OperationAdapter` (provider adapters) | Blocked on `CallClient` + `from_call` |
| alknet-http (→ alkhttp) | `OperationAdapter` trait (to implement `from_openapi`/`from_mcp`) | Blocked — can't define HTTP-backed adapters without the trait |
| alknet-ssh (historical) | Stable alkcall types (no adapter dependency) | Not blocked — ssh depends on the vendored core types, not alkcall's adapters. Proceeds in parallel. |
| alknet-agent (historical) | `CallClient` (tool dispatch), `from_call` (remote tool import), `OperationAdapter` (provider adapters) | Blocked on `CallClient` + `from_call` |
| Container service (dispatch rewrite) | `CallClient` + `from_call` | Blocked — this is the primary consumer |
| Runner pattern (dev runner, opencode runner) | `CallClient` + `from_call` | Blocked — the runner IS a `CallClient` |
| alknet-napi | `CallClient` (Node.js calls remote ops) | Blocked — NAPI projects `CallClient` to JS |
| alknet-napi (historical) | `CallClient` (Node.js calls remote ops) | Blocked — NAPI projects `CallClient` to JS |
## Constraints
- **No HTTP in alknet-call.** `from_openapi`/`from_mcp`/`from_jsonschema`/
`to_openapi`/`to_mcp` live in `alknet-http`. The `OperationAdapter`
- **No HTTP in alkcall.** `from_openapi`/`from_mcp`/`from_jsonschema`/
`to_openapi`/`to_mcp` live in `alkhttp`. The `OperationAdapter`
trait and the call-protocol-backed adapter (`from_call`, transport-
agnostic) live in `alknet-call`. `from_jsonschema` was originally
(mis)placed in `alknet-call` as a schema-only placeholder; ADR-027
moved it to `alknet-http` as a real HTTP-backed adapter. See Adapter
agnostic) live in alkcall. `from_jsonschema` was originally
(mis)placed in the call protocol as a schema-only placeholder; ADR-027
moved it to the HTTP host crate as a real HTTP-backed adapter. See Adapter
Location Map.
- **No secret material on the wire.** `ConnectionCredentials` carries vault-derived
material for the *outbound* connection (TLS identity); `auth_token` is a
@@ -729,7 +746,7 @@ Based on the gap analysis and the downstream unblock chain:
not a singular connection overlay. `invoke_peer()` routes to the right peer
via `PeerRef::Specific` / `PeerRef::Any` (ADR-024 §1-2).
- **`from_call` is a manual free function.** The assembly layer calls it
after the dial (in `AlknetClient`). The overlay is per-connection so
after the dial (in the composition layer's dial helper, `AlknetClient` — today the alknet glue; OQ-43). The overlay is per-connection so
re-import on reconnect is naturally scoped (DC-2, OQ-12). See
[ADR-028](decisions/028-from-call-manual-free-function.md).
- **`from_call` namespace collision is same-peer only.** Cross-peer collision
@@ -739,7 +756,7 @@ Based on the gap analysis and the downstream unblock chain:
- **`OperationAdapter::import()` returns `Result`.** Failures surface as
`AdapterError` (DC-4, OQ-11).
- **MCP stdio transport is not built.** Streamable HTTP is the only supported
MCP transport in alknet. stdio = spawn arbitrary executable = built-in RCE.
MCP transport in the alk stack. stdio = spawn arbitrary executable = built-in RCE.
Recorded as an explicit security position, not a feature gap.
- **Pure-client X.509 connections are not in the peer graph on the client
side.** A `CallClient` connection to a public X.509 endpoint with no
@@ -749,10 +766,10 @@ Based on the gap analysis and the downstream unblock chain:
invoked through the `CallConnection` handle. The client-side
`ServerCertVerifier` uses CA verification (`WebPkiServerVerifier`) for
such remotes; known peers (hub with `PeerEntry`) use fingerprint
pinning. See [ADR-034](/workspace/@alkdev/alknet/docs/architecture/decisions/034-outgoing-only-x509-and-three-peer-roles.md).
pinning. See [ADR-054](decisions/054-outgoing-only-x509-three-remote-roles.md).
- **`ConnectionCredentials.remote_identity: None` is load-bearing.** `None`
means "no `PeerEntry` for this remote → use CA verification (X.509)
or fail closed (Ed25519 raw key)" per the ADR-034 §3 verifier rule.
or fail closed (Ed25519 raw key)" per the verifier rule (ADR-054).
The implementation must not default `remote_identity` to a placeholder
to satisfy the field, and must not treat `None` as "skip verification"
— `None` + X.509 is CA verification, `None` + raw key is a hard
@@ -763,11 +780,11 @@ Based on the gap analysis and the downstream unblock chain:
| Decision | ADR | Summary |
|----------|-----|---------|
| Call protocol client and adapter contract | [ADR-022](decisions/022-call-protocol-client-and-adapter-contract.md) | `CallClient` opens connections; `from_call` imports remote ops; connection direction independent of call direction; trait is async; adapters produce `HandlerRegistration` bundles |
| `from_jsonschema` as HTTP-backed single-endpoint adapter in alknet-http | [ADR-027](decisions/027-from-jsonschema-as-http-adapter.md) | Moved `from_jsonschema` from `alknet-call` (broken schema-only placeholder) to `alknet-http` as a real reqwest-backed single-endpoint adapter; `FromJsonSchema` provenance stays in `alknet-call` as a leaf |
| `from_jsonschema` as HTTP-backed single-endpoint adapter | [ADR-027](decisions/027-from-jsonschema-as-http-adapter.md) | Moved `from_jsonschema` from the call protocol (broken schema-only placeholder) to the HTTP host crate as a real reqwest-backed single-endpoint adapter — today `alkhttp` (ex-`alknet-http`); `FromJsonSchema` provenance stays in alkcall as a leaf |
| Peer-graph routing model (DC-1, supersedes ADR-023) | [ADR-024](decisions/024-peer-graph-routing-model.md) | Peer-keyed overlays + `PeerRef` routing; peer authorization via existing `AccessControl::check(peer_identity)`; retires `remote_safe`/`trusted_peer` |
| PeerEntry and Identity.id decoupling | [ADR-025](decisions/025-peerentry-and-identity-id-decoupling.md) | `PeerId` source changes from UUID to `Identity.id` (= `PeerEntry.peer_id`, stable across key rotation); `Identity.id` decoupled from crypto material on the fingerprint path |
| Forwarded-for identity | [ADR-026](decisions/026-forwarded-for-identity.md) | `forwarded_for` field on `call.requested` and `OperationContext`; the `from_call` handler populates it; metadata only, never used by `AccessControl::check` |
| Storage boundary and repo/adapter pattern | [alknet ADR-033](/workspace/@alkdev/alknet/docs/architecture/decisions/033-storage-boundary-and-repo-adapter-pattern.md) | Core defines repo traits + in-memory defaults; persistence adapters are separate crates |
| Storage boundary and repo/adapter pattern | [alknet ADR-033](/workspace/@alkdev/alknet/docs/architecture/decisions/033-storage-boundary-and-repo-adapter-pattern.md) (provenance) | Core defines repo traits + in-memory defaults; persistence adapters are separate crates (composition-layer work — OQ-43) |
| Secret material flow and capability injection | [ADR-010](decisions/010-secret-material-flow-and-capability-injection.md) | The no-env-vars invariant's foundation; capabilities injected at assembly layer |
| Handler registration, provenance, and composition authority | [ADR-018](decisions/018-handler-registration-provenance-and-composition-authority.md) | The registration bundle adapters produce; `composition_authority: None` for leaves |
| Operation registry layering | [ADR-019](decisions/019-operation-registry-layering.md) | Layer 2 per-connection overlay where `from_call` imports land |
@@ -776,11 +793,11 @@ Based on the gap analysis and the downstream unblock chain:
| Operation error schemas | [ADR-016](decisions/016-operation-error-schemas.md) | `error_schemas` mirrored by `from_call` from remote op's spec |
| Streaming handler for subscriptions | [ADR-021](decisions/021-streaming-handler-for-subscriptions.md) | `from_call` `Subscription` ops register a `StreamingHandler` (`HandlerKind::Stream`) that calls `CallConnection::subscribe()` and forwards the remote stream; `Query`/`Mutation` stay `HandlerKind::Once` |
| TLS identity redesign | [alknet ADR-027](/workspace/@alkdev/alknet/docs/architecture/decisions/027-tls-identity-redesign-acme-rawkey-decoupling.md) | RFC 7250 raw key / X.509 cert dimensions of the local `TlsIdentity` (now carried by `ConnectionCredentials.local_identity`) |
| Outgoing-only X.509 and three peer roles | [alknet ADR-034](/workspace/@alkdev/alknet/docs/architecture/decisions/034-outgoing-only-x509-and-three-peer-roles.md) | Public X.509 endpoint is not a `PeerEntry` on the client side (no `PeerId`, not in peer graph); client-side verifier by `PeerEntry` presence (CA vs fingerprint pin); hub = mixed-fingerprint `PeerEntry` |
| HD derivation for encryption keys | [alknet ADR-020](/workspace/@alkdev/alknet/docs/architecture/decisions/020-hd-derivation-for-encryption-keys.md) | Vault-derived TLS identity material |
| Vault key model | [alknet ADR-026](/workspace/@alkdev/alknet/docs/architecture/decisions/026-vault-key-model-hd-derivation.md) | Vault-derived TLS identity material |
| Vault local-only dispatch | [alknet ADR-025](/workspace/@alkdev/alknet/docs/architecture/decisions/025-vault-local-only-dispatch.md) | Vault access at assembly layer only; the credential injection path's first hop |
| Crate decomposition | [ADR-031](decisions/031-crate-decomposition.md) | `alknet-http` depends on `alknet-call` (protocol-foundation exception, noted in Adapter Location Map) |
| Outgoing-only X.509 and three remote roles | [ADR-054](decisions/054-outgoing-only-x509-three-remote-roles.md) (re-owned; provenance: [alknet ADR-034](/workspace/@alkdev/alknet/docs/architecture/decisions/034-outgoing-only-x509-and-three-peer-roles.md)) | Public X.509 endpoint is not a `PeerEntry` on the client side (no `PeerId`, not in peer graph); client-side verifier by `PeerEntry` presence (CA vs fingerprint pin); hub = mixed-fingerprint `PeerEntry` |
| HD derivation for encryption keys | [alknet ADR-020](/workspace/@alkdev/alknet/docs/architecture/decisions/020-hd-derivation-for-encryption-keys.md) (provenance — vault ADRs live in the source repo) | Vault-derived TLS identity material |
| Vault key model | [alknet ADR-026](/workspace/@alkdev/alknet/docs/architecture/decisions/026-vault-key-model-hd-derivation.md) (provenance) | Vault-derived TLS identity material |
| Vault local-only dispatch | [alknet ADR-025](/workspace/@alkdev/alknet/docs/architecture/decisions/025-vault-local-only-dispatch.md) (provenance) | Vault access at assembly layer only; the credential injection path's first hop |
| Crate decomposition | [ADR-031](decisions/031-crate-decomposition.md) | `alkhttp` depends on alkcall (protocol-foundation exception, noted in Adapter Location Map) |
| One-way door decision framework | [ADR-032](decisions/032-one-way-door-decision-framework.md) | Door-type classification for DC-1..4 |
## Open Questions
@@ -827,7 +844,7 @@ See [open-questions.md](open-questions.md) for full details.
`PeerEntry` supports multiple credential paths (fingerprints +
auth_token_hash), `ApiKeyEntry` is for tokens that ARE the identity.
See OQ-20 in open-questions.md.
- **OQ-21** (resolved, alknet ADR-034 — not ported): X.509 outgoing-only
- **OQ-21** (resolved by ADR-054): X.509 outgoing-only
case — three
remote roles named (public X.509 endpoint, transport relay, hub).
`PeerEntry` asymmetry is correct: a pure-client connection to a public
@@ -837,10 +854,10 @@ See [open-questions.md](open-questions.md) for full details.
land in the connection's Layer 2 overlay and are invoked through the
connection handle. The client-side `ServerCertVerifier` is selected
by `PeerEntry` presence: known peer → fingerprint pin; unknown X.509
remote → CA verification (`WebPkiServerVerifier`). The deciding ADR
(alknet ADR-034, "Outgoing-Only X.509 and Three Peer Roles") was not
ported into alkcall; the surviving alkcall surface is ADR-025's
`PeerEntry` asymmetry. See OQ-21 in open-questions.md.
remote → CA verification (`WebPkiServerVerifier`). The rule is
re-owned in-crate by ADR-054 (provenance: alknet ADR-034, "Outgoing-Only
X.509 and Three Peer Roles", not ported); ADR-025's
`PeerEntry` asymmetry is the registry surface. See OQ-21 in open-questions.md.
## References
@@ -852,11 +869,16 @@ See [open-questions.md](open-questions.md) for full details.
model (the server-side complement to this document)
- `operation-registry.md` — `HandlerRegistration`, provenance, capability
injection, service discovery (the discovery API `from_call` consumes)
- `docs/research/alknet-call-completion/gap-analysis.md` — DC-1..4, the
implementation-state audit, the downstream unblock chain
- `docs/research/alknet-call-peer-routing/findings.md` — the peer-graph
routing research that identified ADR-023's structural gap and validated
the ADR-024 design via POC
- Source-repo research (alknet, provenance — these paths live in
`/workspace/@alkdev/alknet/docs/research/`, not this repo):
- `alknet-call-completion/gap-analysis.md` — DC-1..4, the
implementation-state audit, the downstream unblock chain
- `alknet-call-peer-routing/findings.md` — the peer-graph
routing research that identified ADR-023's structural gap and validated
the ADR-024 design via POC
- `alknet-ssh/phase-0-findings.md` — SSH Phase 0; confirms ssh depends on
the vendored core types, not alkcall's adapters, so it proceeds in
parallel with the completion
- `/workspace/@alkdev/operations/` — TypeScript prior art (`from_openapi.ts`,
`from_mcp.ts`, `from_schema.ts`, `scanner.ts`)
- `/workspace/@alkdev/dispatch/` — concrete downstream consumer (container
@@ -864,7 +886,4 @@ See [open-questions.md](open-questions.md) for full details.
- `/workspace/aisdk/` — downstream consumer (Rust port of Vercel AI SDK); the
no-env-vars invariant makes its `std::env::var` reads unreachable
- `/workspace/rust-sdk/` — MCP Rust SDK (rmcp); streamable HTTP transport for
`alknet-http`'s `from_mcp`/`to_mcp` (separate crate, separate Phase 0)
- `docs/research/alknet-ssh/phase-0-findings.md` — alknet-ssh Phase 0;
confirms ssh depends on alknet-core not alknet-call's adapters, so it
proceeds in parallel with this completion
`alkhttp`'s `from_mcp`/`to_mcp`