Files
alksocks/docs/architecture/decisions/011-inline-vendor.md
T
glm-5.3-flash 001aac1fd5 docs: the targeted fork — ADR-013, the RFC 1928 core is owned
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.
2026-09-22 12:33:36 +00:00

164 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)