Files
alkhttp/docs/reviews/006-alkcall-0.3.0-consequence-review.md
T
glm-5.3-flash 802d94ec07 docs(review 007 Unit 2): WS-31 discovery-shadowing note + record corrections
- WS-31: websocket.md §"Data channels for browsers" + ADR-067's landed
  note record that WS-session discovery is the bootstrap set — the
  hook's bootstrap `services/*` registrations overwrite a
  base-registry `services/*` registration on the WS path by design
  (a deployment's custom `services/list` is shadowed on WS sessions
  only).
- ADR-048's landed note: correction + completion — the WS-26
  retention sentence was aspirational at the landed commit (WS-28) and
  is now real; the UP-02 posture's override half is now an explicit
  surface (`with_ws_op_register_acl` / `OpRegisterAcl`), with the
  note that `ChannelsPolicy` could not carry an op ACL.
- ADR-067's landed note: review-007 notes (WS-28 fix + gate, WS-29
  surface, WS-31 record).
- OQ-05 resolution: the retention claim carries the WS-28 correction.
- review-006 UP-02 log + WS-26 paragraph: corrections marking what the
  pre-fix tree did not have, with the landed remediation named.
- review-002 WS-17: the "bounded at 64 sessions" claim corrected —
  the bare-registry semaphore was per-request and bounded nothing;
  `SessionSlots` is the shared-cap surface.
- review-007 status: open for remediation → remediated, with the
  decisions taken (both "implement" options) and the gate names.

Verification: cargo test 454 passed / 0 failed; cargo doc --no-deps
clean (6 pre-existing warnings, identical at baseline).

Review: docs/reviews/007-ws-data-channel-surface-review.md
2026-09-05 05:45:31 +00:00

