- 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
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) |