- Rewrote em dash prose across the whole tree outside docs/reviews/ (the historical reviews stay verbatim, matching the alktunnels and alktty precedent): src doc comments and inline comments (587), docs/architecture (319), docs/research + sdd_process (484), tests (361), tasks (899), fuzz (28), README/CHANGELOG/Cargo.toml (77), AGENTS.md (85), and the .opencode/agents prompts (109) - Fenced content follows the precedent: comments, ASCII-diagram label separators, and template placeholders rewritten; wire data, JSON/BAST documents, byte diagrams, and transcripts survive verbatim; assert, expect, panic, error-display, and format message literals rewritten as prose; no code, fixture, or test data change - src/rfc1928/LICENSE: the two dashes in alksocks' own provenance header rewritten; the MIT legal text untouched - AGENTS.md adds Project Convention 18: no em dashes in prose; varied restructure, no spaced-hyphen substitutes, fenced prose rule, en dash numeric ranges exempt, docs/reviews/ exempt from the greps - tasks/*.md frontmatter: the name values rewritten by the sweep are now quoted (a raw colon inside an unquoted YAML scalar parsed as null); taskgraph validate reports 37 tasks, no issues Verification: cargo test; cargo clippy --all-targets -D warnings; cargo fmt --check; cargo doc --no-deps; fuzz corpus replay (cargo test --manifest-path fuzz/shared/Cargo.toml); wasm check and clippy for wasm32-unknown-unknown with -D warnings; em dash grep over the repo returns matches only in docs/reviews/ and the bast.md BAST JSON data fence
5.9 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| implementation/core-consumer-connect | Core: consumer half, Socks5ChannelSession (CONNECT) + e2e tests | completed |
|
narrow | low | component | implementation |
Description
Implement the CONNECT consumer session in src/consumer.rs per
consumer.md §The Channels-Native Sessions: Socks5ChannelSession,
the typed client that opens channels/socks5/sub and drives the
client side of the RFC conversation in-stream (POC #1's
use_stream shape).
Deliverables (port from /workspace/alksocks-connect-poc/src/ consumer.rs, adapted to the crate's params/error surface):
Socks5ChannelSession::connect(client, target): opens the channel viaChannelClient::open_channel(OP_SOCKS5_OPEN, params, SOCKS5_ALPN)(trivial params, ADR-001), then drives the forked client typestate'suse_streampath over the channel'sBiStream(generic over the backing stream, and the POC #1 fit:Socks5StreamimplsAsyncRead + AsyncWriteitself, so the session over aBiStreamis the intended use).- Target construction via
ToTargetAddr(IP-string forms parse to IP targets; domain form passes through unresolved, per ADR-012). - Post-reply payload pipe: after the reply, the session is a
raw byte pipe. Expose
split()(read/write halves) andinto_inner()(the underlyingSocks5Stream<BiStream>for advanced use). 0 B framing (CONNECT). - Teardown ownership (alktunnels ADR-005 posture, inherited):
the session owns the channel; on Drop it closes/joins. No
Clone(compile-asserted, since aliasing a session would alias its teardown; the alktunnelscompile_faildoctest shape). - Error surface: open failures are the typed
SocksOpenErrorreasons (ADR-049 §4: a refused session never yields a phantom channel); a refused target is an RFC reply code surfaced through the session type (consumer.md §Error surface). - Tests (end-to-end through the harness): connect → echo round-trip (payload bytes both directions); refused dial → the RFC reply code surfaced as a typed session error; FORBIDDEN open → typed open error, no session; half-close semantics (dropping the write half EOFs the producer's pump cleanly); domain-form target passes through unresolved (assert the wire bytes, the echo dial ignores the target).
Acceptance Criteria
Socks5ChannelSessionwithconnect/split/into_innerper the POC shape- Teardown ownership on Drop; no
Clone(compile-asserted) - Typed error surface (open errors vs reply-code refusals)
- E2E tests through the harness (round-trip, refusals, half-close, domain pass-through)
cargo testgreen; clippy/fmt clean; both wasm checks clean
References
- docs/architecture/consumer.md §The Channels-Native Sessions
- docs/architecture/decisions/003-udp-handle-swap.md (the framing mirror, where CONNECT is the 0 B case)
- POC source:
/workspace/alksocks-connect-poc/src/consumer.rs - alktunnels template:
/workspace/@alkdev/alktunnels/src/consumer.rs(the teardown-ownership + no-Clone posture)
Notes
Filled by the implementation agent (2026-09-23):
- The session shape is exactly the POC's, adapted to the crate
surface:
Socks5Stream::use_stream(bidi, None, Config::default())overBiStream::from_joined(recv, send), thenrequest(TCPConnect, target).Config::default()is the no-auth posture (theskip_authknob stays internal to the fork; the identity seam is the open op's, CF-005/CF-006; no RFC-facing auth on this path). split()returns borrowed halves (tokio::io::splitover a&mut Socks5Stream<BiStream>; theSplitPipetype alias), not owned ones: the session keeps teardown ownership for every shape, including the split-driven one. The owned path isinto_inner()(TakenInner { session, inner }; the session rides along as the teardown owner).- Refusal teardown: an in-stream refusal (Rfc error) tears the
adopted channel down inside
connectbefore the error returns (teardown-at-birth; the phantom-channel property, tested both sides). The producer's own handler-exit teardown races the consumer's, and the leak-check probe polls past that race. - Drop is the sync-reap posture (
teardown_channelis sync;Dropcannot await) + the structural EOF of the dropped pipe; the graceful path isclose()(write-half shutdown first, then reap). - Harness: added the consumer-side topology (
wire_consumer_side[_with], where the consumer dials with a realChannelClientand the producer accepts with the real registration) pluswitnessing_echo_dial(the dial-witness target-form probe). Theallow(dead_code)attr rides the alktunnels harness posture (each test binary compiles the harness and uses a different subset). - No
Cloneis compile-asserted by the doctest; a runtime probe pinsSend(theClonebound itself is the doctest's job).
Summary
Implemented Socks5ChannelSession (CONNECT) in src/consumer.rs:
connect/channel_id/split/into_inner/close + Drop teardown
ownership and the SplitPipe/TakenInner companions, with the
typed error surface (SocksOpenError for opens, the RFC reply code
through SocksSessionError::Rfc for refused targets) and
teardown-at-birth on the in-stream refusal path. E2E suite
(tests/consumer-connect.rs, 9 tests over the new consumer-side
harness topology): echo round-trip + CF-006 witness, target forms
(IP-string parses / domain passes through, per ADR-012 and wire-observed
at the dial), refused dial → typed RFC error + no phantom channel,
FORBIDDEN open → typed open error + no session, drop teardown +
connection health, half-close EOF semantics, into_inner pairing,
and the no-Clone/Send compile shape (the compile_fail doctest
pins !Clone). Verification: cargo test (219 incl. the doctest),
clippy -D warnings (all targets), fmt, and both wasm checks clean.