chore(deps): consume alkcall 0.7.0 — CONNECTION_CLOSED maps 503
Dependency wave + one wire-surface behavior fix: - bump alkcall 0.6 -> 0.7 (gateway feature unchanged). No code change forced: this crate constructs no ServingConfig (the one 0.7.0 struct-literal break — from_wss keeps the pure-consumer from_connection; opt-in serving remains the review-006 Part C deferred decision), and the CF-006 per-call opener-identity overlay is behavior-only upstream (hub-forwarded WS-ferry opens now resolve the end client's identity, the intended effect) - gateway error table maps CONNECTION_CLOSED -> 503 Service Unavailable: alkcall 0.7.0's CF-007 amendment makes it the eighth protocol code (ADR-016's list is formal at eight); it previously fell to the catch-all 500, misreporting a retryable, provably-undelivered call as a permanent server failure. retryable: true, so the retry_after details path applies on 503. Tests pin the mapping and the Retry-After passthrough - ADR-023 amended: the protocol-code list is eight (ALREADY_EXISTS + CONNECTION_CLOSED rows added to the §3 table, normative rule and call.error code list updated); http-adapters.md six-code references de-staled Verification: cargo test (456 passed), cargo test --all-features (589 passed), clippy -D warnings (default + all-features), fmt --check, cargo doc --no-deps (0 warnings)
This commit is contained in:
1 parent
126fdec243
commit
dbaaa4ec8c
6 files changed
+80
-22
No files matched your search
@@ -6,6 +6,33 @@ this crate adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **`alkcall` dependency bumped to 0.7.0** (CF-005/006/007 — the
|
||||||
|
connect-side caller-identity seam + the downstream-findings sweep).
|
||||||
|
No code change in this crate: it constructs no `ServingConfig`
|
||||||
|
(the one struct-literal break — `from_wss` keeps the pure-consumer
|
||||||
|
`ChannelClient::from_connection`; opt-in serving remains the
|
||||||
|
review-006 Part C deferred decision) and the per-call opener-identity
|
||||||
|
overlay (CF-006) is behavior-only upstream — hub-forwarded opens on
|
||||||
|
the WS ferry now resolve the end client's identity instead of the
|
||||||
|
hub's, the intended effect. alkcall 0.7.0's ADR-016 amendment (the
|
||||||
|
protocol-code list is eight codes) resolves the upstream half of
|
||||||
|
review-006 Part C's doc-drift note.
|
||||||
|
|
||||||
|
### Changed (wire-surface behavior)
|
||||||
|
|
||||||
|
- **Gateway error table maps `CONNECTION_CLOSED` → 503 Service
|
||||||
|
Unavailable** (the alkcall 0.7.0 CF-007 amendment makes it the eighth
|
||||||
|
protocol code; ADR-023 amended). Previously it fell to the catch-all
|
||||||
|
500, which misreported a retryable, provably-undelivered call as a
|
||||||
|
permanent server failure. Reachable through the gateway when an
|
||||||
|
imported op's forwarding handler calls into a dropped transport
|
||||||
|
(`from_wss`'s pending-call fail-all is retryable
|
||||||
|
`CONNECTION_CLOSED`). The code is `retryable: true`, so the existing
|
||||||
|
`retry_after` details path applies on 503. Tests pin the mapping and
|
||||||
|
the `Retry-After` passthrough.
|
||||||
|
|
||||||
## [0.3.0] — 2026-09-07
|
## [0.3.0] — 2026-09-07
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
Generated
+2
-2
@@ -27,9 +27,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alkcall"
|
name = "alkcall"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a44500900b82904d2a870e444051fc041a042e42ff7190803bbc5b3973f4feeb"
|
checksum = "5162a10144df65b03b887e30f77eb30d956ca1ca460a3f81b409128cbcae3199"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ h2 = ["server", "dep:hyper", "hyper-util/http2", "hyper/http2"]
|
|||||||
http1 = ["server", "dep:hyper", "hyper-util/http1", "hyper/http1"]
|
http1 = ["server", "dep:hyper", "hyper-util/http1", "hyper/http1"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
alkcall = { version = "0.6", features = ["gateway"] }
|
alkcall = { version = "0.7", features = ["gateway"] }
|
||||||
arc-swap = { version = "1", optional = true }
|
arc-swap = { version = "1", optional = true }
|
||||||
axum = { version = "0.8", optional = true, features = ["ws"] }
|
axum = { version = "0.8", optional = true, features = ["ws"] }
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
|
|||||||
@@ -4,21 +4,25 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Accepted (amended by alknet ADR-049 — protocol-level code list extended to six; alkcall ADR-021)
|
Accepted (amended by alknet ADR-049 — protocol-level code list extended to six; alkcall ADR-021; alkcall 0.7.0 CF-007 — the list is eight: `ALREADY_EXISTS` (alkcall ADR-022, mapped 409 Conflict by review 006 UP-01) and `CONNECTION_CLOSED` (alkcall ADR-016 CF-001, mapped 503 Service Unavailable in this crate's gateway error table))
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
The `OperationSpec` in the call crate (now alkcall) has `input_schema` and
|
The `OperationSpec` in the call crate (now alkcall) has `input_schema` and
|
||||||
`output_schema` but no `error_schemas`. The `call.error` payload (the alkcall
|
`output_schema` but no `error_schemas`. The `call.error` payload (the alkcall
|
||||||
crate's `call-protocol.md`) carries a `code` and `message`, where `code` is
|
crate's `call-protocol.md`) carries a `code` and `message`, where `code` is
|
||||||
one of six infrastructure codes: `NOT_FOUND`, `FORBIDDEN`, `INVALID_INPUT`,
|
one of eight infrastructure codes: `NOT_FOUND`, `FORBIDDEN`, `INVALID_INPUT`,
|
||||||
`INVALID_OPERATION_TYPE`, `INTERNAL`, `TIMEOUT`.
|
`INVALID_OPERATION_TYPE`, `INTERNAL`, `TIMEOUT`, `ALREADY_EXISTS`,
|
||||||
|
`CONNECTION_CLOSED`.
|
||||||
|
|
||||||
These six codes cover **protocol-level failures** — the call protocol
|
These eight codes cover **protocol-level failures** — the call protocol
|
||||||
itself can always fail to find an operation, deny access, reject bad input,
|
itself can always fail to find an operation, deny access, reject bad input,
|
||||||
reject the wrong dispatch method for the operation type, time out, or hit
|
reject the wrong dispatch method for the operation type, time out, hit an
|
||||||
an internal error. They are emitted by the dispatch machinery (the registry,
|
internal error, reject an `op/register` collision (`ALREADY_EXISTS`, alkcall
|
||||||
the adapter), not by operation handlers. `INVALID_OPERATION_TYPE` was added
|
ADR-022), or observe a provably-undelivered call (`CONNECTION_CLOSED`,
|
||||||
|
alkcall ADR-016 CF-001 — the only retryable protocol code). They are emitted
|
||||||
|
by the dispatch machinery (the registry, the adapter), not by operation
|
||||||
|
handlers. `INVALID_OPERATION_TYPE` was added
|
||||||
by alknet ADR-049 (streaming handler for subscriptions — now alkcall ADR-021;
|
by alknet ADR-049 (streaming handler for subscriptions — now alkcall ADR-021;
|
||||||
`invoke()` called on a `Sub`, or `invoke_streaming()` on a `Query`/`Mutation`).
|
`invoke()` called on a `Sub`, or `invoke_streaming()` on a `Query`/`Mutation`).
|
||||||
|
|
||||||
@@ -173,8 +177,8 @@ optional-array convention.
|
|||||||
|
|
||||||
- `code` — the error code. Either a protocol-level code (`NOT_FOUND`,
|
- `code` — the error code. Either a protocol-level code (`NOT_FOUND`,
|
||||||
`FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`,
|
`FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`,
|
||||||
`TIMEOUT`) or an operation-level domain code from `error_schemas` (e.g.,
|
`TIMEOUT`, `ALREADY_EXISTS`, `CONNECTION_CLOSED`) or an operation-level
|
||||||
`FILE_NOT_FOUND`, `RATE_LIMITED`).
|
domain code from `error_schemas` (e.g., `FILE_NOT_FOUND`, `RATE_LIMITED`).
|
||||||
- `message` — human-readable error message. Unstructured — for logging and
|
- `message` — human-readable error message. Unstructured — for logging and
|
||||||
debugging, not for programmatic handling. Consumers should switch on
|
debugging, not for programmatic handling. Consumers should switch on
|
||||||
`code`, not parse `message`.
|
`code`, not parse `message`.
|
||||||
@@ -190,8 +194,8 @@ optional-array convention.
|
|||||||
|
|
||||||
### 3. Protocol-level vs operation-level error codes
|
### 3. Protocol-level vs operation-level error codes
|
||||||
|
|
||||||
The six existing codes are **protocol-level** — emitted by the dispatch
|
The eight protocol-level codes are **protocol-level** — emitted by the
|
||||||
machinery, not by handlers:
|
dispatch machinery, not by handlers:
|
||||||
|
|
||||||
| Code | Emitted by | Meaning |
|
| Code | Emitted by | Meaning |
|
||||||
|------|-----------|---------|
|
|------|-----------|---------|
|
||||||
@@ -201,6 +205,8 @@ machinery, not by handlers:
|
|||||||
| `INVALID_OPERATION_TYPE` | Registry / `OperationEnv` | Wrong dispatch path for the operation's type (`invoke()` on a `Sub`, `invoke_streaming()` on a `Query`/`Mutation`, `invoke_sink()` on a `Query`/`Mutation`/`Sub`, or `OperationEnv::invoke()` on a `Sub` during composition — alkcall ADR-021; `Pub` ops dispatch via `invoke_sink()`, alkcall ADR-046) |
|
| `INVALID_OPERATION_TYPE` | Registry / `OperationEnv` | Wrong dispatch path for the operation's type (`invoke()` on a `Sub`, `invoke_streaming()` on a `Query`/`Mutation`, `invoke_sink()` on a `Query`/`Mutation`/`Sub`, or `OperationEnv::invoke()` on a `Sub` during composition — alkcall ADR-021; `Pub` ops dispatch via `invoke_sink()`, alkcall ADR-046) |
|
||||||
| `INTERNAL` | Registry / Adapter | Handler panic, unhandled error, connection failure |
|
| `INTERNAL` | Registry / Adapter | Handler panic, unhandled error, connection failure |
|
||||||
| `TIMEOUT` | Adapter | Request timed out |
|
| `TIMEOUT` | Adapter | Request timed out |
|
||||||
|
| `ALREADY_EXISTS` | Registry / collision gate | `op/register` collision — a peer-announced op may not replace the serving side's own registrations (alkcall ADR-022 G-03) |
|
||||||
|
| `CONNECTION_CLOSED` | Client write path | The `call.requested` frame could not be written — the call is provably undelivered (alkcall ADR-016 CF-001). `retryable: true` — the only protocol code a caller may auto-retry. |
|
||||||
|
|
||||||
Operation-level domain codes are emitted by **handlers** — the operation's
|
Operation-level domain codes are emitted by **handlers** — the operation's
|
||||||
own logic determines what went wrong. They are declared in `error_schemas`
|
own logic determines what went wrong. They are declared in `error_schemas`
|
||||||
@@ -251,9 +257,9 @@ accordingly.
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Normative rule (review #002 W20)**: `from_openapi` must not produce error
|
**Normative rule (review #002 W20)**: `from_openapi` must not produce error
|
||||||
codes that collide with the six protocol-level codes (`NOT_FOUND`,
|
codes that collide with the protocol-level codes (`NOT_FOUND`,
|
||||||
`FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`,
|
`FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`,
|
||||||
`TIMEOUT`). The adapter prefixes
|
`TIMEOUT`, `ALREADY_EXISTS`, `CONNECTION_CLOSED`). The adapter prefixes
|
||||||
imported error codes with `HTTP_` and the status number (e.g., `HTTP_404`,
|
imported error codes with `HTTP_` and the status number (e.g., `HTTP_404`,
|
||||||
`HTTP_429`) to avoid collision. This is a requirement for the adapter, not
|
`HTTP_429`) to avoid collision. This is a requirement for the adapter, not
|
||||||
a naming convention — the `from_openapi` example above was previously shown
|
a naming convention — the `from_openapi` example above was previously shown
|
||||||
|
|||||||
@@ -553,8 +553,9 @@ for the factoring recommendation (thin shared struct, not a trait).
|
|||||||
`from_openapi` maps OpenAPI non-2xx response status codes to
|
`from_openapi` maps OpenAPI non-2xx response status codes to
|
||||||
`ErrorDefinition`s (alkcall ADR-016 §5). The normative rule (review
|
`ErrorDefinition`s (alkcall ADR-016 §5). The normative rule (review
|
||||||
#002 W20): `from_openapi` must not produce error codes that collide
|
#002 W20): `from_openapi` must not produce error codes that collide
|
||||||
with the six protocol-level codes (`NOT_FOUND`, `FORBIDDEN`,
|
with the protocol-level codes (`NOT_FOUND`, `FORBIDDEN`,
|
||||||
`INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`, `TIMEOUT`). The
|
`INVALID_INPUT`, `INVALID_OPERATION_TYPE`, `INTERNAL`, `TIMEOUT`,
|
||||||
|
`ALREADY_EXISTS`, `CONNECTION_CLOSED`). The
|
||||||
adapter prefixes imported error codes with `HTTP_` and the status
|
adapter prefixes imported error codes with `HTTP_` and the status
|
||||||
number:
|
number:
|
||||||
|
|
||||||
@@ -601,7 +602,7 @@ request-level cap failure (422; GW-16 unified it with the
|
|||||||
`INVALID_INPUT → 422` mapping — it was hand-rolled as 400 before
|
`INVALID_INPUT → 422` mapping — it was hand-rolled as 400 before
|
||||||
review-002). `BatchResultEntry.error` references
|
review-002). `BatchResultEntry.error` references
|
||||||
the `BatchError` component (review-002 PRJ-16b): the serialized
|
the `BatchError` component (review-002 PRJ-16b): the serialized
|
||||||
`CallError` as a oneOf over the six protocol-code envelopes plus a
|
`CallError` as a oneOf over the protocol-code envelopes plus a
|
||||||
generic arm carrying the operation-declared codes. See alkcall ADR-016.
|
generic arm carrying the operation-declared codes. See alkcall ADR-016.
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
|
|||||||
+27
-3
@@ -4,9 +4,11 @@
|
|||||||
//! (`NOT_FOUND`, `FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`,
|
//! (`NOT_FOUND`, `FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`,
|
||||||
//! `TIMEOUT`, `ALREADY_EXISTS` — the seventh code, alkcall ADR-022's
|
//! `TIMEOUT`, `ALREADY_EXISTS` — the seventh code, alkcall ADR-022's
|
||||||
//! `op/register` collision policy, mapped 409 Conflict, review-006
|
//! `op/register` collision policy, mapped 409 Conflict, review-006
|
||||||
//! UP-01) map to fixed statuses; operation-level codes imported from
|
//! UP-01; `CONNECTION_CLOSED` — the eighth code, alkcall ADR-016's
|
||||||
//! external HTTP APIs are prefixed `HTTP_<status>` and map to their
|
//! retryable undelivered-call code, mapped 503 Service Unavailable,
|
||||||
//! declared status.
|
//! alkcall 0.7.0's CF-007 amendment made the list formal) map to fixed
|
||||||
|
//! statuses; operation-level codes imported from external HTTP APIs are
|
||||||
|
//! prefixed `HTTP_<status>` and map to their declared status.
|
||||||
//!
|
//!
|
||||||
//! The identity-aware variant maps the ambiguous protocol codes
|
//! The identity-aware variant maps the ambiguous protocol codes
|
||||||
//! (`FORBIDDEN`, `INVALID_OPERATION_TYPE`) to `401` when no token
|
//! (`FORBIDDEN`, `INVALID_OPERATION_TYPE`) to `401` when no token
|
||||||
@@ -30,6 +32,7 @@ const PROTOCOL_CODE_INVALID_INPUT: &str = "INVALID_INPUT";
|
|||||||
const PROTOCOL_CODE_INVALID_OPERATION_TYPE: &str = "INVALID_OPERATION_TYPE";
|
const PROTOCOL_CODE_INVALID_OPERATION_TYPE: &str = "INVALID_OPERATION_TYPE";
|
||||||
const PROTOCOL_CODE_TIMEOUT: &str = "TIMEOUT";
|
const PROTOCOL_CODE_TIMEOUT: &str = "TIMEOUT";
|
||||||
const PROTOCOL_CODE_ALREADY_EXISTS: &str = "ALREADY_EXISTS";
|
const PROTOCOL_CODE_ALREADY_EXISTS: &str = "ALREADY_EXISTS";
|
||||||
|
const PROTOCOL_CODE_CONNECTION_CLOSED: &str = "CONNECTION_CLOSED";
|
||||||
const PROTOCOL_CODE_INTERNAL: &str = "INTERNAL";
|
const PROTOCOL_CODE_INTERNAL: &str = "INTERNAL";
|
||||||
|
|
||||||
const HTTP_PREFIX: &str = "HTTP_";
|
const HTTP_PREFIX: &str = "HTTP_";
|
||||||
@@ -40,6 +43,7 @@ const STATUS_FORBIDDEN: u16 = 403;
|
|||||||
const STATUS_UNPROCESSABLE: u16 = 422;
|
const STATUS_UNPROCESSABLE: u16 = 422;
|
||||||
const STATUS_TIMEOUT: u16 = 504;
|
const STATUS_TIMEOUT: u16 = 504;
|
||||||
const STATUS_CONFLICT: u16 = 409;
|
const STATUS_CONFLICT: u16 = 409;
|
||||||
|
const STATUS_SERVICE_UNAVAILABLE: u16 = 503;
|
||||||
const STATUS_INTERNAL: u16 = 500;
|
const STATUS_INTERNAL: u16 = 500;
|
||||||
|
|
||||||
const RETRY_AFTER_STATUSES: &[u16] = &[429, 503];
|
const RETRY_AFTER_STATUSES: &[u16] = &[429, 503];
|
||||||
@@ -77,6 +81,7 @@ pub fn call_error_to_http_status_with_identity(
|
|||||||
}
|
}
|
||||||
PROTOCOL_CODE_TIMEOUT => STATUS_TIMEOUT,
|
PROTOCOL_CODE_TIMEOUT => STATUS_TIMEOUT,
|
||||||
PROTOCOL_CODE_ALREADY_EXISTS => STATUS_CONFLICT,
|
PROTOCOL_CODE_ALREADY_EXISTS => STATUS_CONFLICT,
|
||||||
|
PROTOCOL_CODE_CONNECTION_CLOSED => STATUS_SERVICE_UNAVAILABLE,
|
||||||
PROTOCOL_CODE_INTERNAL => STATUS_INTERNAL,
|
PROTOCOL_CODE_INTERNAL => STATUS_INTERNAL,
|
||||||
code if code.starts_with(HTTP_PREFIX) => code[HTTP_PREFIX.len()..]
|
code if code.starts_with(HTTP_PREFIX) => code[HTTP_PREFIX.len()..]
|
||||||
.parse::<u16>()
|
.parse::<u16>()
|
||||||
@@ -193,6 +198,25 @@ mod tests {
|
|||||||
assert_eq!(call_error_to_http_status(&error), 409);
|
assert_eq!(call_error_to_http_status(&error), 409);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn connection_closed_maps_to_503() {
|
||||||
|
let error = CallError::connection_closed("undelivered");
|
||||||
|
assert_eq!(call_error_to_http_status(&error), 503);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn connection_closed_carries_retry_after_when_details_present() {
|
||||||
|
let error = CallError::connection_closed("undelivered")
|
||||||
|
.with_details(serde_json::json!({ "retry_after": "5" }));
|
||||||
|
let resp = call_error_to_http_response(&error);
|
||||||
|
assert_eq!(resp.status(), 503);
|
||||||
|
let retry_after = resp
|
||||||
|
.headers()
|
||||||
|
.get(header::RETRY_AFTER)
|
||||||
|
.map(|v| v.to_str().unwrap().to_string());
|
||||||
|
assert_eq!(retry_after.as_deref(), Some("5"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn internal_maps_to_500() {
|
fn internal_maps_to_500() {
|
||||||
let error = CallError::internal("boom");
|
let error = CallError::internal("boom");
|
||||||
|
|||||||
Reference in new issue
Block a user