569 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Review 006 — alkcall 0.3.0 consequence review (WS-24/WS-25 upstream, alkhttp residual)
## Status
All units landed: Unit 2 + Unit 3 remediated and verified (`030c5ef`);
Unit 4 (docs reconciliation) landed with the 0.3.1 consumption
(`Cargo.lock` bump + the `services/list-peers` discovery gate). UP-02
decided (recorded below). UP-03 fixed in alkcall 0.3.1 (`fd21230`,
ADR-030) and asserted by the extended WS-path gate. See Remediation
log.
## Scope
Consequence review of alkcall 0.3.0 (published 2026-09-04 — the
remediation of alkcall reviews 004005, which is the upstream
resolution of this repo's review-003 findings WS-24/WS-25) against
this crate. The pass answers three questions:
1. What does the 0.3 bump itself cost here (source breaks, behavior
changes)?
2. Which review-003 findings are now resolved upstream, and which
remain alkhttp-side work?
3. Did the upstream remediation introduce anything this crate must
absorb (new protocol codes, new wiring contracts, new decisions)?
Sources read: alkcall review 004 (`docs/reviews/004-…md`, remediated)
and review 005 (`docs/reviews/005-…md`, resolved G-01..G-05), the
0.3.0 changelog, the ADR amendments (alkcall ADR-047 §4 amendment #2,
ADR-022 amendments 2026-09-03 + 2026-09-04), and the published
0.3.0 crate source. Every claim below verified in source at this
tree (`df86f89`, consuming alkcall 0.3.0) or the published crate.
Findings use the new prefix `UP` (upstream-consequence); review 003's
WS numbering is referenced for continuity.
## Baseline verification (this pass)
```
cargo test → 453 passed, 0 failed
cargo test --all-features → 575 passed, 0 failed
cargo clippy --all-targets -- -D warnings → clean
cargo clippy --all-features --all-targets -- -D warnings → clean
cargo fmt --check → clean
cargo doc --no-deps → clean
```
Verified after the 0.2 → 0.3 bump (`3dee46a`). The bump cost was
two source-break classes, both mechanical:
- `ChannelClient::take_call_connection` now returns
`Option<Arc<CallConnection>>` (was a bare `CallConnection`) —
`WssSession::connect` dropped its `Arc::new` re-wrap
(`src/adapters/from_wss.rs:268-274`).
- `OperationRegistry::register`, `ChannelOperations::register_on`,
and `ChannelCore::register_openable` now take
`&OperationRegistry` (was `&mut`) — 51 stale `mut` bindings
dropped via `clippy --fix` across `src/adapters/{from_wss,to_mcp,
to_openapi}.rs`, `src/gateway/routes.rs`, `src/server/adapter.rs`,
and `tests/`.
The `&self`-registration change is the interior-mutability change
that makes the per-session fork surface possible; for every existing
alkhttp call site it is source-compatible (reborrow). The
`registration()` → owned and `list_operations()` → owned return-type
changes required no edits here (call sites already cloned through
the borrow or consumed by value-compatible iteration — verified by
the clean build).
## Verdict
- **WS-24 and WS-25 are resolved upstream.** The two design gaps
review 003 Part B identified are now mechanized in alkcall 0.3.0
and decided in its ADRs: the per-session fork is the
dispatch-registry mechanism (alkcall ADR-047 §4 amendment #2
review-003 option (a), the wire-proven shape), and `op/register`
is the client-side registration mechanism (alkcall ADR-022
amendment 2026-09-03 — review-003's bootstrap-op candidate). The
alkhttp-side consequences of review-003 Unit 1's candidates are
exactly what landed.
- **One upstream remediation result lands here for free:** review
005 Unit 1 mechanized the accept-side pending-resolution arms into
`run_loop_single_stream` (the loop our WS channel-0 dispatch
runs). Pre-0.3, an accept-side nested-composing handler (a
`from_call` imported-op stub riding the WS connection) could
deadlock against the inline dispatch; post-0.3 the loop spawns
Once invocations and Sub pumps, so the G-01 hazard class is shut
for WS sessions by the bump alone — no alkhttp code involved.
- **The remaining review-003 findings are all still open and now
unblocked** (WS-20/21/22/26 mechanical, WS-27 cleanup, WS-23
tests): the hook still discards the `ChannelManager`
(`src/websocket/upgrade.rs:255`), no channel ops / openables /
bootstrap discovery are registered, no deployment surface declares
openable ALPNs, and no live connection handle is retained. All
grep-verified at this tree.
- **One new finding** (UP-01): alkcall 0.3 introduced the
`ALREADY_EXISTS` protocol code; this crate's gateway error table
has no arm for it and would map it to 500. Unreachable today
(only `op_register_handler` emits it), load-bearing the moment
Unit 2 wires `op/register`.
- **One wiring contract Unit 2 must honor** (UP-02, recorded as a
decision point, not a defect): the `op/register` collision policy
binds the WS session's handler to the session fork.
## Severity legend
Same scale as review 003: **[major]** — a decided behavior is
missing/wrong or a real hazard; **[minor]** — drift, convention
violation, or doc/spec inconsistency with no correctness impact.
---
# Part A — What 0.3.0 resolves for this crate
## WS-24 [was major, cross-crate] — dispatch-resolution mechanism → RESOLVED upstream
alkcall ADR-047 §4 amendment #2 (2026-09-03) decides option (a): the
`install_channel_zero` hook forks the deployment's base registry,
registers the per-session ops on the fork (`ChannelOperations::
register_on`, `ChannelCore::register_openable`,
`install_bootstrap_discovery` — the last closed over the fork so
`services/list` sees per-session openables, review-004 F-06), and
dispatches channel 0 over the fork. The connection overlay remains
the peer-announced-op landing zone and nested-invocation target, not
the dispatch-resolution path. The reference shape is alkcall's
`fork_registry_open_op_resolves_and_is_discoverable` gate
(`alkcall/src/channels/client.rs:1994` — fork → `register_openable`
`install_bootstrap_discovery``Dispatcher::new(fork, …)`), which
is the shape Unit 2 must reproduce in `install_channel_zero`.
Upstream also resolved the fork's discovery half (review-004 F-06):
`install_bootstrap_discovery` registers `services/list`,
`services/list-peers`, and `services/schema` closed over the fork
itself, so per-session openables are discoverable without a
re-registration walk. The bootstrap set is closed at four with
`op/register` (alkcall ADR-022 amendment, `services/list-peers`
added 2026-09-04 per review-005 G-05).
**alkhttp consequence:** none to code yet; Unit 2 consumes it.
## WS-25 [was major, cross-crate] — client-side op registration → RESOLVED upstream
alkcall ADR-022 amendment (2026-09-03) decides the bootstrap-op
mechanism: `op/register` (`registry::op_register` module) — a peer
announces an op spec over channel 0; the hub's per-session handler
rebuilds the spec, wraps it as a forwarding stub that nested-calls
back over channel 0, and lands it in the connection overlay via
`register_imported` (forced `Visibility::Internal`, provenance
`FromCall`). The wire stays the six `call.*` envelope kinds.
Collision policy (2026-09-04 sub-amendment, review-005 G-03): an
announced op may replace other *announced* ops (`replace` governs)
but never the serving side's own registrations — `ALREADY_EXISTS`
regardless of `replace`; the handler gates on the serving registry
first, then the overlay.
The serving half it composes on (`Dispatcher::serve_single_stream`,
opt-in via `ChannelClient::from_connection_with_serving`) landed
with review-005 Unit 1's concurrency rework: Once invocations, Sub
pumps, and sink response writers are spawned in both single-stream
loops, so same-connection nested composition (the flagship
hub-composes-peer-op flow) resolves concurrently instead of
deadlocking to the 30 s sweeper (review-005 G-01, empirically
verified upstream with a since-removed probe, productized as two
5s-bounded gates).
**alkhttp consequences:** Unit 2 wires `op/register` on WS sessions
by registering `op_register_spec(acl)` +
`op_register_handler(connection, fork)` on the per-session fork
(the collision set is the fork — exactly what the session
dispatches over, mirroring the upstream gates' production shape).
Two additional facts from review 005 apply:
- The channel-0 `Connection` must carry the peer identity for
`compose_root_env` to attach the connection's overlay (keyed by
`identity.id`). alkhttp's hook already does this
(`src/websocket/upgrade.rs:264` — identity propagated from the
upgrade's `AuthContext`); verified correct as-is.
- The serving loop's concurrency means a hub handler that composes
an announced op resolves live (no sweeper); no alkhttp-side
scheduling work is needed.
ADR-048's browser bidirectionality promise is now mechanized on the
hub half; the browser half (announcing via WS framing) is a
client-side concern with no alkhttp code beyond serving the op.
The re-point edits in ADR-048/OQ-05 (committed `df86f89`) record
this.
## Fixed-upstream items this crate inherits
- **Accept-side nested-composition hazard** (review-005 G-01's
second consequence): mechanized shut by the pending-resolution
arms now in `run_loop_single_stream` — inherited by the bump, no
alkhttp code.
- **`resource_id_path` wire round-trip** (review-005 G-04): a
`from_call`-imported or announced op declaring ownership-scoped
resource extraction previously rebuilt with `resource_id: None`
(ACL checks ran without the resource id). Both round-trip halves
now carry the field. `from_wss`'s import path inherits the fix.
- **Head-of-line blocking / interleaved directions**: a serving
side's outbound calls resolve while it serves a Sub (the
interleaved-directions gate upstream) — relevant to any future
serving-enabled `from_wss` (see Part C).
---
# Part B — Findings at this tree
## UP-01 [minor] — The `ALREADY_EXISTS` protocol code is unmapped in the gateway error table
**Verified:** YES. alkcall 0.3 added `CallError::already_exists`
(`"ALREADY_EXISTS"`, non-retryable —
`alkcall-0.3.0/src/protocol/wire.rs:124`) for the `op/register`
collision policy; its own ADR-022 amendment records the code.
alkhttp's status map (`src/gateway/error.rs:57-80`) has no arm for
it: `NOT_FOUND`, `FORBIDDEN`, `INVALID_INPUT`,
`INVALID_OPERATION_TYPE`, `TIMEOUT`, `INTERNAL`, and `HTTP_*`
prefixed codes are mapped; anything else falls to `STATUS_INTERNAL`
(500). alkcall's ADR-016 code list (six codes) was not amended for
the seventh — upstream doc drift, noted for that repo.
**Reachability today:** none — the only emitter is
`op_register_handler`, which no alkhttp path invokes yet (grep
verified; the gateway dispatches base-registry ops directly). It
becomes reachable exactly when Unit 2 wires `op/register` on WS
sessions, at which point a collision rejection would surface to
wire callers as 500 with an `ALREADY_EXISTS` body instead of a
conflict status. Because the collision path is also how a peer
experiences the reconnect-reannounce contract (`replace` refused
for serving-registry names), fidelity matters there.
**Fix (Unit 2, ride-along):** map `ALREADY_EXISTS` → 409 Conflict in
`call_error_to_http_status_with_identity` (plus the test arm),
matching ADR-023's `HTTP_<status>` discipline for the new code.
One-line change + test; no consumer breaks (the code is new).
## UP-02 [minor, decision point] — `op/register`'s AccessControl on the WS surface needs a decided default
Not a defect — a decision Unit 2 cannot avoid. alkcall's
`op_register_spec(access_control)` takes the ACL as a parameter; its
doc notes the default (`AccessControl::default()`) lets any peer
register, and the op is `Mutation`/`External`-typed. This crate's
convention (AGENTS.md §13, ADR-015): adapter-registered ops are
`Visibility::Internal` by default — but `op/register` cannot be
Internal on the wire surface (the *peer* must call it; Internal ops
resolve spec-404 to wire callers). The analogous precedent here is
the built-in gateway's `NoCap` posture for channel opens (review-001
SRV-10: crate default is permissive, deployments gate via the
`ChannelsPolicy` extension). The consistent shape: register
`op/register` on the per-session fork with `AccessControl::default()`
on the built-in surface and let an assembly-layer override pass a
stricter ACL the same way `ChannelsPolicy`/`OpenableAlpn` flow —
record the posture in the Unit-2 task and in ADR-048's amendment
note when the wiring lands.
## WS-20/WS-21/WS-22/WS-26 [major] — unchanged, now unblocked
Re-verified at this tree; all four stand as review 003 found them:
- `install_channel_zero` still discards the manager
(`src/websocket/upgrade.rs:255``_manager` unused); no
`ChannelCore`, no `ChannelOperations::register_on` (grep: zero
callers in this crate), no openables.
- No `OpenableAlpn` surface exists (`src/server/adapter.rs` builders
unchanged: `with_decoy` / `with_extra_routes` /
`with_ws_max_sessions` / `with_ws_idle_timeout`).
- No live `Arc<CallConnection>` handle is retained by the session
(the hook moves channel 0's connection into the spawned task);
`WsSessions` still tracks pump handles only.
The fix shape from review 003 Unit 2 stands, now with the upstream
mechanism named concretely: per-session `fork()` in the hook →
`ChannelOperations::register_on(&fork)` → openables via
`register_openable(spec, handler, &fork, auth)`
`install_bootstrap_discovery(&fork)``op_register_spec` +
`op_register_handler(Arc<CallConnection>, fork)` → dispatch over the
fork (`Dispatcher::run_loop_single_stream` unchanged as the loop).
The `OpenableAlpn` list + `with_ws_openable_alpns` threading
(RouterState → SessionState → hook, request-extension fallback
mirroring `ChannelsPolicy`) is unchanged, as is the WS-26
handle-retention design (eviction hooks into `WsSessions`).
## WS-27 [minor] — unchanged; folds into the Unit-2 hook rework
`adapter_install_channel_zero` (`src/websocket/upgrade.rs:290-295`)
is still `#[allow(dead_code)]` with its only caller test-gated
(`src/adapters/from_wss.rs` test server). The rework gives both
callers a real hook; fold the cfg cleanup there.
## WS-23 [minor] — unchanged; Unit 3's e2e gates gain two upstream-mirroring gates
The browser-opened-channel e2e test is still absent (no test opens a
data channel through a live WS session). Unit 3's five scenarios
stand as written. Two gates to add, mirroring what review 005
taught: `services/list` on a live WS session lists the per-session
openable (the F-06 shape), and the `op/register` announce → hub
nested-composition → routes-back-to-consumer flow (the stub path —
the one review-005's original gate missed and G-02 called out).
---
# Part C — Noted, not findings
- **Connect-side serving for `from_wss`**: alkcall 0.3's
`from_connection_with_serving` makes the WSS consumer half capable
of serving inbound calls (the from_wss session could answer
hub→consumer calls). `from_wss` keeps the pure-consumer default
(`from_connection`, resolution-only pump — verified unchanged);
opt-in serving is a deliberate later decision (it changes
`from_wss`'s security posture: a dial would then execute inbound
requests against a caller-chosen registry). Recorded here so the
opportunity is visible; no finding, no unit.
- **`op/register` forced-Internal semantics vs AGENTS.md §13**: the
announced op lands `Internal`/`FromCall` on the hub — consistent
with the adapter-registered-ops-are-Internal convention (it is
composition material, not wire-callable from the hub side). No
conflict; noted to preempt an apparent tension.
- **`list_operations` returns `External` only** — the
`to_openapi`/`to_mcp` projections and the gateway `/search`
continue to see exactly the External set; unchanged from 0.2.
- **Upstream doc drift (alkcall-side, not ours)**: ADR-016's
protocol-code list ("six codes", last amended by ADR-021) does not
name `ALREADY_EXISTS`. The 2026-09-04 ADR-022 collision
sub-amendment records the code's use; ADR-016's list is stale.
Flagged for the next alkcall doc pass; UP-01 is the alkhttp-side
half of the same gap.
---
# Remediation log
## Unit 2 — alkhttp wiring (WS-20/21/22/25/26/27 + UP-01) — LANDED (`030c5ef`)
**Hook rework (WS-20/21/25).** `install_channel_zero`
(`src/websocket/upgrade.rs`) now takes `(registry, sessions, policy,
openable_alpns)` and, per the alkcall ADR-047 §4 amendment #2 shape
(verified against alkcall's own e2e reference
`fork_registry_open_op_resolves_and_is_discoverable`):
1. Forks the deployment's base registry.
2. Registers on the fork: `ChannelOperations::register_on` (the
generic `channel/close` / `channel/control` /
`channel/resources/subscribe` — WS-21), the deployment's openables
via `ChannelCore::register_openable` (WS-22),
`install_bootstrap_discovery` closed over the fork (F-06 — the
session's `services/list` sees its own openables), and
`op/register` (WS-25 — `op_register_spec` +
`op_register_handler(connection, fork)`; the collision set is the
fork, the production shape review-005's gates use).
3. Dispatches over the fork (`Dispatcher::run_loop_single_stream`
unchanged as the loop; the loop's pending-resolution arms are the
review-005 G-01 mechanization the bump inherited).
**UP-02 decided.** `op/register` is registered with
`AccessControl::default()` on the built-in surface (the SRV-10
precedent: permissive crate default), and an assembly layer overrides
via a stricter policy passed through the hook — the posture mirrors
`ChannelsPolicy`. The override surface rides the existing `policy`
parameter; the assembly layer's `ChannelsPolicy` value is the same
object the open wrappers consult. *(Review 007 WS-29 correction: the
`ChannelsPolicy` object is an `Arc<dyn ChannelLifecyclePolicy>` — the
channel-cap policy — and cannot carry an operation `AccessControl`; at
this commit the hook hardcoded the permissive default and no override
surface existed. Remediation implemented the surface as decided:
`HttpAdapter::with_ws_op_register_acl` + the `OpRegisterAcl` request
extension, review 007 Unit 2. The posture above stands as recorded;
only the "rides the existing `policy` parameter" mechanism was wrong.)*
**Openable-ALPN surface (WS-22).** `OpenableAlpn { spec,
open_handler }` + `HttpAdapter::with_ws_openable_alpns(Vec<..>)`,
threaded `RouterState``SessionState``run_channels_session`
hook, with the `OpenableAlpns` request-extension fallback on the
upgrade handler (mirroring `ChannelsPolicy`/`WsTimeouts`). Default:
no openables (channel 0 only).
**WS-26.** `WsSessions` gains a `connections` map +
`live_connections()` / `live_connection_count()`; the hook retains
the channel-0 `Arc<CallConnection>` under a self-removing
`ConnectionGuard` (dropped when the channel-0 task ends — any
teardown path). Shared only when a shared `WsSessions` instance is in
play (the bare-registry/test-server path passes `None`). *(Review 007
WS-28 correction: at this commit the guard was bound inside the `if
let` block and dropped microseconds after insertion — the retention
sentence above described the intended scope, not the landed one.
Remediation bound the guard as an `Option<ConnectionGuard>` in the
channel-0 task's frame; the gate
`live_connections_visible_mid_session_and_drain_after_teardown` pins
it.)*
**Policy threading.** The hook takes the session's resolved
`ChannelsPolicy` (the extension resolution, `NoCap` default) and
passes it to both `ChannelOperations` and `ChannelCore` — one policy
instance across the open wrappers and the demux-loop teardown path.
(First cut used a fresh `default_policy()` per hook; the
ledger-decrement gate caught it — the `ChannelsPolicy` extension was
never consulted. Fixed by threading; that interaction is exactly the
seam SRV-10's extension was built for.)
**UP-01.** `ALREADY_EXISTS` → 409 Conflict in
`call_error_to_http_status_with_identity` + the test arm; the module
doc names the seventh code.
**from_wss import exclusion (new in this unit, surfaced by the
gates).** The reworked hook serves the bootstrap + channel-lifecycle
set on every WS session, so a `from_wss` import's `services/list`
listing now carries them. Proxying session-scoped machinery would be
nonsense (`channel/close` proxied to the remote would forward a close
for *this* session's channel ids; `op/register` proxied duplicates
the import itself). `FromWss::import` now runs discovery first and
passes the listing minus the protocol-session ops
(`services/list`, `services/schema`, `services/list-peers`,
`op/register`, `channel/close`, `channel/control`,
`channel/resources/subscribe`) as the `operation_filter` — the
`from_call` filter surface, used for the first time by alkhttp. The
three from_wss discovery tests assert the original op sets and pass.
**WS-27.** `adapter_install_channel_zero` cfg-gated to exactly its
caller's cfg (`all(test, feature = "server", feature = "wss")`); the
`#[allow(dead_code)]` is gone; it inherits the reworked hook (the
from_wss test-server producer now serves the session ops too).
**Gate results (Unit 3, `tests/ws_upgrade_session.rs`):**
- `data_channel_open_discoverable_and_bytes_round_trip`
`services/list` lists the session's openable + generic channel ops
+ `op/register` (F-06 shape); open op returns an even `channel_id`;
a data chunk echoes back through the `OpenHandler`'s `Connection`.
- `channel_close_resolves_and_decrements_the_opener_ledger` — close
resolves `{"closed": true}`; the ledger drops to 0.
- `channel_cap_denies_open_over_the_limit` — the second open over the
cap denies with the `channel:too_many_channels` code (the
`ChannelsPolicy` extension is consulted).
- `disconnect_mid_open_tears_down_and_decrements` — hard disconnect →
the demux teardown decrements the ledger, no leak.
- `too_large_data_channel_chunk_survives_demux_resync` — a
`MAX_CHUNK_LEN + 1` declared chunk is skipped (the skip consumes
the declared payload off the transport, so the gate sends the
filler), the next chunk routes and echoes — resync through the WS
path (scenario 5).
- `op_register_served_per_session_and_collision_is_already_exists`
announce resolves `registered: true`; a second announce without
`replace` rejects with the overlay gate's `ALREADY_EXISTS`;
announcing the serving side's own op name with `replace: true`
rejects `ALREADY_EXISTS` (the G-03 gate, through the WS path).
- `disconnect_mid_call_does_not_hang_the_server` and the full
pre-existing suite stay green.
Test-harness hardening the gates required (recorded so the next
pass doesn't re-derive it): `call_and_await` filters by request id
(a prior Sub's trailing `call.completed` may interleave) and
tolerates data-channel chunks; `await_envelope` is the same filter
for hand-built frames; `CallError`-payload envelopes serialize flat
(`env.payload["code"]`, not `env.payload["error"]["code"]`).
**Verification (post-fix):**
```
cargo test → 454 passed, 0 failed
cargo test --all-features → 582 passed, 0 failed
cargo clippy --all-targets -- -D warnings → clean
cargo clippy --all-features --all-targets -- -D warnings → clean
cargo fmt --check → clean
cargo doc --no-deps → clean
```
---
## Unit 4 — spec reconciliation (docs) — LANDED (with the 0.3.1 consumption)
As planned in this review's remediation plan: OQ-05 resolved,
ADR-067 v1-cut note + ADR-048 amendment note updated to the landed
state, websocket.md §"Data channels for browsers" status block removed
(the section now documents the landed surface: `with_ws_openable_alpns`,
the `OpenableAlpns` fallback, cap policy, discovery, gates), the
idle-knob deployment note surfaced (silent data channels make the 60 s
default bite more often; semantics unchanged), the `op/register` ACL
posture (UP-02's decision) recorded in ADR-048's landed note, and the
UP-03 alkcall-side gap closed by 0.3.1 and asserted alkhttp-side (the
extended `op/register` gate). The alknet-ADR-044 §5 pointer was
checked: not stale (the rationale lives in this crate's ADR-044 §5;
the alknet-ADR-043 pointers are correctly marked parked/not-ported).
---
## Findings added during remediation
### UP-03 [minor, alkcall-side] — `services/list-peers` cannot list peer-announced ops; `PeerCompositeEnv` does not override `peer_operations`
Discovered by the Unit-3 gates: the first draft asserted announced-op
discoverability through `services/list-peers` and failed — the peers
array carries only `peer_id` entries with empty operation lists.
**Verified in both the published 0.3.0 crate and the alkcall tree:**
`services_list_peers_handler` maps `ctx.env.peer_operations(&peer_id)`
per peer (`alkcall/src/registry/discovery.rs:347-351`), but
`PeerCompositeEnv` overrides `peer_ids` only — `peer_operations` falls
to the trait default (`Vec::new()`,
`alkcall/src/registry/env.rs:63-65`); the `connections` map and
`connection_order` it would list from are in scope but unused there.
`OverlayOperationEnv` likewise overrides `contains` only. Net effect:
the peer-keyed listing shows `peer_id` entries whose `operations`
array is always empty, and the ADR-022 amendment's "announced op is
discoverable via `services/list-peers`" promise does not resolve on
the wire today. The alkcall gates never caught it because the F-05
gate calls the announced op directly (bypassing discovery) and the
G-02 gate composes it — neither lists peers after announcing.
**Fix lives in alkcall** (one method override:
`PeerCompositeEnv::peer_operations(peer)` → the connection env's
overlay registration names, ACL-filtered like the local listing;
`OverlayOperationEnv` may want a matching helper). Flagged for the
next alkcall review pass; the alkhttp-side gate
(`op_register_served_per_session_and_collision_is_already_exists`)
asserts the overlay-landing + collision semantics instead, which is
the behavior Unit 2 actually wires, and is not blocked by UP-03.
**LANDED in alkcall 0.3.1** (2026-09-04, `fd21230`): implemented as
alkcall ADR-030 — `OperationEnv` gains a defaulted
`list_operation_names`; `OverlayOperationEnv` overrides it;
`PeerCompositeEnv::peer_operations` delegates to the peer overlay,
and its `list_operation_names` aggregates session + connections +
base. Upstream gate:
`announced_op_is_discoverable_via_services_list_peers`. The version
pin here is `0.3` (semver-compatible), so the fix arrives through a
`Cargo.lock` bump with no source change; the WS-path gate that
originally surfaced UP-03 now asserts the discovery promise —
`op_register_served_per_session_and_collision_is_already_exists`
gained the `services/list-peers` step (announce → the `alice` peer
entry lists `consumer/exec`) and passes against 0.3.1 (it fails
against 0.3.0, verified by the original draft).
### UP-02 decision (recorded)
`op/register` is served per WS session with
`AccessControl::default()` on the built-in surface (the SRV-10
posture: the crate default is permissive, deployments gate via the
`ChannelsPolicy` extension whose value the hook now consults). The
wire-callability tension in this review's UP-02 note resolves the
same way: the peer must call `op/register` (it is in the bootstrap
set), so Internal visibility was never an option; the ACL parameter
is the deployment's gate, not the visibility flag.
---
## Verification log (this pass)
- Bump verified empirically: `cargo check` (default + all-features)
surfaced exactly the two break classes; post-fix gates all green
(453 / 575 / clippy both / fmt / doc).
- WS-24/WS-25 resolutions read in the published 0.3.0 crate source
(`registry::op_register` handler + collision gate;
`Dispatcher::serve_single_stream`; `install_bootstrap_discovery`;
`OperationRegistry::fork`) and in the upstream ADR amendment
texts; the reference fork shape read at
`alkcall/src/channels/client.rs:1988-2130`.
- Review-003 residual claims re-verified at this tree: hook
discards manager (`upgrade.rs:255`), zero `register_on` callers,
no openable surface, no retained connection handle, WS-27 caller
census unchanged.
- UP-01 verified: no `ALREADY_EXISTS` arm in
`src/gateway/error.rs:57-80`; emitter census in 0.3.0 = `op_register.rs`
only; fall-through confirmed to `STATUS_INTERNAL`.
- UP-02 verified: `op_register_spec` signature and its
default-ACL doc note; AGENTS.md §13 vs wire-callable tension
resolved against the SRV-10 precedent (permissive crate default +
extension override).
- Cross-repo state verified: alkcall review 005 resolved
(G-01..G-05 remediation log), 0.3.0 published with
`cargo semver-checks` 196 checks against 0.2.0 (changelog's
Changed section matches the two source-break classes this crate
experienced).