docs(arch): call-completion — ADR-028 peer-scoped filtering + client-and-adapters spec + tasks

Resolves the four gap-analysis decisions (DC-1..4) blocking the alknet-call
client/adapter surface specced in ADR-017:

- ADR-028 (new): locks the one-way door for DC-1 — CallClient registry is
  default-deny (remote_safe: bool on HandlerRegistration, default false across
  all provenance); share-global is an explicit trusted-peer opt-in; filtering
  is a dispatch-time read over the single Layer-0 registry, not a copy.
- client-and-adapters.md (new spec): operationally fills the gap ADR-017 left
  to implementation — CallClient, from_call, from_jsonschema, OperationAdapter
  trait, adapter location map, no-env-vars invariant, exchange-of-operations
  pattern. Keeps call-protocol.md and operation-registry.md under the
  700-line split threshold.
- ADR-017 amended: records DC-2/3/4 v1 defaults (auto-on-reconnect,
  error-on-collision, Result error type) and points DC-1 at ADR-028.
- OQ-25..28 (new): two-way-door remainders (remote_safe shape, AdapterError
  variants, re-import trigger, namespace collision) with v1 defaults recorded.
- Index/cross-ref updates across READMEs and the two existing call specs.

Tasks: 6 task files under tasks/call/ decomposing the completion work along
the gap-analysis priority order — remote-safe-marking (one-way door, first)
→ call-client (phase-risk) → from-call → operation-adapter-trait →
from-jsonschema (parallel with call-client) → review-completion. Graph
validated with taskgraph; parallelism designed in (from-jsonschema runs
concurrent with call-client/from-call once the trait lands).
This commit is contained in:
2026-06-26 12:25:13 +00:00
parent 6940d9858d
commit 2649e068e5
14 changed files with 1817 additions and 11 deletions

View File

