ADR-013 supersedes ADR-011's maintenance posture (not its mechanics):
the extracted T-generic subset is fully owned code, the vendor
framing is retired. Module renamed src/vendor/ -> src/rfc1928/
(naming decided in the ADR: 'vendor' names an ownership relation
now gone; 'socks5' would stutter and collide with the future
producer/consumer layers). Fork point = the tree as of the
extraction-review commit (5b18d4d); provenance stays permanent
(module docs + the extended LICENSE notice).
- ADR-013: fork posture, license mechanics (notice extended, not
replaced; post-fork modifications under the crate license),
hand-port replaces re-vendor, lint-allow posture dissolved
- ADR-011 marked Superseded (extraction mechanics remain
authoritative); ADR-002 references re-pointed
- vendor.md -> rfc1928.md rewritten as the owned-core spec (the cut
table re-read as the hand-port map)
- spec sweep: README index (ADR-013 row, doc table, principle 8),
overview (module map, deps, ADR table), producer/consumer/wire/
bast reference vocabulary, open-questions OQ-SK-04 resolution,
AGENTS.md conventions 4/17 + verification note + context bullet,
Cargo.toml description
Verification: cargo doc --no-deps clean; no stale vendor.md/src-vendor
references outside the ADR-011 historical text.
8.7 KiB
ADR-011: the Inline Vendor — fast-socks5's T-Generic Subset, In-Tree
Status
Superseded by ADR-013 (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:
- The wasm gate —
tokio::net+socket2(viamio) hard-error onwasm32-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). - BIND is absent —
Socks5Command::TCPBindis parsed but every handler refuses it; POC #5 proved the wrapper must drive the typestate itself for the BIND shape (ADR-007). - The
run_udp_proxy_custombind seam — the "custom" seam is narrower than it looks: it callsudp_bind_random_portunconditionally 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. new_replyis 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 clientuse_streampath — the parts already generic overT: 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_customseam fix, and thenew_replypublication. (Post-Amendment: the net gate dissolves — see §Amendment; BIND andnew_replyare 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-socks5stays the differential-testing reference for RFC edge cases (reply-code mapping, domain addressing, fragmentation) — keep/workspace/fast-socks5for 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-socks5guards 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 intosrc/vendor/; the kernel-net handlers (run_tcp_proxy,run_udp_proxy*,udp_bind_random_port, the legacySocks5Server/IncomingAPI,Socks5Datagram's kernel socket path, the DNS-resolve helper, the net-only tests), thesocks4module (upstream's separate default-offsocks4cargo feature, which POC #4 never compiled; out of this crate's RFC 1928 scope), and the legacyConfig<A>/Authenticationauth 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 invendor.mdso 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_replyneeds touch only the extracted region (BIND's driving is wrapper-side code per POC #5 — "zero upstream ask for BIND itself"). - The
netfeature 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) andcomposed(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 staysdefault-features = falsewith the wasm-clean subset (AGENTS.md convention 4);tokio/netrideslocalalone. - 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_custombind seam — dissolves (the handler isn't extracted; the typestate-driving shape POC #2 validated is the crate's ASSOCIATE path); (4)new_replypublication — 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-featuresnative) is the structural guard. The extracted crate must compile clean with nonet-shaped feature. - The extraction's dependency surface is adjusted, not absorbed:
upstream's
logcalls in the extracted region are dropped or re-pointed at the crate'stracing(the alk* convention); the extracted region'sanyhow::Contexterror wrapping is dropped (error surface is the crate'sthiserrortypes — the vendoredSocksError/SocksServerErrorstay the wire-level vocabulary);async-traitis dropped (its four upstream uses are all on the legacyAuthenticationlayer, which is cut — the typestate'sAuthMethod<T>traits are native async-fn-in-trait); thesocks4module and the legacyConfig/Authenticationlayer 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_replyneeds), 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 + thenew_replyfinding) - ADR-002 (the protocol base), ADR-007 (BIND), ADR-009 (the feature
gates);
/workspace/fast-socks5(the reference checkout, v1.0.0)