feat(websocket): WS-29/30/32 — op/register ACL surface, SessionSlots, builder-path gate
Review 007 Unit 2 (the two "implement" decisions taken during remediation, plus the coverage gap): - WS-29: the `op/register` override surface review-006 UP-02 and ADR-048 recorded as landed is now implemented. The hook threads an `op_register_acl` `AccessControl` into `op_register_spec` (the permissive `AccessControl::default()` remains the default everywhere); the built-in surface sets it via `HttpAdapter::with_ws_op_register_acl`, bare-registry/custom routes via the `OpRegisterAcl` request extension (mirroring `ChannelsPolicy`/`WsTimeouts`/`OpenableAlpns`). A peer whose identity does not satisfy the ACL gets `FORBIDDEN` on the announce. Gates: builder path (`FORBIDDEN` scope-less / announce-ok scoped) + extension path. - WS-30: the bare-registry `SessionState` is built by `FromRef` per request, so its default-cap semaphore bounds nothing across requests (corrects review-002 WS-17's "bounded at 64 sessions" claim). New `SessionSlots` request extension carries the shared semaphore for routes that need an effective cap; the upgrade handler prefers it over the state value. Doc comments corrected (`SessionState`, `WsTimeouts`, `ws_upgrade_handler`). Gate: cap-1 route → 503 over cap → slot freed on session end. - WS-32: the built-in openables threading (`with_ws_openable_alpns` → `RouterState` → `SessionState` → hook) gets its first gate — every Unit-3 gate rode the `OpenableAlpns` extension fallback. `builder_path_openables_serve_the_data_channel_ surface` discovers the openable via `services/list`, opens the channel, and round-trips bytes through the builder-built router. Verification: cargo test 454 passed / 0 failed; cargo test --all-features 587 passed / 0 failed (+5 gates); clippy (both configs) clean; fmt clean. Review: docs/reviews/007-ws-data-channel-surface-review.md
This commit is contained in:
@@ -53,6 +53,11 @@ pub(crate) struct RouterState {
|
||||
/// `None` (the default) declares no openables — a WS session then
|
||||
/// carries no data-channel open ops (the pre-Unit-2 shape).
|
||||
pub(crate) ws_openable_alpns: Option<Arc<[crate::websocket::OpenableAlpn]>>,
|
||||
/// The `op/register` surface's `AccessControl` for WS sessions
|
||||
/// (review 007 WS-29): the per-session announce op is registered
|
||||
/// with this ACL. Default: `AccessControl::default()` (the
|
||||
/// permissive crate default).
|
||||
pub(crate) ws_op_register_acl: alkcall::registry::spec::AccessControl,
|
||||
}
|
||||
|
||||
impl axum::extract::FromRef<RouterState> for crate::websocket::SessionState {
|
||||
@@ -63,6 +68,7 @@ impl axum::extract::FromRef<RouterState> for crate::websocket::SessionState {
|
||||
Arc::clone(&state.ws_session_slots),
|
||||
state.ws_idle_timeout,
|
||||
state.ws_openable_alpns.clone(),
|
||||
state.ws_op_register_acl.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -103,6 +109,7 @@ mod tests {
|
||||
)),
|
||||
ws_idle_timeout: Some(crate::websocket::DEFAULT_WS_IDLE_TIMEOUT),
|
||||
ws_openable_alpns: None,
|
||||
ws_op_register_acl: alkcall::registry::spec::AccessControl::default(),
|
||||
};
|
||||
let extracted: DecoyConfig = axum::extract::FromRef::from_ref(&state);
|
||||
assert!(matches!(extracted, DecoyConfig::Redirect { .. }));
|
||||
|
||||
Reference in New Issue
Block a user