feat(cf-006/cf-007): per-call opener identity for open-op hooks; ADR-016 code-list sweep
Batch-fixes every remaining open alkcall-side finding from downstream consumers so 0.7.0 is the only release they need to absorb. - CF-006 (the CF-005 corollary): run_open_wrapper derives a per-call AuthContext — the opener's dispatch-resolved identity (the same identity the ACL gate and cap check saw) overlaid onto the install-time context — and passes it to both the establisher and the pump handler. Identity-less calls keep the install-time identity (no synthetic-anonymous rewrite); transport-truthful fields are never rewritten. Signatures unchanged — behavior-only; identical on per-connection registries, hub-forwarded opens now show the end client. Gates: open_wrapper_overlays_per_call_identity_on_install_time_auth + open_wrapper_keeps_install_time_identity_when_call_identityless. - CF-007 (alkhttp review 006 Part C doc drift): ADR-016 amended to the eight-code list — ALREADY_EXISTS + CONNECTION_CLOSED in the Context, §3 table, and from_openapi collision rule; new §2a documents the undelivered-vs-ambiguous write-failure distinction. - ChannelPlan type doc now states the Send + Sync payload constraint (alktunnels POC F-1 re-derived it by compiler error). - Ledger: CF-006 and CF-007 filed + resolved; CF-005's corollary note points at CF-006; the Open section is empty. ADR-049 gains the per-call-identity note. Changelog 0.7.0 covers the batch. Sweep result (all downstream reviews): no other open alkcall items — alktunnels W3/F-2 are downstream-by-design, alktty R3/P14 are closed constraints, alknet has none; OQ-24/37/39/40/41 stay deferred-by-design (no consumer pull yet). Verification: cargo test (631) + --all-features (648), clippy (all-targets, all-features, -D warnings), fmt --check, doc --no-deps, wasm32 check, semver-checks (no update required), publish --dry-run.
This commit is contained in:
1 parent
db5530ed34
commit
ed741c34e0
5 files changed
+324
-79
No files matched your search
@@ -2,23 +2,30 @@
|
||||
|
||||
## Status
|
||||
|
||||
Accepted (amended by ADR-021 — protocol-level code list extended to six)
|
||||
Accepted (amended by ADR-021 — protocol-level code list extended to six;
|
||||
amended 2026-09-07 — extended to eight, see §2a below)
|
||||
|
||||
## Context
|
||||
|
||||
The `OperationSpec` in alknet-call has `input_schema` and `output_schema` but
|
||||
no `error_schemas`. The `call.error` payload (call-protocol.md L128–134)
|
||||
carries a `code` and `message`, where `code` is one of six infrastructure
|
||||
carries a `code` and `message`, where `code` is one of eight infrastructure
|
||||
codes: `NOT_FOUND`, `FORBIDDEN`, `INVALID_INPUT`, `INVALID_OPERATION_TYPE`,
|
||||
`INTERNAL`, `TIMEOUT`.
|
||||
`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,
|
||||
reject the wrong dispatch method for the operation type, time out, or hit
|
||||
an internal error. They are emitted by the dispatch machinery (the registry,
|
||||
reject the wrong dispatch method for the operation type, time out, hit an
|
||||
internal error, reject a registration collision, or report a provably
|
||||
undelivered call. They are emitted by the dispatch machinery (the registry,
|
||||
the adapter), not by operation handlers. `INVALID_OPERATION_TYPE` was added
|
||||
by ADR-021 (streaming handler for subscriptions — `invoke()` called on a
|
||||
`Subscription`, or `invoke_streaming()` on a `Query`/`Mutation`).
|
||||
`ALREADY_EXISTS` was added by the ADR-022 collision sub-amendment
|
||||
(2026-09-04 — `op/register` rejects a peer-announce collision; non-retryable,
|
||||
registration is state). `CONNECTION_CLOSED` was added by the consumer
|
||||
findings ledger CF-001 (2026-08-30 — write failures on provably undelivered
|
||||
`call.requested` frames; **retryable** — see §2a).
|
||||
|
||||
But operations also have **domain-level failures** that are not covered:
|
||||
|
||||
@@ -186,7 +193,7 @@ optional-array convention.
|
||||
|
||||
### 3. Protocol-level vs operation-level error codes
|
||||
|
||||
The six existing codes are **protocol-level** — emitted by the dispatch
|
||||
The eight existing codes are **protocol-level** — emitted by the dispatch
|
||||
machinery, not by handlers:
|
||||
|
||||
| Code | Emitted by | Meaning |
|
||||
@@ -197,6 +204,20 @@ machinery, not by handlers:
|
||||
| `INVALID_OPERATION_TYPE` | Registry / `OperationEnv` | Wrong dispatch path for the operation's type (`invoke()` on a `Subscription`, `invoke_streaming()` on a `Query`/`Mutation`, or `OperationEnv::invoke()` on a `Subscription` during composition — ADR-021) |
|
||||
| `INTERNAL` | Registry / Adapter | Handler panic, unhandled error, connection failure |
|
||||
| `TIMEOUT` | Adapter | Request timed out |
|
||||
| `ALREADY_EXISTS` | Registry (`op/register` collision gate) | A peer-announce or import collides with an existing registration (`replace: false`, or a serving-side name — ADR-022). Non-retryable without `replace: true`. |
|
||||
| `CONNECTION_CLOSED` | Client write path | The `call.requested` frame could not be written — the call is provably undelivered (CF-001). `retryable: true` — the only protocol code a caller may auto-retry. Mid-publish and completed-frame write failures stay `INTERNAL` (delivery ambiguous). |
|
||||
|
||||
#### 2a. `CONNECTION_CLOSED` — the retryable undelivered-call code (CF-001)
|
||||
|
||||
The write-failure mapping distinguishes **provably undelivered** from
|
||||
**delivery-ambiguous**: a failed write of the *request* frame means the
|
||||
producer never saw the call — reconnect/retry is safe. A failed write
|
||||
*after* delivery started (mid-publish, completed frame) is ambiguous —
|
||||
retry unsafe — and stays `INTERNAL`. The producer-side `fail_all(...)` on
|
||||
connection close also stays `INTERNAL` (the callee cannot know what the
|
||||
caller received). The code is additive to the wire vocabulary; consumers
|
||||
treat unknown codes per their existing policy, with the `retryable` flag
|
||||
as the machine-readable signal.
|
||||
|
||||
Operation-level domain codes are emitted by **handlers** — the operation's
|
||||
own logic determines what went wrong. They are declared in `error_schemas`
|
||||
@@ -247,9 +268,9 @@ accordingly.
|
||||
```
|
||||
|
||||
**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`,
|
||||
`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`,
|
||||
`HTTP_429`) to avoid collision. This is a requirement for the adapter, not
|
||||
a naming convention — the `from_openapi` example above was previously shown
|
||||
@@ -410,6 +431,10 @@ enum instead of a generic `Result<Output, string>`.
|
||||
- ADR-021: Streaming handler for subscriptions (amends this ADR's
|
||||
protocol-level code list — `INVALID_OPERATION_TYPE` added as the sixth
|
||||
protocol-level code)
|
||||
- ADR-022 (collision sub-amendment 2026-09-04): `ALREADY_EXISTS` — the
|
||||
`op/register` collision rejection code
|
||||
- Consumer findings ledger CF-001 (2026-08-30): `CONNECTION_CLOSED` —
|
||||
the retryable provably-undelivered-call code
|
||||
- docs/sdd_process.md L19, L423 (Safe Exit protocol — the general principle
|
||||
of making failure typed and declared)
|
||||
- TypeScript reference: `/workspace/@alkdev/operations/src/types.ts`
|
||||
|
||||
@@ -332,6 +332,15 @@ implementation detail; the wire surface is unchanged from §1/§3):
|
||||
`Fn(Value, AuthContext) -> BoxFuture<'static,
|
||||
Result<Establishment, EstablishmentError>>`; the `Connection`
|
||||
belongs exclusively to the `OpenHandler` (unchanged).
|
||||
**Identity semantics (CF-005 corollary, 2026-09-07):** the
|
||||
`AuthContext` the establisher and the pump handler receive is the
|
||||
**per-call** context — the opener's dispatch-resolved identity (the
|
||||
same identity the ACL gate and the per-identity cap check saw)
|
||||
overlaid onto the install-time context; transport-truthful fields
|
||||
(`alpn`, `remote_addr`, `tls_client_fingerprint`) stay
|
||||
install-time. On a per-connection registry (ADR-047 §4) this is
|
||||
identical to the install-time context; on hub-forwarded opens the
|
||||
establisher sees the end client, not the hub.
|
||||
2. **The bound is the earlier of the dispatch deadline and the
|
||||
per-registration timeout.** §2 names the dispatch deadline "when
|
||||
the `OperationContext` carries one, else the crate constant";
|
||||
|
||||
Reference in new issue
Block a user