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

190 lines
10 KiB
Markdown

---
id: implementation/composed-accept
name: Composed — the bind-first accept (the listener on a listen resource)
status: completed
depends_on: [implementation/composed-egress]
scope: moderate
risk: medium
impact: component
level: 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.