feat(client): land the -R leg — the acceptor registry + the reverse-listen rows (w4-reverse-listen)

- src/client_accept.rs: the assembly-mutable acceptor registry
  (per-type fixed slots under `tunnel`; refuse-never-replace install;
  payload = the `TunnelChannel` duplex + the open's echo/originator
  fields — the harness `ForwardedAcceptor` vocabulary grown), the
  reverse-listen rows (bound-port surfaced; the listen/cancel forms;
  the runtime-Unix gate at the listen), and the accepted-opens'
  spawned handoff tasks.
- src/client_handler.rs: the `forwarded-tcpip` /
  `forwarded-streamlocal` hooks route through the registry at
  arrival time — registered + admitted → accept + handoff;
  unregistered/unfeatured/cap-exhausted → the clean
  `AdministrativelyProhibited` decline (never accept-and-drop);
  pre-install arrivals decline, never buffered.
- src/client.rs: the dial wires the empty registry into the handler
  + the connection (`SshConnection::acceptors`, the late-install
  accessor) + the listener budget's dedicated counter.
- src/error.rs: `OpError::NoAcceptor` / `ListenRefused` /
  `ListenCapExceeded`; `RequestDenied` → `ListenRefused` (the
  listen/cancel forms' wire decline class; the port-0 mint rides
  alone at this pin — an explicit port's bound *is* the requested
  one, surfaced).
- tests/common/sshd_double.rs: the fake `sshd`'s `-R` half — the
  listen/cancel hooks bind real sockets (the out-param mint is the
  real bound port), the accept loops open the server-initiated
  forwarded channels + pump per accepted connection, cancels match
  by the raw wire strings and tear the listener down.
- tests/client_reverse_listen.rs: the contract rows (listen + data
  leg, the mint, the pre-wire gates, the declines, late-install
  on/off, the cancels, the ceiling, the unix twins).
- tests/tunnel_mapping.rs: the full-circle client ↔ server-direction
  smoke (both `-R` legs through the alkcall fabric double); real-
  `sshd` `-R` stays wave-5 e2e (recorded).
- client.md: the acceptor's landing record (shape, cap relation,
  gates, unix-gate placement, port-0 parity).
- fix(w4-op-rows, test cfg): the test mod's `family_exit_code`
  import gated to the exit-consuming builds (`--features tunnel`
  did not compile its tests before).

Verification: cargo test (default / each feature / --all-features)
green; cargo clippy --all-targets -- -D warnings (none +
--all-features) clean; cargo fmt --check clean; cargo doc --no-deps
clean (default build; all-features carries only the pre-existing
client_ops private-link warnings).
This commit is contained in:
glm-5.3-flash committed 2026-10-29 17:19:24 +00:00
1 parent 2371c2e169
commit 7e5989f574
11 files changed
+2314 -54

No files matched your search

+33
View File
@@ -69,6 +69,39 @@ half-capability, which the family forbids (ADR-003 symmetry +
consumers turn capabilities on/off per connection; the shape is an
assembly-mutable acceptor registry).
> **The landed acceptor shape (`w4-reverse-listen` — recorded at
> landing):** the registry is **per-type fixed slots** — exactly the
> two `-R` data-leg types the client direction can serve
> (`forwarded-tcpip`, `forwarded-streamlocal`, both under `tunnel`;
> no map, no generic key), one acceptor trait per type; install is
> **refuse-never-replace** (the `ListenRouter::register` recorded
> posture, mirrored client-side); the slots are read **at arrival
> time** by the dial handler's hooks — a pre-install/unfeatured open
> is declined at arrival, never buffered. The payload an accepted
> open hands the assembly: the channel duplex (family-typed — the
> `-L` rows' `TunnelChannel` byte duplex, one type both legs ride) +
> the open's echo/originator fields; **the pump is the acceptor's**
> from the handoff on (the family handler shape) — the crate keeps no
> accepted-open ledger. Bounded bookkeeping, the cap relation: the
> registry bounded by construction (two slots); the
> **reverse-listen count** and the **accepted-open handoff count**
> are two dedicated counters, each against the same
> `Limits::max_concurrent_channels` ceiling (the server-side
> listener/ListenerCap posture mirrored — independent counts, one
> ceiling; the listens live at the peer, the counter exists so a dial
> cannot mint an unbounded listener set). The **forward-request rows
> gate on the registered acceptor** (the issuing-side disposition:
> a listen whose data leg cannot serve is never asked for —
> install-first, `OpError::NoAcceptor` pre-wire), plus
> `OpError::ListenRefused` (the peer's `RequestDenied` — denied
> listens and unknown-listener cancels, sshd raw-string parity) and
> `OpError::ListenCapExceeded` (the ceiling, pre-wire). The
> **runtime-Unix gate** rides the unix *listen* rows only (the bind
> is the platform-bound op; the `forwarded-streamlocal` ingestion is
> platform-neutral byte pumping). The `tcpip_forward` reply's mint
> rides only for `port = 0` requests (russh/sshd parity: an explicit
> port's bound *is* the requested port, which the row surfaces).
## Lifecycle primitives (OQ-12 resolution)
- Forward listeners return the **actually-bound port** (russh