The crate's core task (implementation/core-producer-connect): the producer half of src/producer.rs per producer.md, adapted to the crate's seam types and the alkcall 0.8.0 establishment surface. - The session-gate establisher (`socks5_session_establisher`): validates/records the opener identity (the CF-005/CF-006 seam, ADR-005) via the optional `identity_witness` probe (the alktunnels posture); returns `Establishment::default()` — no plan payload (the dial happens post-establishment; POC #1's wire note). - The pump handler (`make_socks5_pump_handler`): `accept_bi()` → the forked typestate over the `BiStream` (`accept_no_auth → read_command`) → dispatch. CONNECT: the dial seam (`DialFn`, ADR-004) at the command-read interception point; success: `reply_success(sentinel)` then `pump_bidi` awaited inline (R-02 — the JoinHandle tracks the data-plane lifetime). Refusal: `reply_error` with the mapped RFC code — no phantom data plane. - The ASSOCIATE/BIND arms land as RFC-faithful refusals (`CommandNotSupported`) — the pre-vertical posture (ADR-008); the dispatch skeleton + registration signature carry the seam parameters from day one so the associate/bind tasks swap the arms without touching the registration shape. - `register_socks5_openable`: spec + establisher + pump via `register_openable_with_establisher` (`timeout: None` = the 10s default), scope-gated `socks5:open`. Missing callbacks = those commands refuse RFC-faithfully, never a panic. - `serve_socks5_over<T>`: the substrate-free protocol entry (the POC shape), directly testable without channels; the builder (`Socks5Producer::builder()`) is the spec'd assembly shape. - Tests: 11 in-module (substrate-free duplex-driven — round-trip + half-close teardown, refusal mapping per variant, missing-dial refusal, ASSOCIATE/BIND arms, dispatch guard, establisher witness, builder shape) + 5 harness integration (end-to-end CONNECT echo with the identity witness, dial refusal 0x05 with no phantom data plane, scope-class refusal 0x02, ASSOCIATE/BIND refusals with CONNECT still serving, repeated CONNECTs on one registration). The harness retargeted to the real producer (`RegisterMode:: Producer`); the minimal stand-in is a harness-internal helper. Verification: cargo test 87+19+37 green; clippy --all-targets -D warnings clean; fmt clean; wasm check + wasm clippy clean; cargo doc clean; --all-features green.
7.8 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |||
|---|---|---|---|---|---|---|---|---|---|---|
| implementation/core-producer-connect | Core — producer half: establisher + dispatch + registration + CONNECT | completed |
|
moderate | medium | phase | implementation |
Description
Implement the producer half of src/producer.rs per producer.md:
the session-gate establisher, the command-dispatch pump handler, the
registration surface, and the CONNECT data plane (POC #1's validated
scope). This is the crate's core task — the open op all three
commands ride.
Deliverables (port from /workspace/alksocks-connect-poc/src/ producer.rs + /workspace/alksocks-udp-poc/src/producer.rs, adapted
to the crate's seam types and alkcall 0.8.0):
- The session-gate establisher (
socks5_session_establisher): validates/records the opener identity (the CF-005/CF-006 seam — ADR-005) and applies session-level policy. RunsEstablishment::default()-shaped (no plan payload — the dial happens post-establishment, so there is nothing to thread establisher→handler; POC #1's wire note). Optionalidentity_witnessprobe seam (the alktunnels posture) for tests. - The pump handler (
make_socks5_pump_handler(dial, egress, accept)):accept_bi()→ drive the forked typestate over theBiStream(accept_no_auth → read_command) → dispatch:- CONNECT → invoke
DialFn(target)at the command-read interception point; success:reply_successthenpump_bidiawaited inline (R-02 — the returnedJoinHandletracks the data-plane lifetime; early return = teardown-at-birth). Refusal:reply_errorwith the mapped RFC code (dial-failure →ConnectionRefused; scope-class →ConnectionNotAllowed) — no phantom data plane. - UDP ASSOCIATE / TCP BIND → this task lands the dispatch
arms as RFC-faithful refusals (
CommandNotSupported); the associate/bind producer tasks replace them. (The dispatch skeleton exists from day one so the regression guard — one registration, all three commands — is testable early.) - The handler is substrate-agnostic by construction — it sees boxed halves, never a socket type.
- CONNECT → invoke
register_socks5_openable: spec + establisher + pump viaregister_openable_with_establisher(timeout: None= the 10s default), scope-gatedsocks5:open(the registry runs the ACL before the establisher). Missing callbacks = those commands refuse RFC-faithfully (CommandNotSupported-class), never a panic (ADR-008).serve_socks5_over<T>— the substrate-free protocol entry point (the POC shape): generic overT: AsyncRead + AsyncWrite + Unpin, driving the typestate and dispatching. This is what makes the producer testable without channels (the differential-suite pattern) and reusable by the batch-2 local backend.- Tests (via the harness + the raw RFC client helper): CONNECT
echo round-trip through
pump_bidi; dial refusal → 0x05 in the reply, no phantom data plane; FORBIDDEN for a scope-less identity (fails closed, no phantom channel); per-call opener identity witnessed (CF-006); ASSOCIATE/BIND refuseCommandNotSupported(the pre-vertical posture); half-close teardown (client EOF ends the session cleanly).
Acceptance Criteria
- Establisher: session-gate shape (
Establishment::default(), identity recorded; witness seam optional) - Pump handler: typestate-driven dispatch;
pump_bidiinline for CONNECT; no substrate types leak register_socks5_openablewires spec + establisher + pump + injected seams; missing-callback commands refuse RFC-faithfullyserve_socks5_over<T>is substrate-free and directly testable- Integration tests: round-trip, refusal mapping (0x05), FORBIDDEN fails closed, identity witness, refusal arms for ASSOCIATE/BIND, half-close teardown
cargo testgreen; clippy/fmt clean; both wasm checks clean
References
- docs/architecture/producer.md (the normative shapes; the wiring requirement)
- docs/architecture/decisions/001/004/005/006 (op, seams, auth, gates)
- POC sources:
/workspace/alksocks-connect-poc/src/producer.rs(CONNECT + establisher),/workspace/alksocks-udp-poc/src/producer.rs(the dispatch skeleton + registration) - alkcall ADR-049 (establishment; R-02 inline-await contract),
/workspace/@alkdev/alktunnels/src/producer.rs(the 0.8.0 registration template)
Notes
Implementation notes (2026-09-23):
Socks5ProducerBuilderwas added beyond the deliverable list — it is the spec'd assembly shape (producer.md §Wiring, theSocks5Producer::builder()sketch);register_socks5_openablestays as the explicit per-registration form. Both ride the sameregister_openable_with_establishercall; the builder'sregister/serve_socks5_overdelegate to them verbatim.- The
establisheracceptsOption<IdentityWitness>(the alktunnels*_with_witnessposture) rather than a required witness — the witness is a test seam, not a protocol surface;register_socks5_openablethreads it through.- The ASSOCIATE arm carries the
EgressFactoryparameter (and the BIND arm theBindAcceptFactory) in the registration signature from day one — the associate/bind tasks replace the refusal arms without touching the registration shape (no churn for the callers). Until those tasks land, the seams are accepted and their commands refuseCommandNotSupported(the pre-vertical posture, ADR-008's missing-callback class) — tested.- CONNECT's refusal error shape: dial-seam refusal returns
SocksServerError::Bug("dial refused in-stream")afterreply_error(the refusal was already RFC-reported; the error return is the session-end signal, not a second wire message). A missing dial callback refusesCommandNotSupportedviaSocksServerError::UnknownCommand(cmd).
Summary
Completed 2026-09-23 (commit on
main).src/producer.rsfilled: the session-gate establisher ([socks5_session_establisher] — CF-005/CF-006 witness seam,Establishment::default()per POC #1's no-plan wire note), the command-dispatch pump handler ([make_socks5_pump_handler] —accept_bi→ typestate (accept_no_auth → read_command) → dispatch), the CONNECT data plane (DialFnat the command-read interception point →reply_success(sentinel)→pump_bidiawaited inline, R-02),register_socks5_openable(spec + establisher + pump viaregister_openable_with_establisher,timeout: None= the 10s default,socks5:open-gated),serve_socks5_over<T>(the substrate-free entry), and the builder assembly shape.Dispatch arms: CONNECT serves; ASSOCIATE/BIND refuse
CommandNotSupported(the pre-vertical posture — the skeleton exists so the regression guard is testable early; the associate/ bind tasks replace the arms).Tests: 11 in-module (substrate-free, duplex-driven: round-trip + half-close teardown, refusal mapping per variant 0x05/0x03/0x04/ 0x02/0x01, missing-dial refusal, ASSOCIATE/BIND arms, dispatch guard, establisher witness + no-plan, builder shape, sentinel shape) + 5 harness integration (end-to-end CONNECT echo through channels with the identity witness, dial refusal 0x05 with no phantom data plane, scope-class refusal 0x02, ASSOCIATE/BIND refusals with CONNECT still serving — the one-registration regression guard, repeated CONNECTs on one registration). The harness's
RegisterModegained the real-producer mode; the minimal CONNECT stand-in is retained only as a harness-internal helper.Verification:
cargo test87+19+37 green; clippy--all-targetsclean; fmt clean; both wasm checks clean;cargo docclean;--all-featuresgreen.