Files
alktunnels/tasks/tunnels/local-bind-halves.md
T
glm-5.3-flash 514e0c1f25 feat: the local bind-first halves (shape 3 substrate half, ADR-008 §1)
- `bind_tcp_first` + `TcpListenerHalves::accept_pair`: the real-socket
  BindFn source (port 0 resolved via local_addr — the `bound` reply
  field's data) and the per-accept (peer, halves) pair (the peer rides
  accept()'s return — the forwarded op's `peer` carrier)
- unix bind-first parity shipped (OQ-TN-14): `bind_unix` +
  `UnixListenerHalves` (local_addr / queue-feeding accept_loop /
  bind-first accept_pair; an unnamed peer fails loud — the forwarded
  `peer` param requires an address) + `bind_unix_first`
- assembly conveniences: `local_bind_first()` (tcp/unix dispatch; udp
  fails loud — no accept on a UDP socket, the shape is not defined
  there, never a fake listener) + `local_bindfirst_accept_loop()`
  (downcasts the opaque listener; a foreign type fails loud)
- direct-op DialFn reuse verified + pinned: `local_dial` shared
  unchanged (the UDP framed wrap composes inside the closure);
  `direct_udp_through_local_dial_round_trips_framed` drives a REAL
  udp echo through the direct op's establisher path
- tests/local_bindfirst.rs: tcp e2e over real sockets (bound fidelity
  — the test dials the reported address; two clients → two forwarded
  channels pumping independently, real distinct peers), unix parity
  (real path in `bound`, named-peer client via socket2, real peer path
  on the forwarded open), udp refusal, downcast-miss probe
- accept-first shapes (accept_loop + AcceptQueue) untouched

Verified: cargo test (default) + --all-features green (45 lib; 151
integration), clippy -D warnings clean (native all-features + wasm32),
fmt clean, wasm32 check passes (default crate stays wasm-clean),
cargo doc clean (no new warnings).

Tasks: tunnels/local-bind-halves (status completed; Notes + Summary
filled). CHANGELOG: Unreleased → Added.
2026-09-19 16:30:46 +00:00

9.3 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
tunnels/local-bind-halves local feature — TCP bind halves + the bind-first assembly pieces completed
tunnels/bindfirst-producer
narrow low component implementation
local
tcp
bind
listener
feature-gated

Description

Extend the local feature (non-wasm, tokio/net-gated — tunnels/local-socket-halves established the pattern) with the bind-first shape's substrate half: a real TCP bind function (BindFn-compatible) and an accept loop producing (peer, TargetHandle) pairs, plus the direct-op convenience dial closure composition (the DialFn the direct op shares with the base op — likely already satisfied by local_dial; verify, don't duplicate).

The bind half

/// The real-socket BindFn source (the `local` feature's shape 3
/// backing): bind `addr` (port 0 = OS-chosen), report the REAL
/// bound address (the `bound` reply field's source — ADR-008 §2),
/// expose the accept loop that feeds the dispatcher.
pub async fn bind_tcp_first(addr: &str)
    -> Result<BoundListener, TunnelEstablishError>;