@@ -1,6 +1,6 @@
---
status: draft
last_updated: 2026-06-20
last_updated: 2026-06-26
---
# Open Questions
@@ -315,4 +315,97 @@ These questions are acknowledged but not active. They will be promoted to open w
- **Door type**: One-way (wire format), two-way (mapping mechanism)
- **Priority**: high
- **Resolution**: `OperationSpec` gains `error_schemas: Vec<ErrorDefinition>` where each `ErrorDefinition` carries a `code`, `description`, `schema` (JSON Schema for the error detail payload), and optional `http_status` (for adapter projection). The `call.error` payload gains an optional `details` field carrying the typed error payload. Protocol-level codes (`NOT_FOUND`, `FORBIDDEN`, `INVALID_INPUT`, `INTERNAL`, `TIMEOUT`) are distinct from operation-level domain codes (`FILE_NOT_FOUND`, `RATE_LIMITED`, etc.) — protocol codes are emitted by the dispatch machinery, operation codes by handlers. `from_openapi`/`to_openapi` map OpenAPI response status codes to/from `ErrorDefinition`s, making the adapter contract from ADR-017 faithful on the error axis. `services/schema` exposes `error_schemas` for client code generation. See ADR-023.
- **Cross-references**: ADR-017, ADR-023, docs/reviews/001-pre-implementation-architecture-sanity-check.md (C5), [operation-registry.md](crates/call/operation-registry.md), [call-protocol.md](crates/call/call-protocol.md)
- **Cross-references**: ADR-017, ADR-023, docs/reviews/001-pre-implementation-architecture-sanity-check.md (C5), [operation-registry.md](crates/call/operation-registry.md), [call-protocol.md](crates/call/call-protocol.md)
## Theme: Call Client and Adapters
These open questions are the two-way-door remainders from the
call-completion gap analysis
(`docs/research/alknet-call-completion/gap-analysis.md`, DC-1..4). The
one-way door among them (DC-1, the *existence* of peer-scoped filtering as
the default) is resolved by ADR-028; what remains open here is the shape.
The v1 defaults for DC-2/3/4 are recorded in
[client-and-adapters.md](crates/call/client-and-adapters.md) and may be
revisited during implementation without a new ADR.
### OQ-25: Remote-Safe Marking Shape for CallClient Peer-Scoped Filtering
- **Origin**: [client-and-adapters.md](crates/call/client-and-adapters.md), ADR-017 (§1 Consequences), ADR-028
- **Status**: open
- **Door type**: Two-way (shape only — existence is one-way, resolved by ADR-028)
- **Priority**: medium
- **Resolution**: ADR-028 locks the one-way door: a `CallClient`'s registry
view is **default-deny** (no operation is exposed to the remote peer unless
explicitly marked remote-safe), with share-global as an explicit trusted-peer
opt-in. The v1 shape is a `remote_safe: bool` field on
`HandlerRegistration` (default `false` across all provenance). The shape is
the two-way-door remainder: a boolean is the simplest shape that supports
default-deny; a deployment that needs per-peer differentiation (different
subsets exposed to different peers on the same node) needs a richer
mechanism — per-peer allowlist, capability-class tag, or a peer-id-keyed map
on the registration. v1's boolean limits this to "remote-safe for any peer"
vs "not", which is acceptable for the runner/dispatch pattern (one remote
peer per `CallClient`). A future ADR may amend or supersede ADR-028's shape
without revisiting the *existence* of filtering. Also open under this OQ:
whether a richer shape should *expose-but-deny* non-remote-safe ops in
`services/list` (returning `NOT_FOUND` on call) instead of *hiding* them.
v1 hides them — a peer should not see ops it cannot call, so discovery and
dispatch filters agree (ADR-028 Assumption 2); expose-but-deny is the
richer-shape question, not a v1 question.
- **Cross-references**: ADR-009, ADR-014, ADR-015, ADR-017, ADR-022, ADR-024,
ADR-028, [client-and-adapters.md](crates/call/client-and-adapters.md),
[operation-registry.md](crates/call/operation-registry.md)
### OQ-26: OperationAdapter Error Type (AdapterError Variants)
- **Origin**: [client-and-adapters.md](crates/call/client-and-adapters.md), ADR-017 §5
- **Status**: open
- **Door type**: Two-way
- **Priority**: medium
- **Resolution**: ADR-017 §5 showed `async fn import(&self) ->
Vec<HandlerRegistration>` with no error type. The trait returns
`Result<Vec<HandlerRegistration>, AdapterError>` where `AdapterError` is a
crate-level enum. The *presence* of an error type is recorded in
[client-and-adapters.md](crates/call/client-and-adapters.md); the exact
variants are the two-way-door remainder. The failure modes real
implementations hit: discovery transport failure (`from_call` remote
unreachable), schema parse failure (`from_openapi`, `from_jsonschema`),
unauthorized (HTTP 401 for `from_openapi`, `from_mcp`). Likely variants:
`DiscoveryFailed`, `SchemaParse`, `Transport`, `Unauthorized`. Decided
during implementation; recorded here, not in a full ADR.
- **Cross-references**: ADR-017, [client-and-adapters.md](crates/call/client-and-adapters.md)
### OQ-27: from_call Re-Import Trigger
- **Origin**: [client-and-adapters.md](crates/call/client-and-adapters.md), ADR-017 Assumption 4
- **Status**: open
- **Door type**: Two-way
- **Priority**: low
- **Resolution**: ADR-017 Assumption 4 noted re-import "happens on
reconnection or is triggered explicitly." The v1 default is
**auto-re-import on connection establishment**. The overlay is
per-connection (Layer 2, ADR-024), so a stale overlay dies with the
connection; re-import on reconnect is naturally scoped to the new
connection. This is the right default for the runner pattern (a worker
reconnects → the hub re-discovers the worker's ops automatically).
Explicit re-import via a future `CallConnection::refresh()` method is
additive and can be added if a deployment needs manual control. Reversal
is cheap; no ADR needed.
- **Cross-references**: ADR-017, ADR-024, [client-and-adapters.md](crates/call/client-and-adapters.md)
### OQ-28: from_call Namespace Collision Behavior
- **Origin**: [client-and-adapters.md](crates/call/client-and-adapters.md), ADR-017 §3
- **Status**: open
- **Door type**: Two-way
- **Priority**: low
- **Resolution**: ADR-017 §3's `FromCallConfig` namespace prefix is
**optional, default no prefix, collision = error**. A node importing from
two remotes that both expose `/container/exec` without prefixes should fail
loudly rather than silently overwrite. The operator adds prefixes when they
know they're importing from multiple sources. This matches the
default-deny, explicit-allow posture (ADR-015, ADR-028). Reversal is cheap;
no ADR needed. The alternative (last-wins) would silently mask one
remote's op behind another's, which is the kind of surprise the
default-deny posture exists to avoid.
- **Cross-references**: ADR-015, ADR-017, ADR-028, [client-and-adapters.md](crates/call/client-and-adapters.md)