Files
glm-5.3-flash e196ed43e3 docs: remove em dashes from repo prose and forbid them going forward
- 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
2026-11-03 19:31:53 +00:00

9.1 KiB


status: draft last_updated: 2026-09-25 (the cred-bearing door's call-connection contract reconciled: TokenClient's door-only posture, one command per accepted connection; the BIND spent posture reconciled: the take-guarded reply#2 reader, review 002 C-1/D-1; the landed session signatures reconciled 2026-09-24; Phase 1 initial draft 2026-09-20)

alksocks: The Consumer Half

The consumer opens SOCKS5 channels against a produced service and speaks RFC 1928 inside them. Two consumption shapes, structurally separate (the Phase 0 scope resolution):

  1. The channels-native session: a typed client that opens channels/socks5/sub and drives the RFC conversation in-stream (wrapper-aware; the POC #1 use_stream / POC #2 associate session shapes).
  2. The front door (optional, feature local): a real local endpoint (TCP for CONNECT/BIND; UDP for ASSOCIATE) that vanilla clients point at; the door bridges each accepted connection/socket onto a channel, with the RFC codec living only at that boundary.

Both wrap the same protocol layer; neither is required by the other.

The Channels-Native Sessions

Socks5ChannelSession (CONNECT)

Opens channels/socks5/sub via ChannelClient::open_channel (alkcall ADR-037/043), then drives the client side of the RFC conversation in-stream over the channel's BiStream:

  • the forked client typestate's use_stream path, generic over the backing stream (POC #1: Socks5Stream impls AsyncRead + AsyncWrite itself, so the session over a BiStream is the intended use, not a hack);
  • target construction via ToTargetAddr (IP-string forms parse to IP targets; domain form passes through unresolved, ADR-012);
  • after the reply, the session is a raw byte pipe: the consumer reads/writes payload bytes directly (0 B framing, CONNECT).

The associate session

The UDP ASSOCIATE client (POC #2's shape):

  • opens the channel, negotiates, sends ASSOCIATE, reads the reply; receives the sentinel BND.ADDR and never uses it for addressing (the handle is the channel; ADR-003);
  • send_datagram(dst, bytes) frames once ([len: u16 BE][addr + data], the RFC 1928 address encoding) and writes the frame;
  • recv_datagram() returns Some((source_addr, bytes)) per datagram; its incremental reader state (mid-frame position + batched-datagram pending queue) persists across calls (a datagram split across channel chunks survives a caller-driven receive loop; POC #2's finding);
  • shutdown() half-closes the stream; the mux's implicit-EOF sentinel reaches the producer's stage as EOF and the association ends (association lifetime = channel lifetime);
  • teardown ownership: the session owns the channel; on Drop it closes/joins (the alktunnels ADR-005 posture: the consumer session type owns teardown), combined with ADR-004's ownership-rides-the-handle contract.

The BIND client

The BIND reply#2 reader (POC #5's read_reply2 sketch): the forked client request() reads reply#1; reply#2 is hand-read with the forked public read_address codec (reply#1's codec applied again; no second codec). The session then rides the pass-through data plane. The reply#2 phase is take-guarded: the one normative reader is await_inbound, and the first read consumes the session's stream (the returned plane owns it; the session stays the teardown owner), so a second call returns the family's spent shape (Ok(None), the same posture the sibling sessions pin) and cannot re-read the live data plane; a failed read restores the inner (the producer ends the session on every post-reply#1 failure exit, so a re-call EOFs; never a live data-plane re-read either way). The reply1() handle is a view (the advertised BND.ADDR + the raw pre-reply#2 stream), not a reader. The BIND client is a wrapper-aware-consumer convenience; vanilla clients reach BIND through the front door instead.

Error surface

SocksServerError / SocksError (the forked surface) mapped faithfully; channel-open failures are the typed channel:open_failed reasons (alkcall ADR-049). A refused session never yields a phantom channel; a refused target is an RFC reply code surfaced through the session type.

The Front Doors (feature local, optional)

The consumer's optional local exposure, the ssh -D shape (POC #1: curl end-to-end; POC #2 test 8: the UDP door; POC #5 test 8: the BIND door):

  • The CONNECT/BIND door: bind a real TCP listener; for each accepted connection, pipe it raw onto a channel BiStream (copy_bidirectional, zero protocol translation, POC #1's literal 0 B pass-through conclusion). The door's credential half (below) is the only RFC work it does.
  • The UDP door: bind a real UDP socket; decode RFC-header'd datagrams (parse_udp_request), send {addr, data} frames in-band, return replies RFC-header'd (new_udp_header): the RFC codec at the door boundary only (ADR-003). FRAG ≠ 0 → refuse (the v1 posture).
  • The BIND door's reply#2 fidelity: the local variant's real listener/peer addresses pass through the pipe unchanged (the conversation is the vanilla client's own); the door adds nothing.

Door variants and auth (ADR-005)

  • The NoAuth door (POC #1's raw-pipe shape): zero protocol translation; for loopback-only or wrapper-aware use.
  • The cred-bearing door (RFC 1929): a vanilla client carrying user:pass gets a real RFC 1929 conversation at the door; the door maps the accepted credential to local configuration (which channel identity/endpoint to open the producer channel with) via a static config table (ADR-005: user:pass → channel identity/endpoint selection). The SOCKS password never crosses the channel as a SOCKS password; the channel identity never reaches the vanilla client. The door checks credentials itself (no forked AuthMethod impl needed; the door owns the accept loop). The mapped identity rides the open-op call's auth_token payload (CF-005 precedence (1)) via TokenClient::wrap; the client's channel-0 CallConnection is taken once per door (the door-only contract: a cred-bearing door's client is a door client; other shapes, such as the UDP door's association and channels-native sessions, ride their own connections to the same producer registration, one registration, many connections). The vanilla conversation is one command per accepted connection (the door's RFC half consumes the greeting + one command; the data plane rides to EOF).

Door variant selection (NoAuth vs cred-bearing; the UDP door's bind address) is assembly-layer configuration.

Wiring (Assembly Layer)

let session = Socks5ChannelSession::connect(
    &channel_client, target).await?;
let (mut read, mut write) = session.split();   // post-reply payload pipe

let assoc = AssociateSession::associate(&channel_client).await?;
assoc.send_datagram(dst, &bytes).await?;       // frames once, ADR-003
let (src, bytes) = assoc.recv_datagram().await?.expect("a datagram");

let door = FrontDoor::builder()
    .credentials(cred_table)       // RFC 1929 mapping, ADR-005
    .bind("127.0.0.1:1080")
    .build();
door.serve(channel_client).await?;

The landed session signatures: the constructors take &ChannelClient (shared; many sessions ride one client, the concurrency posture); send_datagram takes impl ToTargetAddr (the CONNECT session's target-construction posture, ADR-012); recv_datagram() returns Result<Option<(TargetAddr, Bytes)>, SocksSessionError>; None is the channel EOF (the association's clean end), never an empty datagram (F-2: an empty payload is a legal datagram).

Design Decisions

ADR Decision Summary
001 ALPN + op the consumer opens the one op
003 The associate session sentinel BND.ADDR; frames once; reader state persists
004 Ownership rides the handle handle payloads own channel sessions; the session-teardown posture is inherited from alktunnels ADR-005 (the consumer session type owns teardown)
005 Door auth RFC 1929 at the door; static credential→identity mapping
007 BIND client the reply#2 reader; vanilla clients via the door
011 The extraction the client use_stream path in-tree
013 The targeted fork the core is owned (src/rfc1928/)

Open Questions

Open questions are tracked in open-questions.md. None currently blocking this document.

References

  • POC #1 (poc-connect-wasm-findings.md: the raw-pipe door, the use_stream fit), POC #2 (poc-udp-associate-findings.md: the associate session, the reader-state finding, the UDP door), POC #5 (poc-bind-findings.md: the reply#2 reader, the BIND door)
  • Upstream: alkcall ADR-037/043 (channel open), alktunnels ADR-005 (consumer session owns teardown; the posture inherited), the CF-005/CF-006 seam
  • producer.md (the other half), wire.md (the normative wire)