Files
alksocks/tasks/implementation/composed-accept.md
T
glm-5.3-flash 4e1673eefc composed: the bind-first accept — the listener on a listen resource
Lands the composed accept (ADR-007 variant 2 / ADR-008): the BIND
accept whose listener lives on an alktunnels 0.2.0 bind-first listen
resource, at the graduation's vanilla fidelity.

- `composed::bindfirst_accept` — the BindAcceptFactory: opens the
  listen channel (reply#1 advertises the open-op reply's real
  `bound`, the 0.8.0 reply projection — no expected-addr stand-in),
  inserts the minted `listen` token into the correlation registry,
  and returns the accept future parked on the token's oneshot.
- `composed::register_forwarded_serving` — the forwarded-op serving
  half on the SOCKS5 producer's connection: the shared handler
  correlates by token and owns the middle pump (either-EOF
  completion; a three-party per-direction shutdown cascade would
  deadlock).
- The Sync-bound wrinkle: `SyncHalves`/`OwnedWrite` (mutex
  Sync-proof, no unsafe) over the forwarded halves; the owner
  (`ForwardedOwner`, the TunnelListener) rides inside the write
  payload — dropping the handle drops the session, halves, and
  listener together (teardown structural, compiler-enforced).
- `tests/composed-accept.rs`: the full story (real bound, real peer,
  raw pass-through round trip), client-gone teardown drop-witness,
  distinct tokens for concurrent BINDs, per-accept isolation of a
  FORBIDDEN forwarded open, late-forwarded correlation miss.
- The producer-side adopted forwarded entry rides the per-adopter
  contract (transport-EOF reap), as upstream's bind-first suites
  concede; the no-leak probe reflects that shape.

Verification: cargo test (default + --all-features), wasm
check/clippy (hard gate), clippy --all-targets --all-features -D
warnings, fmt --check — all clean.
2026-09-25 22:13:17 +00:00

10 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
implementation/composed-accept Composed — the bind-first accept (the listener on a listen resource) completed
implementation/composed-egress
moderate medium component implementation

Description

The composed feature's accept provider (ADR-008's composed variants; ADR-007 variant 2): the BIND accept whose listener lives on an alktunnels 0.2.0 bind-first listen resource (register_tunnel_bindfirst_openable + BindFirstInjections) — per-listener ACL for free, hub-relayable. Vanilla-fidelity with no stand-ins since the graduation (ADR-007): reply#1 advertises the real OS-chosen bound (the open-op reply's bound field, alkcall 0.8.0 reply projection); reply#2 carries the real peer (the forwarded op's peer param).

