diff --git a/src/composed/mod.rs b/src/composed/mod.rs index ebb65c9..cdc2dfb 100644 --- a/src/composed/mod.rs +++ b/src/composed/mod.rs @@ -715,6 +715,13 @@ pub fn register_forwarded_serving( /// data plane is raw on both ends; a UDP listen substrate would frame /// — a datagram BIND variant is a different task). A non-TCP `bound` /// fails the factory (`NotAllowed`). +/// +/// **The 0x02 layering** (review 003 N-2): on the channels path the +/// ADR-006 command gate precedes this factory — an identity without +/// `socks5:bind` never reaches it. A factory-side `NotAllowed` (0x02, +/// the same code the gate uses) therefore means the *tunnel* layer's +/// per-listener ACL (this variant's real policy source), not the +/// command gate — semantically distinct, wire-identical. pub fn bindfirst_accept( client: Arc, registry: CorrelationRegistry, diff --git a/src/door.rs b/src/door.rs index 783b6d1..f528780 100644 --- a/src/door.rs +++ b/src/door.rs @@ -268,9 +268,12 @@ impl FrontDoorBuilder { self } - /// The max-concurrent-sessions cap (ADR-014's accept-path defense - /// knob 2): an accept beyond the bound is refused door-side — the - /// socket is closed immediately, no channel opened. + /// The max-concurrent-*connections* cap (ADR-014's accept-path + /// defense knob 2; review 003 N-3's naming — the mechanism bounds + /// accepted connections, including ones past the idle gate that + /// are merely slow readers, not active RFC sessions): an accept + /// beyond the bound is refused door-side — the socket is closed + /// immediately, no channel opened. pub fn max_sessions(mut self, cap: usize) -> Self { self.max_sessions = Some(cap); self @@ -817,6 +820,14 @@ impl UdpFrontDoor { /// Serve the association over an existing relay socket (the /// test/assembly seam — the same pump without the bind). + /// + /// Posture (accepted residual, review 003 S-1): the client's + /// source address is learned from the first inbound datagram and + /// pinned for the association's lifetime — a wildcard-bind + /// deployment accepts the off-path-first-datagram shape (the + /// RFC's own per-association peer expectation; the loopback bind + /// is the documented posture). A client whose source port + /// rebinds mid-association (NAT) stops receiving replies. pub async fn serve_on( self, socket: UdpSocket, diff --git a/src/local/producer_door.rs b/src/local/producer_door.rs index cc2cd27..5355ea2 100644 --- a/src/local/producer_door.rs +++ b/src/local/producer_door.rs @@ -133,9 +133,12 @@ impl ProducerDoorBuilder { self } - /// The max-concurrent-sessions cap (ADR-014 knob 2): an accept - /// beyond the bound is refused door-side — the socket is closed - /// immediately, no session spawned. + /// The max-concurrent-*connections* cap (ADR-014 knob 2; review + /// 003 N-3's naming — the mechanism bounds accepted connections, + /// including ones past the idle gate that are merely slow + /// readers, not active RFC sessions): an accept beyond the bound + /// is refused door-side — the socket is closed immediately, no + /// session spawned. pub fn max_sessions(mut self, cap: usize) -> Self { self.max_sessions = Some(cap); self @@ -191,6 +194,10 @@ impl ProducerDoorGuard { /// they own their sockets and finish with their clients (per- /// connection isolation cuts both ways). Returns the loop task's /// fatal outcome, if any (`None` = the loop ended by close). + /// + /// The receiver is dropped before the await — the loop's + /// `done_tx.closed()` fires only when every receiver is gone; + /// holding it across the await would deadlock the graceful stop. pub async fn close(mut self) -> Option { self.done_rx = None; let task = self.task.take();