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:
glm-5.3-flash committed 2026-09-07 11:26:12 +00:00
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";
+64 -2
View File
@@ -78,11 +78,73 @@ Format: date | found-in (alkhttp context) | severity | status.
consumer can `set_identity` before wrapping).
- The corollary (`register_openable`'s closed-over
establishment-time `AuthContext` vs the per-call opener identity)
remains open as a design follow-up — it affects the establisher /
pump handler seam, not the ACL gate; tracked separately.
was resolved in the same batch (2026-09-07, CF-006 below).
- **Status:** resolved — 2026-09-07 (regression gates: `cf005_*`
tests in `src/channels/client.rs`).
### CF-006 — open-op establisher/pump handler receive the install-time `AuthContext`, never the per-call opener's identity (CF-005 corollary) (2026-09-07) — RESOLVED 2026-09-07
- **Found in:** the CF-005 verification pass (same alktunnels
reverse-flow POC W1 material; the ledger's CF-005 entry named it as
a "design follow-up, tracked separately"). `run_open_wrapper`
(`src/channels/operations.rs`) closed over the
`install_channel_zero`-time `AuthContext` and passed `auth.clone()`
verbatim to both the establisher (ADR-049 §1) and the pump handler
(`OpenHandler`'s 4th parameter). The per-call opener's identity —
the same identity the ACL gate and the per-identity cap check saw —
reached neither hook. On a per-connection registry (ADR-047 §4)
install-time identity IS the caller's, so the gap was invisible
there; it shows on hub-forwarded opens (the establisher saw the
hub, not the end client) and on the connect-side serving path
(token- or `ServingConfig.identity`-resolved callers). alktty
review 002 R3 independently recorded the same seam (closed
per-connection by design, with the "registry must stay
per-connection" constraint).
- **Impact:** an establisher doing per-principal authorization or
ownership checks on the backend could not see the actual opener;
the pump handler tagging sessions by principal saw the hub.
- **Fix (2026-09-07):** `run_open_wrapper` derives a **per-call
`AuthContext`** — the opener's dispatch-resolved identity overlaid
onto the install-time context — and passes it to both the
establisher and the pump handler. When the call has no resolved
identity, the install-time identity is kept (anonymous-keep; no
synthetic-`anonymous` rewrite of a real install-time identity).
Transport-truthful fields (`alpn`, `remote_addr`,
`tls_client_fingerprint`) are never rewritten. Signatures are
unchanged (`OpenEstablisher`, `OpenHandler` still take
`AuthContext`) — behavior-only, additive; downstream code compiles
unchanged. Regression gates:
`open_wrapper_overlays_per_call_identity_on_install_time_auth` +
`open_wrapper_keeps_install_time_identity_when_call_identityless`
in `src/channels/operations.rs`.
- **Also in this batch:** `ChannelPlan`'s type doc now spells out the
`Send + Sync` payload constraint (alktunnels POC F-1 re-derived it
by compiler error — a doc line prevents the next consumer repeating
that).
- **Status:** resolved — 2026-09-07.
### CF-007 — ADR-016's protocol-code list is stale: omits `ALREADY_EXISTS` and `CONNECTION_CLOSED` (2026-09-07) — RESOLVED 2026-09-07
- **Found in:** alkhttp review 006, Part C
(`alkhttp/docs/reviews/006-alkcall-0.3.0-consequence-review.md`
L207–233, L319–324 — "upstream doc drift … flagged for the next
alkcall doc pass"). ADR-016 said "six codes" and its table listed
only the original six, while the wire vocabulary had grown:
`ALREADY_EXISTS` (ADR-022 collision sub-amendment, 0.3.0) and
`CONNECTION_CLOSED` (CF-001, 0.4.x, the retryable
provably-undelivered-call code). Downstream consumers already
handle both; the authoritative ADR was the thing that was wrong.
- **Fix (2026-09-07):** ADR-016 amended — Status notes the 2026-09-07
extension to eight; the Context paragraph and the §3 table carry
both codes with their semantics (`ALREADY_EXISTS` non-retryable,
registration state; `CONNECTION_CLOSED` retryable — the only
protocol code a caller may auto-retry), a §2a subsection documents
the undelivered-vs-ambiguous write-failure distinction, the
`from_openapi` collision rule lists all eight, and the
cross-references cite the two amendments. Doc-only; the wire
surface is unchanged.
- **Status:** resolved — 2026-09-07.
---
### CF-004 — `services_schema_handler` discloses Internal/ACL-restricted op specs — no visibility or AccessControl check (2026-08-30) — RESOLVED 2026-08-31