Deliverables:

  1. src/composed/ additions (feature composed) — the composed accept, named per producer.md §Wiring:
    • bindfirst_accept(...) → BindAcceptFactory: the POC #5 composed_bind_accept shape updated to the graduation — the POC's pinned workaround (reply#1 first, the tunnel open rides the accept future; the factory carries an expected bind_addr) is retired (ADR-007 §Consequences). The landed shape: the factory opens the bind-first listen channel (TunnelListener::open — the reply projection surfaces bound), reply#1 advertises the real bound, and the accept future correlates the forwarded sessions (the listen token → the adopted forwarded sessions the assembly correlates; the forwarded op's peer param is reply#2's peer).
    • The correlation shape is the design center (the POC's upstream-ask ledger retired by the graduation): the factory returns BindAccept whose accept future waits for the ONE forwarded session matching the listener's token. The correlation registry is assembly-side bookkeeping (the alktunnels TunnelListener docs: "forwarded sessions arrive as ordinary adopted sessions on the manager; the listener does not own the receipt queue"). The provider owns: the register_tunnel_forwarded_openable serving side (the opener half — the SOCKS5 producer's connection must serve the forwarded op), the token→waiter map, and the per-forwarded pump. Design the wait shape against the landed alkcall 0.8.0 adopt surface (manager.adopt_channel) and record it in Notes.
    • The Sync-bound wrinkle (shared with composed-egress): the accepted halves must be Box<dyn AsyncRead + Send + Sync + Unpin> (BindTargetHandle's payloads); alktunnels' Taken Halves yields Send + Unpin only. The owner-wrapper rides inside the handle payloads (egress.rs's documented posture) — the wrapper owns the adopted channel session so dropping the handle drops everything (the teardown-at-birth discipline, compiler-enforced). This is the load-bearing wrapper: design it once (with composed-egress's needs), pin it with a test.
    • Peer-address fidelity: the forwarded op's peer param is reply#2's real peer (the graduation's win); the wrapper-aware sentinel (0.0.0.0:0) is the in-band default's shape, not this variant's.
    • The establishment bound covers only the open-op round trip (ADR-007): the accept wait lives in the teardown-governed pump phase — the quiet wait's select! drop of the accept future IS the client-gone teardown; the listener (inside the future) closes with it. The forwarded-op correlation must survive a client-gone drop without leaking the listener (the accept future owns it end-to-end — the seam contract).
    • Module doc: names the ADRs (007/008), the graduation's vanilla-fidelity win, the correlation shape, the owner-wrapper.
  2. Tests (tests/composed-accept.rs): the full composed BIND story over channels — a bind-first tunnel producer (register_tunnel_bindfirst_openable + injections), the SOCKS5 producer with composed::bindfirst_accept(...), a consumer BIND session: reply#1 carries the real bound, an app server dials the far listener, reply#2 carries the real peer, the pass-through data plane round-trips; the client-gone teardown releases the listener (drop-witness); two concurrent BIND sessions get distinct tokens; a forwarded-open failure is per-accept isolated.

Acceptance Criteria

  • cargo test passes; cargo test --all-features passes
  • cargo check --features composed --target wasm32-unknown- unknown clean (hard gate)
  • cargo clippy --all-targets --all-features -- -D warnings clean; cargo fmt --check clean
  • The graduation's shape lands (reply#1 = the real bound; reply#2 = the forwarded op's real peer) — no expected-addr stand-in — pinned by test
  • The owner-wrapper rides inside the handle payloads (Sync halves; teardown structural) — pinned by test
  • The client-gone teardown releases the listener (the accept future owns it end-to-end) — pinned by drop-witness test
  • The forwarded-op serving side + correlation land; per-accept isolation — pinned by tests

References

  • docs/architecture/decisions/007-bind-in-base.md (variant 2 + the graduation), 008-provider-defaults.md, 004 (the accept seam's contracts), 006 (the command gate)
  • docs/architecture/producer.md §Wiring (composed::bindfirst_accept), §The Three Seams
  • POC reference: /workspace/alksocks-bind-poc/src/accept.rs (composed_bind_accept — the superseded-in-shape workaround, the owner-wrapper's first witness)
  • Landed surfaces: src/egress.rs (BindAccept/BindTargetHandle — the contracts), alktunnels 0.2.0 (register_tunnel_bindfirst_ openable, BindFirstInjections, TunnelListener, register_tunnel_forwarded_openable, ForwardedHandler)

Notes

  • The correlation shape (the design center): a CorrelationRegistry (Arc<tokio::sync::Mutex<HashMap<u32, oneshot::Sender<ForwardedSession>>>>) keyed on the listen token. The factory inserts the token → oneshot right after TunnelListener::open; the provider's shared forwarded handler (make_forwarded_handler, registered via register_forwarded_serving on the SOCKS5 producer's connection) removes the entry on the arriving forwarded open and delivers ForwardedSession { far duplex halves, peer }. A dropped receiver (client-gone) or a miss makes the handler drop the forwarded channel stream — teardown-at-birth, R-02 (the inline pump_bidi await in register_openable's wrapper then reaps the entry).
  • The Sync-bound wrinkle: alktunnels' halves are Send + Unpin only; the landed BindTargetHandle payloads carry + Sync. Two adapters pin the no-unsafe Sync proof (Mutex<T: Send> is Sync; contention never occurs — one pump task at a time): SyncHalves (read half) and OwnedWrite (write half + the owner inside).
  • The owner-wrapper rides inside the payloads (ForwardedOwner): the write payload owns the TunnelListener — dropping the handle drops the listener → the listen channel EOFs → the far teardown cascades (teardown structural, compiler-enforced). The edition-2021 disjoint-capture trap (an earlier draft captured listener.channel_id, dropping the listener at the factory's return — EOF at birth) is why the whole struct is rebound into the accept future.
  • The three-party bridge pump: the forwarded handler bridges the forwarded BiStream to a local duplex pair whose far end travels into the accept future's payloads. The bridge completes on EITHER direction's EOF (a pump_bidi-then-drop, not a per-direction shutdown cascade — a three-party half-close cycle would deadlock: each pump's completion-shutdown needs the next hop's EOF, and the chain closes on itself). Dropping everything EOFs the SOCKS5 producer-side pump, ends the per-accept pump, and tears the forwarded channel down.
  • Test concession (producer-side adopted forwarded entry): the producer's open_forwarded caller adopts the opener-allocated forwarded channel id on the producer-side manager; that entry's reap rides the per-adopter contract + transport EOF, NOT the listener's teardown (alktunnels ADR-005; upstream's own bind-first suites make the same concession — producer_bindfirst.rs no_stray_channels documents the listen channel, and the forwarded entries are adopted sessions owned by their adopter). assert_only_channels therefore treats odd producer-side ids as the adopted-forwarded shape.
  • Forwarded-open failure semantics: a FORBIDDEN forwarded open is per-accept isolated AT THE TUNNEL LAYER (the producer's accept loop drops that connection and keeps serving; the alktunnels producer-suite pin). The SOCKS5 accept future stays parked — the SOCKS5 producer never learns of the tunnel-layer failure — so no reply#2 arrives; the quiet wait is unaffected. Pinned by test.

Summary

Landed composed::bindfirst_accept + composed::register_forwarded_serving (the composed accept, ADR-007 variant 2 / ADR-008) on the alktunnels 0.2.0 bind-first listen resource, at the graduation's vanilla fidelity: reply#1 advertises the open-op reply's real bound (the alkcall 0.8.0 reply projection; no expected-addr stand-in), reply#2 carries the forwarded op's real peer param. The correlation registry (token → oneshot) is assembly bookkeeping; the provider owns the forwarded-op serving half, the token→waiter map, and the per-forwarded middle pump (either-EOF completion shape). The Sync-bound wrinkle is met by SyncHalves/OwnedWrite (mutex Sync-proof, no unsafe) with the owner (ForwardedOwner, the TunnelListener) riding inside the write payload — dropping the handle drops everything (teardown structural, compiler-enforced). tests/composed-accept.rs pins: the full story (real bound, real peer, raw pass-through round trip), the client-gone teardown (drop-witness: the accept future owns the listener end-to-end; no leaked listen channel), two concurrent BIND sessions' distinct tokens, per-accept isolation of a FORBIDDEN forwarded open, and the late-forwarded correlation miss dropping the channel at birth. Verified: cargo test, cargo test --all-features, cargo check/clippy --target wasm32-unknown-unknown (hard gate), cargo clippy --all-targets --all-features -- -D warnings, cargo fmt --check — all clean.