# ADR-011: the Inline Vendor: fast-socks5's `T`-Generic Subset, In-Tree ## Status Superseded by [ADR-013](013-targeted-fork.md) (2026-09-22): the maintenance posture (the vendor framing, re-base/re-vendor discipline, the verbatim/lint-allow apparatus) is retired; the extracted region is a targeted fork, module `rfc1928`. **The extraction mechanics here remain authoritative**; the cut table, the two carried changes (BIND, `new_reply`), the §Amendment (extraction, not wholesale copy; the net gate dissolves) describe what was copied and what was not; only the post-copy maintenance posture changed. Originally Accepted 2026-09-20 (Phase 1; OQ-SK-04's resolution), Amended 2026-09-21 (pre-decomposition). ## Context fast-socks5 is **third-party upstream** (v1.0.0, MIT; `/workspace/fast-socks5` is a read-only reference checkout; we do not own it, and it has not been forked). The wrapper's needs go beyond what it publishes: 1. **The wasm gate**: `tokio::net` + `socket2` (via `mio`) hard-error on `wasm32-unknown-unknown`; the default crate must compile there (AGENTS.md convention 4). POC #4 measured the gate: ~120 changed lines across 5 files, all `#[cfg]` insertions; the wasm-clean subset is functionally complete (a full RFC 1928 CONNECT conversation passes `--no-default-features`). 2. **BIND is absent**: `Socks5Command::TCPBind` is parsed but every handler refuses it; POC #5 proved the wrapper must drive the typestate itself for the BIND shape (ADR-007). 3. **The `run_udp_proxy_custom` bind seam**: the "custom" seam is narrower than it looks: it calls `udp_bind_random_port` unconditionally and derives the reply port from that kernel socket; the channels path cannot use it (verified 2026-09-13). The channels path drives the typestate directly; making the bind optional / accepting a caller-supplied socket is an in-tree fix. 4. **`new_reply` is private**: the reply encoder is private; the BIND reply#2 writer otherwise hand-rolls the 10-byte reply (POC #5's courtesy item, now a vendor change). Two facts settled the posture: the ownership premise was wrong (no cheap "file asks / PR" path), and the carried needs (2–4) exist regardless of the wasm question, so the vendor pays for itself without leaning on the adapter story. The change set is exactly the shape POC #4 measured as minimal. (Need 1 later dissolves, §Amendment; needs 3's handler is not extracted with it.) ## Decision - **Extract the `T`-generic subset into this crate** (see §Amendment for the mechanical reading of "extract"): the codecs, the typestate machinery, the client `use_stream` path, the parts already generic over `T: AsyncRead + AsyncWrite + Unpin`. The extraction contains zero kernel-net code and is wasm-clean by construction. - **Carry the four needs in the same vendor**: the net gate, BIND (the typestate-driven shape, ADR-007), the `run_udp_proxy_custom` seam fix, and the `new_reply` publication. (Post-Amendment: the net gate dissolves; see §Amendment; BIND and `new_reply` are the two that touch extracted code.) - **Note the vendor point (v1.0.0) in the vendored code**; keep the MIT license obligations (upstream is MIT; preserve the license header per file or a bundled notice). - **`/workspace/fast-socks5` stays the differential-testing reference** for RFC edge cases (reply-code mapping, domain addressing, fragmentation); keep `/workspace/fast-socks5` for differential testing; never wait on an upstream merge. - **Offering the net-gate upstream as a courtesy PR stays optional** (POC #4: it is small enough to offer as-is), never a wait, never a dependency. ## Consequences - The crate owns its protocol code in-tree: no upstream release cadence, no feature waits, and the vendored code co-evolves with the wrapper (the carried changes are first-class, not patches). - The cost is maintenance of the extracted subset (the `T`-generic region + codecs, a fraction of upstream's ~4k LoC; POC #4's ungated inventory is the shopping list). The diff vs upstream is deliberate and bounded: the extraction cut + the carried changes + the license/provenance notes. - **Nothing in the crate routes through the kernel-net handlers**; if a future need surfaces (e.g. a standalone vanilla-server example), the fix is re-vendor that piece then, as an additive decision. - Differential testing against `/workspace/fast-socks5` guards the RFC edge cases the extracted code must keep passing. ## Amendment (2026-09-21, pre-decomposition: "extract" pinned, the `net` gate dissolves) The decision's wording ("vendor the `T`-generic subset") supported two mechanical readings; this amendment pins the intent so the decomposition task is unambiguous: - **Extraction, not wholesale-copy-plus-gate.** Copy *only* the `T`-generic inventory into `src/vendor/`; the kernel-net handlers (`run_tcp_proxy`, `run_udp_proxy*`, `udp_bind_random_port`, the legacy `Socks5Server`/`Incoming` API, `Socks5Datagram`'s kernel socket path, the DNS-resolve helper, the net-only tests), the `socks4` module (upstream's separate default-off `socks4` cargo feature, which POC #4 never compiled; out of this crate's RFC 1928 scope), and the legacy `Config`/`Authentication` auth layer (ungated upstream, but consumed only by the cut legacy/net region) are **not copied into the crate**; they remain available in `/workspace/fast-socks5`, the differential-testing reference. The kernel-net region is carried only as the cfg *map* (POC #4's five-file inventory), recorded in `vendor.md` so a future re-vendor knows the cut. - **Why**: convention 17's letter ("vendor the `T`-generic subset"); no crate consumer routes through the kernel-net region (the producer/consumer/doors drive the typestate and the pure codecs; the local backends are first-party tokio code, ADR-004/ADR-008); and the BIND + `new_reply` needs touch only the extracted region (BIND's driving is wrapper-side code per POC #5, "zero upstream ask for BIND itself"). - **The `net` feature gate dissolves.** With no kernel-net code in-tree, the extraction is wasm-clean by construction; no `#[cfg]` gate needed inside the vendored files. Features collapse to two: `local` (the crate's first-party socket providers and front doors, ADR-008) and `composed` (the alktunnels providers). POC #4's gate map survives as the *extraction recipe* (its ungated inventory is exactly what to copy), not as in-tree cfg. tokio stays `default-features = false` with the wasm-clean subset (AGENTS.md convention 4); `tokio/net` rides `local` alone. - **The carried-changes list re-reads under extraction**: (1) the net gate, dissolves (no net code); (2) BIND, stays (the typestate driving + reply#2 writer are in-tree; ADR-007); (3) the `run_udp_proxy_custom` bind seam, dissolves (the handler isn't extracted; the typestate-driving shape POC #2 validated is the crate's ASSOCIATE path); (4) `new_reply` publication, stays (it is inside the extracted codec region). Net: **two carried changes** touch extracted code. - **Extraction completeness is compiler-checked**: a missed transitive dependency is a build error, and the wasm check (`cargo check --target wasm32-unknown-unknown`, plus `--no-default-features` native) is the structural guard. The extracted crate must compile clean with no `net`-shaped feature. - **The extraction's dependency surface is adjusted, not absorbed**: upstream's `log` calls in the extracted region are dropped or re-pointed at the crate's `tracing` (the alk* convention); the extracted region's `anyhow::Context` error wrapping is dropped (error surface is the crate's `thiserror` types; the vendored `SocksError`/`SocksServerError` stay the wire-level vocabulary); `async-trait` is dropped (its four upstream uses are all on the legacy `Authentication` layer, which is cut; the typestate's `AuthMethod` traits are native async-fn-in-trait); the `socks4` module and the legacy `Config`/`Authentication` layer are out (above). These are extraction-time mechanical adjustments to the copied code, not new carried changes. The verification commands in AGENTS.md that mention the wasm check failing on the dependency graph are stale for this crate (that caveat predated OQ-SK-04's resolution); the extraction makes the wasm check a hard gate as in alktty/alktunnels. ## References - OQ-SK-04 (the resolved posture; the reasoning trail), OQ-SK-08 (the BIND + `new_reply` needs), OQ-SK-03 (the bind seam) - POC #4 + POC #1 (`poc-connect-wasm-findings.md`: the measured gate), POC #5 (`poc-bind-findings.md`: the BIND typestate driving + the `new_reply` finding) - ADR-002 (the protocol base), ADR-007 (BIND), ADR-009 (the feature gates); `/workspace/fast-socks5` (the reference checkout, v1.0.0)