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:
125
tasks/call/client/operation-adapter-trait.md
Normal file
125
tasks/call/client/operation-adapter-trait.md
Normal file
@@ -0,0 +1,125 @@
|
||||
---
|
||||
id: call/client/operation-adapter-trait
|
||||
name: Define OperationAdapter async trait + AdapterError enum (ADR-017 §5, DC-4/OQ-26)
|
||||
status: pending
|
||||
depends_on: [call/registry/handler-registration]
|
||||
scope: narrow
|
||||
risk: low
|
||||
impact: project
|
||||
level: implementation
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Define the `OperationAdapter` async trait and the `AdapterError` crate-level
|
||||
enum in `src/client/adapter.rs` (or `src/registry/adapter.rs` — pick the
|
||||
module that keeps the trait near the types it produces). This is the #3 gap
|
||||
(enabling, not blocking) — `from_call` can be built as a free function before
|
||||
the trait exists, but the trait is needed before `alknet-http`'s
|
||||
`from_openapi`/`from_mcp` adapters can be built. Small, standalone, unblocks
|
||||
`alknet-http` Phase 1.
|
||||
|
||||
### The trait (ADR-017 §5)
|
||||
|
||||
```rust
|
||||
#[async_trait]
|
||||
pub trait OperationAdapter: Send + Sync {
|
||||
async fn import(&self) -> Result<Vec<HandlerRegistration>, AdapterError>;
|
||||
}
|
||||
```
|
||||
|
||||
The trait is **async** because `from_call` requires async discovery
|
||||
(`services/list` + `services/schema` over a QUIC connection). Sync adapters
|
||||
(`from_openapi`, `from_mcp` reading a static spec) trivially satisfy an async
|
||||
trait — their `import()` bodies contain no `.await` points. This is locked by
|
||||
ADR-017 §5; the async/sync question is decided.
|
||||
|
||||
The return type is `Vec<HandlerRegistration>` (not `(OperationSpec, Handler)`
|
||||
pairs) — ADR-022 changed the registration API to the bundle shape, and
|
||||
adapters must produce bundles. Adapter convenience methods construct bundles
|
||||
with `composition_authority: None` and `scoped_env: None` for the leaf ops they
|
||||
produce.
|
||||
|
||||
The `to_*` adapters (`to_openapi`, `to_mcp`) are outbound projections, not
|
||||
`OperationAdapter` implementations — they consume the registry, they don't
|
||||
produce entries for it (ADR-017 §5). Do not implement `to_*` here.
|
||||
|
||||
### AdapterError (DC-4, OQ-26)
|
||||
|
||||
ADR-017 §5 showed `async fn import(&self) -> Vec<HandlerRegistration>` with no
|
||||
error type. A real implementation needs to handle failures. The trait returns
|
||||
`Result<Vec<HandlerRegistration>, AdapterError>` where `AdapterError` is a
|
||||
crate-level enum covering the failure modes real implementations hit:
|
||||
|
||||
- `DiscoveryFailed` — `from_call` remote unreachable / `services/list` failed
|
||||
- `SchemaParse` — `from_openapi` / `from_jsonschema` couldn't parse the spec
|
||||
- `Transport` — underlying transport error (QUIC for `from_call`, HTTP for
|
||||
`from_openapi`/`from_mcp`)
|
||||
- `Unauthorized` — HTTP 401 for `from_openapi`/`from_mcp`, auth rejected for
|
||||
`from_call`
|
||||
- `Conflict` — namespace collision in `from_call` (DC-3); reuse for other
|
||||
adapter collisions
|
||||
|
||||
The exact variant set is the two-way-door remainder (OQ-26); the *presence* of
|
||||
an error type is recorded in `client-and-adapters.md`. Pick the variants
|
||||
above as the v1 set; add a `#[non_exhaustive]` so `alknet-http`'s adapters can
|
||||
extend without breaking match arms. Use `thiserror::Error` for the derive
|
||||
(consistent with the crate's existing error types).
|
||||
|
||||
### Where the trait lives
|
||||
|
||||
The trait lives in **alknet-call** (where the types — `HandlerRegistration`,
|
||||
`OperationSpec`, `Handler` — live). The *implementations* live where their
|
||||
transport dependencies live (the adapter location map, client-and-adapters.md):
|
||||
|
||||
- `FromCall` — QUIC-backed (in `alknet-call`, task `call/client/from_call`)
|
||||
- `FromJsonSchema` — pure parse, no transport (in `alknet-call`, task
|
||||
`call/client/from-jsonschema`)
|
||||
- `FromOpenAPI` — HTTP-backed (in `alknet-http`, separate Phase 0)
|
||||
- `FromMCP` — MCP streamable-HTTP-backed (in `alknet-http`, feature-gated,
|
||||
separate Phase 0)
|
||||
|
||||
Do not implement `FromOpenAPI`/`FromMCP` here — those are `alknet-http` tasks.
|
||||
This task defines the trait + error; `from_call` and `from_jsonschema`
|
||||
implement it (in their tasks).
|
||||
|
||||
### Implementations registered in this task
|
||||
|
||||
Optionally implement a trivial `FromJsonSchema` adapter in this task if it
|
||||
falls out naturally (it's a pure-parse adapter with no transport — see
|
||||
`call/client/from-jsonschema`). If it doesn't fall out naturally, leave it for
|
||||
the `from-jsonschema` task; the trait + error alone satisfy this task's
|
||||
acceptance criteria.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `OperationAdapter` trait defined: `async fn import(&self) -> Result<Vec<HandlerRegistration>, AdapterError>`
|
||||
- [ ] Trait is `#[async_trait]` (async — ADR-017 §5, locked)
|
||||
- [ ] `AdapterError` enum defined with `#[non_exhaustive]` and `thiserror::Error`
|
||||
- [ ] `AdapterError` variants: `DiscoveryFailed`, `SchemaParse`, `Transport`, `Unauthorized`, `Conflict`
|
||||
- [ ] Trait + error are `pub` and re-exported from `lib.rs`
|
||||
- [ ] Trait is located in alknet-call (where the types live), not alknet-http
|
||||
- [ ] Doc comments link to ADR-017 §5 and client-and-adapters.md
|
||||
- [ ] Unit test: a trivial test adapter implementing the trait compiles and returns Ok
|
||||
- [ ] Unit test: a test adapter returning `Err(AdapterError::SchemaParse)` compiles
|
||||
- [ ] `cargo test -p alknet-call` succeeds
|
||||
- [ ] `cargo clippy -p alknet-call --all-targets` succeeds with no warnings
|
||||
|
||||
## References
|
||||
|
||||
- docs/architecture/crates/call/client-and-adapters.md — OperationAdapter trait §, Adapter Location Map §
|
||||
- docs/architecture/decisions/017-call-protocol-client-and-adapter-contract.md — ADR-017 §5 (the trait contract), Amendments (DC-4 resolution)
|
||||
- docs/architecture/open-questions.md — OQ-26 (AdapterError variants, two-way-door remainder)
|
||||
- docs/research/alknet-call-completion/gap-analysis.md — DC-4, implementation priority #3
|
||||
|
||||
## Notes
|
||||
|
||||
> The trait is async because from_call needs async discovery; sync adapters
|
||||
> (from_openapi reading a static spec) trivially satisfy it. The trait lives
|
||||
> in alknet-call (where the types live); implementations live with their
|
||||
> transport deps (from_call/from_jsonschema here, from_openapi/from_mcp in
|
||||
> alknet-http). The AdapterError variants are the two-way-door remainder
|
||||
> (OQ-26) — `#[non_exhaustive]` lets alknet-http extend without breaking. This
|
||||
> task is small and standalone; it unblocks alknet-http Phase 1's adapter
|
||||
> implementations. The to_* adapters are projections, not OperationAdapter
|
||||
> impls — don't implement them here.
|
||||
Reference in New Issue
Block a user