- 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
13 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level |
|---|---|---|---|---|---|---|---|
| implementation/local-providers | Local (the socket-backed seam providers, tcp_dial / udp_egress / bind_accept) | completed | moderate | medium | component | implementation |
Description
The first batch-2 task: the local feature's first-party socket-backed
providers, the three seams' faithful-RFC constructors (ADR-008's
local variants, ADR-009's feature gate). These are the assembly-layer
convenience constructors over the landed seam types
(src/egress.rs); the protocol layer never sees them.
Deliverables:
Cargo.tomlfeature wiring:local = ["tokio/net"](the feature's own socket providers;socket2only if a provider actually needs it, though the POCs ran tokio-only, so default to NOT adding it). tokio staysdefault-features = falsein[dependencies]; thenetfeature rideslocalalone (ADR-009). Verify the default graph stays net-free (cargo treespot-check) and the default wasm checks stay green.src/local/mod.rs(featurelocal): the provider constructors, named per producer.md §Wiring:tcp_dial()→DialFn:TcpStream::connectper CONNECT target; domain targets resolve here (the ssh-Dsemantic; per ADR-012 resolution is the provider's job); the connected stream splits into theTargetHandlehalves (`Box<dyn AsyncRead + Send + Sync- Unpin>
;TcpStream::into_splithalves are Send+Sync, so verify the boxing compiles). Dial failures map toSocksDialErrorfaithfully (ConnectionRefusedfor connect-refused,NetworkUnreachable/HostUnreachableperio::ErrorKind,NotAllowed` never synthesized by this provider).
- Unpin>
udp_egress()→EgressFactory: one realUdpSocketper association (bind127.0.0.1:0, the assembly-layer bind decision, made explicit here), per-datagramsend_to/recv_from, no flow table; domain destinations resolve at the egress (resolve_target-shaped; port the POC #2LocalEgresshelper). The readiness contract is load-bearing:next_replyparks onsock.readable().awaitthentry_recv_from(WouldBlock→ theOk(None)spurious-wakeup shape; a real async park, never a hotOk(None)); replies carry inner bytes (TargetAddr::to_be_bytes() + data, the sender's address per the RFC's per-datagram source semantics; framing-once, so the length prefix is the wire layer's).sendtakes payload only. Egress failures map toEgressError(bind failure →General; resolve failure →Unreachable).bind_accept()→BindAcceptFactory: a realTcpListenerper BIND session (the faithful RFC shape, ADR-007 variant 1): the requested target's port honored when non-zero (FTP active mode), port 0 → ephemeral;local_addr()is reply#1'sbind_addr; the accept future owns the listener end-to-end (the quiet wait's drop IS the client-gone teardown, and the listener lives inside the future, never in a separate binding);accept()'s peer is reply#2'sBindTargetHandle.peer(byte-exact). Factory failures →BindAcceptFactoryError(bind refused/held port →RefusedorShortageper the io error; accept-stage failures →BindAcceptError). Address policy is the caller's: the constructor takes the bind host (loopback default is the POC's posture; a wildcard bind is the caller's explicit choice).- Module doc: names the ADRs (008/009), the never-binds posture (the providers ARE the explicit bind decisions), and the seam contracts each constructor must honor (readiness, ownership, accept-future-owns, framing-once).
- Tests (
src/local/unit + atests/local-providers.rsintegration file): real sockets (dev-deps already carry full tokio). Echo TCP server fortcp_dialround-trips; a real UDP echo for the egress (datagram round-trip through the producer stage, the POC #2 shape); a real BIND accept (dial the listener, reply#1/reply#2 carry the real addresses); the readiness contract re-pinned with the real socket egress (park-then-datagram, no spin); refusal mappings (connect-refused → 0x05 through the producer's reply).
Acceptance Criteria
cargo testpasses;cargo test --all-featurespassescargo clippy --all-targets -- -D warningsclean;cargo fmt --checkclean- Default wasm checks stay green (
cargo check/clippy --target wasm32-unknown-unknown -- -D warnings), meaninglocalcode is cfg-gated out of the default build - Default dependency graph has no
tokio/net/mio/socket2(cargo treespot-check);local = ["tokio/net"]wired tcp_dial/udp_egress/bind_acceptland with the exact producer.md §Wiring names and the landed seam types- The egress readiness contract honored (real async park; no hot
Ok(None)), pinned by test - The accept future owns its listener end-to-end, pinned by a drop-witness test (client-gone → listener closed)
- Refusal mappings table-tested against the landed error types'
to_reply_error()
References
- docs/architecture/decisions/008-provider-defaults.md (the local variants), 009-feature-gates.md (the feature layout), 004 (the seams), 007 (BIND variant 1), 012 (resolution at the egress)
- docs/architecture/producer.md §Wiring, §The Three Seams
- POC reference:
/workspace/alksocks-udp-poc/src/egress.rs(LocalEgress, the readiness shape),/workspace/alksocks-bind- poc/src/accept.rs(local_bind_accept) - Landed seams:
src/egress.rs(the contracts' normative doc comments)
Notes
Filled by the implementation agent (2026-09-25).
Cargo.toml:local = ["tokio/net"](the feature's own socket providers; nosocket2, because the POCs ran tokio-only and nothing here needs it). tokio staysdefault-features = falsewith the wasm-clean subset; thenetfeature rideslocalalone (ADR-009).cargo treespot-checks: the default graph (-e normal,features --no-default-features) carries notokio/net/mio/socket2(inverting-i mioprints nothing); with--features local --target allthenetfeature appears rooted atalksocks feature "local", draggingmio+socket2in through tokio (the dev-depfullalso appears in dev builds, pre-existing and test-only, not the feature's doing).src/local/mod.rs(featurelocal, one module file + atestssubmodule per convention 15):tcp_dial()→DialFn:TcpStream::connectper CONNECT target; domain targets resolve here (the ssh-Dsemantic, with ADR-012 in play), iterating the resolved address set (the-Dconnect-multi-address posture: first success wins, the last failure is the reported error);into_splithalves boxed asSend + Sync + Unpin(compiles, pinned by the round-trip test). Refusal mapping (dial_error_of):ConnectionRefused→SocksDialError::Refused,NetworkUnreachable/TimedOut→NetworkUnreachable,HostUnreachable→HostUnreachable, resolution failure →HostUnreachable, everything else →General;NotAllowednever synthesized.udp_egress()→EgressFactory: one realUdpSocketper association (bind127.0.0.1:0, the explicit assembly-layer bind decision,LocalEgress::bind); per-datagramsend_to/recv_from, no flow table; domain destinations resolve at the egress (resolve_target, porting the POC #2LocalEgresshelper ported). The readiness contract is the load-bearing shape:next_replyparks onsock.readable().await(a real async park on receive readiness, sincewritable()is always ready for UDP and would spin), thentry_recv_from(WouldBlock→ the spurious-wakeupOk(None)); replies carry inner bytes (TargetAddr::to_be_bytes() + data, the sender's address per the RFC's per-datagram source semantics; framing-once);sendtakes payload only. Bind failure →General; resolve failure →Unreachable.bind_accept(bind_host)→BindAcceptFactory: a realTcpListenerper BIND session (ADR-007 variant 1); the requested port honored when non-zero, port 0 → ephemeral;local_addr()is reply#1'sbind_addr; the accept future owns the listener end-to-end (the listener lives inside the future, so the quiet wait's drop IS the client-gone teardown);accept()'s peer is reply#2'sBindTargetHandle.peer(byte-exact). Factory failures →BindAcceptFactoryError(PermissionDenied/AddrInUse→Shortage,AddrNotAvailable→Refused, elseGeneral); accept-stage failures →BindAcceptError(General). Address policy is the caller's: the constructor takes the bind host (IpAddr-convertible; loopback is the POC's posture, and the wildcard bind is the caller's explicit0.0.0.0).- Module doc names ADR-008/009, the never-binds posture (the providers ARE the explicit bind decisions), and the four seam contracts (readiness, ownership-rides-the-handle, framing-once, accept-future-owns).
src/lib.rs:#[cfg(feature = "local")] pub mod local;+ thetcp_dial/udp_egress/bind_accept/LocalEgressre-exports; the crate doc's "not yet present" paragraph updated (the composed providers remain pending).- Tests: 12 unit tests (
src/local/tests.rs: the TCP echo round-trip incl. domain resolution, connect-refused/unresolvable mappings, the UDP echo round-trips (inner bytes + sender address, in-order, empty payload), the park-then-datagram readiness probe, the BIND accept (real reply#1/reply#2, requested-port honored, held-port refusal, the drop-closes-the-listener witness)) + 6 integration tests (tests/local-providers.rs, feature-gated with#![cfg(feature = "local")]: CONNECT through a real echo target, connect-refused → 0x05 through the producer's reply, ASSOCIATE through the producer's datagram stage with the real egress, the readiness contract re-pinned through the stage (park-then-datagram, no spin), BIND end-to-end with real addresses both directions, and the client-gone-mid-quiet-wait drop-witness (the listener's port frees)). - Deliberate deviations from the POC code: the dial iterates the
full resolved address set (the POC resolved to the first address);
TimedOutmaps toNetworkUnreachableandPermissionDeniedtoGeneralin the dial table (the POC had no io-error mapping at all);bind_accepttakes the bind host as a parameter (the POC hardcoded127.0.0.1) so the wildcard bind is the caller's spelled-out choice; the held-port factory refusal maps toShortage/Refusedper the io kind (the POC mapped everything toShortage).
Summary
Filled on completion (2026-09-25).
Implemented the local feature's socket-backed seam providers
(ADR-008's local variants, ADR-009's feature gate), the three seams'
faithful-RFC assembly-layer constructors over the landed seam types
(src/egress.rs): tcp_dial() (real TcpStream per CONNECT, domain
resolution at the provider, faithful SocksDialError mapping),
udp_egress() (one real UdpSocket per association bound
127.0.0.1:0, per-datagram send/recv, the POC #2 LocalEgress
readiness shape: park on readable(), try_recv_from, inner bytes
to_be_bytes() + data, framing-once), and bind_accept(bind_host)
(a real TcpListener per BIND session, requested port honored, the
accept future owning the listener end-to-end, two-stage failure
mapping). The protocol layer never sees them; these are feature-gated
(local = ["tokio/net"]), the default graph stays
net/mio/socket2-free (cargo tree spot-checked both ways), and the
wasm checks stay green. 18 new tests (12 unit + 6 integration) pin:
the real-socket round-trips through the producer's actual dispatch
path (echo TCP, UDP echo through the framed datagram stage, BIND
end-to-end with byte-exact real addresses), the readiness contract
with the real socket (park-then-datagram, no spin), the
accept-future-owns-its-listener contract (drop-witness: client-gone
→ the advertised port frees), and the refusal mappings
table-tested against the landed error types' to_reply_error()
(connect-refused → 0x05 through the producer's reply, held port →
Shortage/Refused → the RFC catch-all, unresolvable domain →
HostUnreachable/NetworkUnreachable).
Verification: cargo test (157 green across the 9 targets) +
cargo test --all-features green, cargo clippy --all-targets -- -D warnings clean (default + --all-features), cargo fmt --check
clean, both wasm checks (cargo check + cargo clippy -D warnings
on wasm32-unknown-unknown) clean, cargo tree spot-checks: the
default graph carries no tokio/net/mio/socket2 and
--features local --target all roots the net feature at
alksocks feature "local".