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.
164 lines
8.7 KiB
Markdown
164 lines
8.7 KiB
Markdown
# 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<A>`/`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<T>` 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) |