pub fn local_bind_first() -> BindFn;   // renders via bind_tcp_first
  • BoundListener.listener carries the TcpListener (type-erased in the plan — tunnels/bindfirst-producer's shape note); the accept loop is assembly code composed ON it — the protocol crate stays socket-blind, so the loop lives here (local = substrate code, ADR-004):
  • TcpListenerHalves already has an accept loop (accept_loop feeding an AcceptQueue — the accept-first shape). The bind-first shape needs a DIFFERENT loop: per-accept it must produce (peer_addr, TargetHandle) for the accept dispatcher (which opens forwarded channels per accept), not feed a queue for the establisher to pop. Add a sibling method/struct (e.g. TcpListenerHalves::accept_pairs or a new BindFirstAcceptLoop adapter) — decide the honest shape, document in Notes. The peer address rides from accept()'s return value (reply#2 fidelity's source — ADR-008 §3's peer carrier).
  • The direct-op DialFn: local_dial() already renders (Substrate, backing) → halves with the UDP framed adapter — the direct establisher calls the SAME closure with the rendered target string. Verify the UDP wrap happens INSIDE the closure (it does — review 001 U-1) so direct-udp gets the framed adapter free; add a test pinning direct-udp-through-local_dial framing if missing.

Tests (tests/, real sockets — the local suite's posture)

  • Bind port 0 → bound reports the REAL OS-chosen address (the reply#1 fidelity pin).
  • Two clients dial the bound address → two accepts → two forwarded channels (via the bind-first harness) → both pump independently (peer fidelity: each forwarded open carries the REAL accepted peer).
  • Unix-domain bind (OQ-TN-14 parity): bind_unix_first-equivalent — decide whether unix bind-first ships in this task or is deferred with a note (the wire enum already carries unix; the local dial path exists — the bind path is the same shape; prefer shipping both, matching dial parity).

Acceptance Criteria

  • bind_tcp_first + the accept-pairs loop (peer + halves per accept) behind local
  • The bound address is the REAL OS-chosen one (port 0 resolved — pinned by test)
  • Direct-op DialFn reuse verified (no duplicate dial code; UDP framed-adapter wrap composes)
  • Unix bind-first parity decided + shipped or explicitly deferred with rationale
  • cargo test --all-features green; default-crate wasm32 checks still clean (the feature gate holds)
  • Clippy/fmt clean (both feature sets)

References

  • docs/architecture/decisions/008-listen-metadata-forwarded-op.md (§1 shape 3 — the bind-first establisher; §3 the peer carrier)
  • docs/architecture/producer.md §The Registration API (BindFn shape)
  • tasks/tunnels/local-socket-halves.md (the local pattern + the existing bind_tcp/TcpListenerHalves/AcceptQueue accept-first shapes)
  • tasks/tunnels/bindfirst-producer.md (the BindFn/ BindFirstAcceptLoop contracts this implements)
  • OQ-TN-14 (unix ships with local v1; stdio out — alktty's)

Notes

  • The task sketch's bind_tcp_first(addr: &str) signature held — the committed [BindFn] contract takes (substrate, backing); the substrate dispatch lives inside [local_bind_first] (tcp → [bind_tcp_first], unix → [bind_unix_first], udp → fails loud), matching the type-erased closure precedent (the protocol crate stays socket-blind; the closure is the assembly side).
  • The accept-pairs shape: a *_pair method, not a sibling loop struct — [TcpListenerHalves::accept_pair] (one accept per call → (peer, halves)). The task floated accept_pairs or a BindFirstAcceptLoop adapter struct; one method is the honest shape: the DISPATCHER owns the loop pacing (the committed BindFirstAcceptLoop contract is one closure call = one accept), so the substrate half is just the single-accept step with the peer attached. local_bindfirst_accept_loop() is the adapter — it wraps the *_pair methods with the downcast (the assembly side of the type-erased injection; a foreign listener type fails loud as handler_error — pinned by test). The accept-FIRST [TcpListenerHalves::accept_loop] (queue-feeding) is untouched.
  • UDP bind-first is NOT here — refused loud (a decision the task left open): a UDP socket has no accept, so the bind-first shape is not DEFINED for udp; [local_bind_first] maps Substrate::Udp to a handler_error rather than faking a listener (pinned by udp_bind_first_fails_loud). The per-peer associate shape (an accept-analogue for UDP) would be a wire/protocol design decision — an ADR, if it ever ships. The direct/forward udp dial shapes cover the substrate.
  • Unix bind-first SHIPPED (the task's preferred posture — OQ-TN-14 dial parity): bind_unix + UnixListenerHalves (a sibling of [TcpListenerHalves]: local_addr, the queue-feeding accept_loop, and the bind-first accept_pair) + [bind_unix_first]. The bind-first accept_pair's peer is the ACCEPTED client's bound path; an unnamed (autobind) client fails loud — the forwarded op's peer param is required on the wire, an addressless peer cannot be represented (ADR-008 §3). The unix parity test binds the CLIENT to a named path first (socket2 — std has no UnixStream::bind), making the peer representable.
  • bind_unix maps bind failure to ResourceShortage (matching [bind_tcp]'s posture — the held-resource class), not DialFailed: the bind IS the dial for the establisher's error table (shape-3: unbindable/held → dial_failed-class rides the establisher's passthrough), but the substrate function's own class follows the sibling bind functions.
  • Direct-op DialFn reuse: verified, no duplication — [local_dial] is unchanged and shared; the UDP wrap happens INSIDE the closure (review 001 U-1). The missing pin was added: direct_udp_through_local_dial_round_trips_framed (tests/ local_halves.rs) drives TunnelSession::open_direct + Substrate::Udp through [local_dial] against a REAL udp echo socket — datagram boundary preserved through the direct op's establisher path.

Summary

Implemented the local feature's bind-first substrate half (shape 3, ADR-008 as amended) per the task: src/local/mod.rs — bind_tcp_first (the real-socket [BindFn] source: port 0 resolved via local_addr, the bound reply field's data), TcpListenerHalves::accept_pair (one accept → (peer, halves) — the peer rides accept()'s return, the forwarded op's peer carrier), bind_unix + UnixListenerHalves + bind_unix_first (unix bind-first parity SHIPPED — OQ-TN-14; unnamed peer fails loud, the forwarded peer param requires an address), local_bind_first() (the [BindFn] convenience: tcp/unix dispatch, udp fails loud — no accept on a UDP socket, the shape is not defined there, never a fake listener) and local_bindfirst_accept_loop() (the [BindFirstAcceptLoop] convenience: downcasts the plan's opaque listener to the module's wrappers, one pair per call; a foreign listener fails loud). The accept-FIRST shapes (accept_loop + AcceptQueue) are untouched.

Tests: tests/local_bindfirst.rs (4, #![cfg(feature = "local")]) — the tcp e2e over REAL sockets (bind port 0 → the reply's bound is the REAL OS-chosen address the test then DIALS — a fake bound would strand the dial; two clients → two accepts → two forwarded channels → independent interleaved pumps, each forwarded open carrying the REAL accepted peer, distinct ports), unix parity (real socket path in bound; a named-path client connects; the forwarded open carries the REAL peer path), the udp refusal, and the downcast-miss probe. Plus direct_udp_through_local_dial_round_trips_framed in tests/ local_halves.rs — the direct-op DialFn reuse pin (the framed udp adapter composes through the direct op; review 001 U-1).

Verified: cargo test (default) and cargo test --all-features green (46 lib + 151 integration total; the local suites 20 + 4), clippy --all-targets --all-features -D warnings clean, fmt clean, wasm32-unknown-unknown check passes (the default crate stays wasm-clean; the bind-first half lives behind local, non-wasm by design).