docs: pin the vendor scope as extraction — the net gate dissolves

Complete the ADR-011 §Amendment (extraction, not wholesale-copy-plus-
gate) across every doc that stated the pre-amendment posture:

- ADR-011: add the two extraction-recipe exclusions the amendment
  missed (the socks4 module — upstream's separate default-off cargo
  feature, out of RFC 1928 scope; the legacy Config/Authentication
  auth layer — ungated but consumed only by the cut region) and the
  dependency-surface adjustments (log -> tracing, anyhow dropped,
  async-trait dropped with its legacy-layer uses)
- ADR-009: amend — the vendor's net gate dissolves; features collapse
  to local + composed; local carries tokio/net + socket2 itself
- vendor.md: replace the contradictory old "four carried changes"
  section with the two-change reading + extraction-time adjustments;
  extend the cut table (socks4 row, legacy auth layer)
- overview.md: dependency diagram + feature table drop the net
  feature; module map reflects two carried changes
- README.md: principles 7/8 + doc/ADR tables updated
- ADR-002/012, open-questions.md (OQ-SK-04), phase-0.md current-state
  sections, AGENTS.md conventions 4/17 + stale wasm-caveat sentence,
  POC findings posture notes: aligned with the extraction

Verification: repo-wide grep for stale "four carried"/default-on net/
implies-net statements returns only amendment-context matches;
extraction recipe re-verified against POC #4's gate map and the
fast-socks5 v1.0.0 source (async-trait usage confined to the cut
legacy layer; AuthMethod<T> traits are native async-fn-in-trait).
This commit is contained in:
glm-5.3-flash committed 2026-09-21 03:15:04 +00:00
1 parent 9587670138
commit b0cfc26065
12 files changed
+367 -178

No files matched your search

+14 -12
View File
@@ -1,7 +1,8 @@
---
status: draft
last_updated: 2026-09-20 (Phase 1 initial draft — the OQ-SK ledger
promoted into ADRs 001-012 and open-questions.md)
last_updated: 2026-09-21 (the vendor extraction amendment — ADR-011
§Amendment, ADR-009 §Amendment; Phase 1 initial draft 2026-09-20 —
the OQ-SK ledger promoted into ADRs 001-012 and open-questions.md)
---
# alksocks — Architecture
@@ -34,7 +35,7 @@ decomposition phase.
| [wire.md](wire.md) | draft | The wire format: the open op (trivial params — ADR-001), the phase model, the data planes (CONNECT pass-through / ASSOCIATE frames / BIND phases), sentinels |
| [producer.md](producer.md) | draft | The producer half: the session-gate establisher, command dispatch, the three seams (dial/egress/accept), scope gates, the local backend |
| [consumer.md](consumer.md) | draft | The consumer half: the channels-native sessions (connect/associate/bind), the front doors, door auth (ADR-005), teardown ownership |
| [vendor.md](vendor.md) | draft | The inline vendor: the vendored fast-socks5 subset's scope, the four carried changes, the maintenance posture |
| [vendor.md](vendor.md) | draft | The inline vendor: the extracted fast-socks5 subset's scope (extraction, not wholesale copy), the two carried changes, the maintenance posture |
| [bast.md](bast.md) | draft | The BAST document for the ASSOCIATE datagram frame (ADR-003); pass-through stages have no binary framing to describe |
| [open-questions.md](open-questions.md) | draft | Centralized OQ tracker (OQ-SK-01..09 promoted; new questions as OQ-SK2-06.. — the SK2 numbering is not contiguous from 01, working-draft numbers were consumed) |
@@ -50,9 +51,9 @@ decomposition phase.
| [006](decisions/006-access-control.md) | Access-Control Posture — the Open Gate Plus the Command Gate | Accepted |
| [007](decisions/007-bind-in-base.md) | BIND in the Base Crate — the Phase Model Extended | Accepted |
| [008](decisions/008-provider-defaults.md) | Egress/Accept Provider Defaults — Local Socket and Composed (Direct Op) | Accepted |
| [009](decisions/009-feature-gates.md) | Feature-Gate Layout — `net`, `local`, and the Composition Boundary | Accepted |
| [009](decisions/009-feature-gates.md) | Feature-Gate Layout — `local` and the Composition Boundary | Accepted (amended 2026-09-21) |
| [010](decisions/010-channel-capacity.md) | Channel Capacity — Proxy-Workload Recommendation, Not a Wire Change | Accepted |
| [011](decisions/011-inline-vendor.md) | the Inline Vendor — fast-socks5's `T`-Generic Subset, In-Tree | Accepted |
| [011](decisions/011-inline-vendor.md) | the Inline Vendor — fast-socks5's `T`-Generic Subset, In-Tree | Accepted (amended 2026-09-21) |
| [012](decisions/012-target-policy.md) | Target Policy and the Virtual-Subnet Translation Layer | Accepted |
Upstream ADRs (alkcall/alktunnels/alknet) are referenced by their own
@@ -90,14 +91,15 @@ format, `pump_bidi`, identity seam, the channel caps).
convention holds for all three commands; the proxy-workload cap
posture is ADR-010 (config, not wire).
7. **Wasm-clean default crate** (ADR-009): the default crate is
protocol-only; the vendored subset rides the default-on `net`
gate (ADR-011); `local` is opt-in and non-wasm; `composed` rides
alktunnels' wasm-clean default (verified with the wasm check at
implementation).
protocol-only; the vendor is an extraction with zero kernel-net
code — there is no `net` feature (ADR-011 §Amendment); `local` is
opt-in and non-wasm; `composed` rides alktunnels' wasm-clean
default (verified with the wasm check at implementation).
8. **The vendor is first-class** (ADR-011): the `T`-generic subset
in-tree with the four carried changes; `/workspace/fast-socks5`
stays the differential-testing reference; nothing waits on
upstream.
extracted in-tree with two carried changes (BIND, `new_reply`);
`/workspace/fast-socks5` stays the differential-testing reference
and the only place the full upstream surface (the kernel-net
region) exists; nothing waits on upstream.
## Document Lifecycle
@@ -30,10 +30,11 @@ functionally complete (a full RFC 1928 CONNECT conversation passes
`Socks5Server`/`Socks5Socket`/`Incoming` bind-based API is
deprecated upstream and is out — never vendored into the default
path.
- **Base = v1.0.0, inline-vendored** (see ADR-011 for the vendor
mechanics and the four carried changes). `/workspace/fast-socks5`
remains the read-only reference for differential testing of RFC
edge cases; the vendor point is noted in the vendored code.
- **Base = v1.0.0, inline-vendored as an extraction** (see ADR-011
for the vendor mechanics and the two carried changes).
`/workspace/fast-socks5` remains the read-only reference for
differential testing of RFC edge cases; the vendor point is noted
in the vendored code.
- **Refusals map faithfully per RFC 1928** — `ReplyError` codes
(`as_u8`/`from_u8`) are the error vocabulary at the command plane;
refusals are never collapsed into generic errors (AGENTS.md
@@ -41,9 +42,8 @@ functionally complete (a full RFC 1928 CONNECT conversation passes
## Consequences
- The crate owns its protocol code in-tree (the vendor), so the
wasm gate, BIND, and the seam fixes ride the same tree — no
upstream waits.
- The crate owns its protocol code in-tree (the vendor), so BIND and
the `new_reply` publication ride the same tree — no upstream waits.
- The typestate's interception points are the layering seams:
command-read (the dial/accept dispatch), `transfer` (the raw
two-pump), and — where the default handlers are unusable (UDP
@@ -1,35 +1,34 @@
# ADR-009: Feature-Gate Layout — `net` (the Vendor's Gate), `local`, and the Composition Boundary
# ADR-009: Feature-Gate Layout — `local` and the Composition Boundary
## Status
Accepted (2026-09-20, Phase 1; the wasm-clean default-crate invariant
is the alk* family posture — AGENTS.md convention 4)
is the alk* family posture — AGENTS.md convention 4). Amended
2026-09-21 (pre-decomposition): the vendor's `net` gate dissolves
under ADR-011's extraction amendment — the vendor is
wasm-clean by construction; features collapse to `local` +
`composed`; see §Amendment.
## Context
The default crate must compile to `wasm32-unknown-unknown` (the
alktty/alktunnels precedent; the wasm shape is a sandboxed
SOCKS5 consumer — pure byte framing, no local sockets needed
client-side; phase-0 OQ-SK-04's analysis). Three groups of
client-side; phase-0 OQ-SK-04's analysis). Two groups of
platform-specific or sibling-dependent code need gating:
1. **The vendored fast-socks5 subset's kernel-net touches** — POC #4
measured the gate at ~120 cfg lines across 5 files, keyed on one
feature (`net = ["tokio/net", "dep:socket2", "dep:tokio-stream"]`,
default-on for native; wasm users build with
`default-features = false`).
2. **The crate's own local backends** (dial/listen/egress providers
1. **The crate's own local backends** (dial/listen/egress providers
touching real sockets — ADR-008's local variants).
3. **The composed providers** (the alktunnels-composed egress/accept
2. **The composed providers** (the alktunnels-composed egress/accept
helpers — ADR-008's composed variants), which need the
alktunnels dependency.
One design constraint from OQ-SK-04's resolution: the vendored
subset's `net` gate and the crate's own backend gate are
*mechanically the same boundary* (tokio::net + socket2), but their
*ownership* differs — the vendor's gate ships with the vendored
code (v1.0.0-faithful + carried changes), the crate's local backends
are first-party code.
The vendor originally contributed a third gate: its kernel-net
region was to ride a default-on `net` feature (POC #4's measured
shape, ~120 cfg lines across 5 files). ADR-011's 2026-09-21
amendment pins the vendor as *extraction* — the kernel-net region is
not copied, so that gate no longer exists (see §Amendment). POC #4's
gate map survives as the extraction recipe, not as in-tree cfg.
## Decision
@@ -37,15 +36,15 @@ are first-party code.
placement per [overview.md](../overview.md): protocol, wire,
producer, consumer, vendor modules at the core; backend modules
feature-gated, never imported by the protocol layer.
- **`net` (default-on)**: the vendored subset's kernel-net gate
(POC #4's shape, carried into the vendor). Turning it off yields
the wasm-clean functional protocol subset (a full RFC 1928
conversation passes — POC #4's verification).
- **The default build has no `net` feature**: the extracted vendor
contains zero kernel-net code, so the wasm-clean subset is the
whole crate — there is nothing to gate (ADR-011 §Amendment).
- **`local` (default-off)**: the crate's first-party local backends
— the TCP dial, the UDP egress socket, the BIND listener, and the
front doors (the consumer's ssh `-D` local exposure; the
producer's local backend). Implies `net`. Non-wasm by design
(enabling it on wasm is a build error, the alktty posture).
producer's local backend). Brings `tokio/net` + `socket2` into the
graph. Non-wasm by design (enabling it on wasm is a build error,
the alktty posture).
- **`composed` (default-off)**: the composed providers over
alktunnels 0.2.0 (direct-op egress, bind-first listen accept) —
the only feature that adds the alktunnels dependency. The
@@ -53,8 +52,32 @@ are first-party code.
module does, injected at the assembly layer (ADR-004/ADR-008).
- **tokio stays `default-features = false`** with the wasm-clean
subset (`rt`, `sync`, `io-util`, `macros`, `time`) — **never
`features = ["full"]`** (AGENTS.md convention 4); `net` and
`process`-shaped additions ride the features above.
`features = ["full"]`** (AGENTS.md convention 4); `net`-shaped
additions ride `local` alone.
## Amendment (2026-09-21, pre-decomposition — the vendor's `net` gate dissolves)
The original decision carried a third feature — `net`
(default-on), the vendored subset's kernel-net gate, with `local`
implying it. ADR-011's extraction amendment dissolves it:
- The vendor is an extraction of the `T`-generic inventory only; the
kernel-net region (`run_tcp_proxy`, `run_udp_proxy*`, the legacy
`Socks5Server`/`Incoming` API, `Socks5Datagram`'s kernel path, the
DNS-resolve helper, the net-only tests), the `socks4` module, and
the legacy `Config`/`Authentication` layer are not copied into the
tree — the extraction is wasm-clean by construction, with no
in-tree cfg gate.
- Features collapse to two: **`local`** (first-party socket
providers + front doors, ADR-008; brings `tokio/net` + `socket2`
into the graph itself) and **`composed`** (the alktunnels
providers). `local` no longer implies a separate `net` feature —
it is the sole gate for socket/platform I/O.
- POC #4's gate map survives as the *extraction recipe* (its
ungated inventory is what to copy), not as in-tree cfg.
- The wasm verification is a hard gate with no feature toggles:
`cargo check --target wasm32-unknown-unknown` and native
`--no-default-features` both compile the same complete crate.
## Consequences
@@ -65,17 +88,16 @@ are first-party code.
`cargo test --all-features`, and both wasm checks
(`cargo check/clippy --target wasm32-unknown-unknown`) — the
structural guard for the invariant.
- The vendor's carried changes (BIND, the `run_udp_proxy_custom`
bind seam, `new_reply` publication) live inside the `net`-gated
region where they touch kernel sockets and in the ungated
`T`-generic region where they don't (the BIND typestate driving
is `T`-generic; only the local-socket accept variant is
`net`-gated).
- The vendor's carried changes (BIND, `new_reply` publication) live
in the extracted region where they touch codecs/typestate; nothing
vendored is gated — the only feature-gated socket code is the
crate's own first-party `local` module.
## References
- OQ-SK-04 (resolved: inline vendor — see ADR-011), OQ-SK-01/03/08
(the providers the features gate)
- POC #4 `poc-connect-wasm-findings.md` (the measured gate),
- POC #4 `poc-connect-wasm-findings.md` (the measured gate — now the
extraction recipe),
alktty's `local` feature (the posture precedent), alktunnels
ADR-004 + overview.md (the same three-way split)
ADR-004 + overview.md (the same feature split)
@@ -3,7 +3,9 @@
## Status
Accepted (2026-09-20, Phase 1; OQ-SK-04's resolution — the ownership
premise corrected the same day)
premise corrected the same day). Amended 2026-09-21 (pre-decomposition):
the vendor scope is pinned as **extraction** — the kernel-net handlers
are not copied; see §Amendment.
## Context
@@ -35,18 +37,21 @@ 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.
shape POC #4 measured as minimal. (Need 1 later dissolves —
§Amendment; needs 3's handler is not extracted with it.)
## Decision
- **Vendor the `T`-generic subset into this crate** (the codecs, the
typestate machinery, the client `use_stream` path — the parts
already generic over `T: AsyncRead + AsyncWrite + Unpin`), gated
behind the default-on `net` feature (ADR-009; POC #4's gate
shape).
- **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.
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).
@@ -63,12 +68,84 @@ shape POC #4 measured as minimal.
- 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 vendored subset (~4k LoC upstream;
the vendored subset is the smaller `T`-generic region + codecs).
The vendor's diff vs upstream is deliberate and bounded: the four
carried changes + the license/provenance notes.
- 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 vendored code must keep passing.
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
@@ -39,7 +39,9 @@ against a per-identity resource table).
have no resolver at all, and the composed variant's targets ride
the alktunnels direct op (producer-side resolution there, per
alktunnels ADR-007's own posture). fast-socks5's `dns_resolve`
config stays off — it is a kernel-net concern, gated with `net`.
config (and its `resolve_dns` step) is not part of the extracted
vendor region at all — the crate's producer never resolves
(ADR-006/ADR-012), so the extraction drops both.
- **The virtual-subnet translation layer is assembly-layer policy,
not a crate feature.** The crate ships no translation-table
helper in v1: the dial callback is the seam, and a deployment
+8 -6
View File
@@ -1,7 +1,8 @@
---
status: draft
last_updated: 2026-09-20 (Phase 1 initial draft — the OQ-SK ledger
OQ-SK-01..09 promoted; new questions continue as OQ-SK2-06..)
last_updated: 2026-09-21 (OQ-SK-04's resolution updated to the
extraction posture; the OQ-SK ledger OQ-SK-01..09 promoted 2026-09-20;
new questions continue as OQ-SK2-06..)
---
# alksocks — Open Questions
@@ -84,10 +85,11 @@ sequence starts at the numbers in use. Format per
- **Origin**: [phase-0.md](../research/phase-0.md) (OQ-SK-04)
- **Status**: resolved — [ADR-011](decisions/011-inline-vendor.md)
- **Priority**: high
- **Resolution**: 2026-09-20 (Phase 0's final resolution, promoted).
The inline vendor: the `T`-generic subset in-tree behind the
default-on `net` feature; the four carried changes (net gate,
BIND, the udp-bind seam, `new_reply` publication); v1.0.0 noted;
- **Resolution**: 2026-09-20 (Phase 0's final resolution, promoted;
amended 2026-09-21 by ADR-011 §Amendment). The inline vendor as
**extraction**: the `T`-generic subset in-tree with zero kernel-net
code (wasm-clean by construction, no `net` feature); two carried
changes (BIND, `new_reply` publication); v1.0.0 noted;
differential testing against `/workspace/fast-socks5`.
- **Cross-references**: ADR-002, ADR-009, ADR-011
+17 -18
View File
@@ -1,6 +1,7 @@
---
status: draft
last_updated: 2026-09-20 (Phase 1 initial draft — the OQ-SK ledger promoted
last_updated: 2026-09-21 (the vendor extraction amendment — no `net`
feature; Phase 1 initial draft 2026-09-20 — the OQ-SK ledger promoted
into ADRs 001-012 and open-questions.md)
---
@@ -126,12 +127,9 @@ alksocks (default — wasm-clean)
├── serde/serde_json (params schema), thiserror (error surface), tracing
└── (no backend deps — sockets live in feature-gated modules)
alksocks (net feature, default-on) — non-wasm
└── adds: tokio/net, socket2, tokio-stream (the vendored subset's
kernel-net gate; ADR-009)
alksocks (local feature) — non-wasm by design
└── adds: the first-party local backends + front doors (implies net)
└── adds: tokio/net + socket2 (the feature's own socket providers) and
the first-party local backends + front doors
alksocks (composed feature)
└── adds: alktunnels 0.2.0 (the direct op egress; the bind-first
@@ -141,18 +139,19 @@ alksocks (composed feature)
```
Same posture as alktty/alktunnels: the default crate is
protocol-only and wasm-clean; the vendored fast-socks5 subset rides
the `net` gate (ADR-011); substrate I/O is feature-gated
(ADR-009). `fast-socks5` is NOT a Cargo dependency — it is vendored
in-tree (ADR-011).
protocol-only and wasm-clean — the vendor is an *extraction* of
fast-socks5's `T`-generic subset, containing zero kernel-net code,
so there is no `net` feature at all (ADR-011 §Amendment);
substrate I/O rides `local` alone (ADR-009 §Amendment).
`fast-socks5` is NOT a Cargo dependency — it is extracted in-tree
(ADR-011).
## Feature Gates
| Feature | Contents | Wasm |
|---------|----------|------|
| *(default)* | the vendored `T`-generic subset (codecs, typestate), wire codec, open-op spec, the three seams' traits/types, producer + consumer session types — protocol only | yes |
| `net` *(default-on)* | the vendored subset's kernel-net pieces (`tokio/net`, `socket2`): the local-socket default handlers, DNS resolve helper, `Socks5Datagram` kernel socket path | no (wasm builds use `default-features = false`) |
| `local` | the crate's first-party local backends: TCP dial, UDP egress socket, BIND `TcpListener` accept, the front doors (consumer + producer local shapes) — implies `net` | no (build error on wasm, the alktty posture) |
| *(default)* | the extracted `T`-generic subset (codecs, typestate), wire codec, open-op spec, the three seams' traits/types, producer + consumer session types — protocol only; zero kernel-net code, wasm-clean by construction | yes |
| `local` | the crate's first-party local backends: TCP dial, UDP egress socket, BIND `TcpListener` accept, the front doors (consumer + producer local shapes); brings `tokio/net` + `socket2` into the graph | no (build error on wasm, the alktty posture) |
| `composed` | the composed providers over alktunnels 0.2.0: direct-op egress (lazy destination→tunnel table), bind-first listen accept — adds the alktunnels dep | yes (alktunnels' default crate is wasm-clean; the composed providers use only its ungated channels surface — verify with the wasm check at implementation) |
## Module Map
@@ -160,10 +159,10 @@ in-tree (ADR-011).
```
src/
lib.rs — re-exports; crate docs
vendor/ — the vendored fast-socks5 T-generic subset (ADR-011;
vendor/ — the extracted fast-socks5 T-generic subset (ADR-011;
vendor point v1.0.0 noted in-tree): codecs, typestate,
client use_stream path, carried changes (BIND reply
writer, new_reply publication, the udp-bind seam)
client use_stream path; two carried changes (BIND reply
writer, new_reply publication); no kernel-net code
params.rs — the trivial params schema + SOCKS5_OPEN_SCOPE/
SOCKS5_BIND_SCOPE constants (ADR-001/006)
wire.rs — the ASSOCIATE datagram frame codec, re-implemented
@@ -207,9 +206,9 @@ All design decisions are documented as ADRs in [decisions/](decisions/).
| [006](decisions/006-access-control.md) | Access-control posture | `socks5:open` gate + `socks5:bind` command gate; target policy = the dialer |
| [007](decisions/007-bind-in-base.md) | BIND in the base crate | phase model extended (reply#1 → quiet wait → accept → reply#2 → pass-through); additive |
| [008](decisions/008-provider-defaults.md) | Provider defaults | local-socket + composed (direct op) providers, both feature-gated; no implicit dialer |
| [009](decisions/009-feature-gates.md) | Feature gates | `net` (default-on, the vendor's gate), `local`, `composed`; default crate wasm-clean |
| [009](decisions/009-feature-gates.md) | Feature gates | `local` + `composed`; no `net` feature (the vendor is wasm-clean by construction — ADR-011 §Amendment); default crate wasm-clean |
| [010](decisions/010-channel-capacity.md) | Channel capacity | no wire change; assembly-layer config + the documented proxy-workload recommendation |
| [011](decisions/011-inline-vendor.md) | The inline vendor | the `T`-generic subset vendored in-tree; the four carried changes; v1.0.0 noted |
| [011](decisions/011-inline-vendor.md) | The inline vendor | the `T`-generic subset extracted in-tree; two carried changes (BIND, `new_reply`); v1.0.0 noted |
| [012](decisions/012-target-policy.md) | Target policy | dialer-refuses; domain targets pass through unresolved; virtual subnets = assembly policy |
## Open Questions
+103 -41
View File
@@ -1,6 +1,8 @@
---
status: draft
last_updated: 2026-09-20 (Phase 1 initial draft)
last_updated: 2026-09-21 (the extraction amendment — ADR-011 §Amendment:
extraction not wholesale copy, the net gate dissolves, two carried
changes; Phase 1 initial draft 2026-09-20)
---
# alksocks — The Inline Vendor (fast-socks5 subset)
@@ -12,71 +14,131 @@ vendor's scope, the carried changes, and the maintenance posture.
The protocol base decision is ADR-002; the feature gating is
ADR-009.
## What is Vendored
## The Mechanism: Extraction, Not Wholesale Copy (ADR-011 §Amendment)
The vendor is an **extraction**: copy *only* the `T`-generic
inventory from fast-socks5 v1.0.0 into `src/vendor/`; the kernel-net
handlers are **not copied** — they stay in `/workspace/fast-socks5`
(the read-only reference checkout). Concretely: POC #4's cfg map
(its five-file inventory of what is kernel-net-gated) is the base of
the extraction recipe — copy what POC #4 left *ungated*, minus the
two recorded exclusions (the `socks4` module and the legacy
`Config`/`Authentication` layer — see the cut table). The extraction
contains zero kernel-net code, so it is wasm-clean by construction;
there is no in-tree `net`-shaped feature gate (that part of POC #4's
change dissolves — ADR-011 §Amendment).
## What is Extracted
From fast-socks5 **v1.0.0** (MIT, third-party upstream —
`/workspace/fast-socks5` is the read-only reference checkout; the
vendor point is noted in the vendored code):
vendor point is noted in the extracted code):
| Piece | Why it rides | Gating |
|-------|--------------|--------|
| The server typestate (`Socks5ServerProtocol<T, states::*>`, negotiate/auth/command-read/reply) | the producer's conversation driver (ADR-002) | `T`-generic — ungated |
| The auth traits (`AuthMethod<T>`/`AuthMethodSuccessState<T>`) | the negotiation machinery (the crate uses NoAuth in-band; the traits stay for fidelity) | ungated |
| `transfer` (the generic two-pump) | the interception-point shape (the crate's data plane is `pump_bidi`, but `transfer` is the typestate's own seam) | ungated |
| The codecs: `new_udp_header`/`parse_udp_request`, `TargetAddr`/`to_be_bytes`/`read_address`, `new_reply` (published — carried change #4), `ReplyError`/`SocksError`/`SocksServerError` | the RFC wire vocabulary everywhere (front doors, associate frames, reply#2 reader) | ungated |
| The client types: `Socks5Stream<S>::use_stream` + the `AsyncRead/AsyncWrite` impls | the consumer half's stream session (ADR-002; POC #1's fit) — the first-party associate session in `consumer.rs` was informed by this shape (upstream's `Socks5Datagram` kernel path is the net-gated row) | ungated |
| Kernel-net pieces (the local-socket default handlers, `Socks5Datagram`'s kernel socket path, the DNS resolve helper, the legacy `Socks5Server`/`Incoming`) | upstream surface kept for fidelity where `net` is on | **`net`-gated** |
| Piece | Why it rides | Notes |
|-------|--------------|-------|
| The server typestate (`Socks5ServerProtocol<T, states::*>`, negotiate/auth/command-read/reply, `accept_no_auth`/`finish_auth`/`accept_password_auth`) | the producer's conversation driver (ADR-002) | `T`-generic |
| The auth traits (`AuthMethod<T>`/`AuthMethodSuccessState<T>`) | the negotiation machinery (the crate uses `accept_no_auth` in-band; the traits stay for fidelity) | `T`-generic |
| `transfer` + `wait_on_tcp` | the typestate's own two-pump seam (the crate's data plane is `pump_bidi`; `transfer` is the interception-point shape) | `T`-generic |
| The codecs: `new_udp_header`/`parse_udp_request`, `TargetAddr`/`to_be_bytes`/`read_address`, `ReplyError`/`SocksError`/`SocksServerError` | the RFC wire vocabulary everywhere (front doors, associate frames, reply#2 reader) | pure; `new_reply` published in-tree (carried change) |
| The client types: `Socks5Stream<S>::use_stream` + the `AsyncRead/AsyncWrite` impls | the consumer half's stream session (ADR-002; POC #1's fit) — the first-party associate session in `consumer.rs` was informed by this shape | `T`-generic |
| The non-net test modules (where extractable as unit tests) | RFC edge-case coverage rides in-tree | adjusted to the crate's test layout |
Not vendored: the deprecated `Socks5Server`/`Socks5Socket`/`Incoming`
bind-based API as a *default path* (it exists upstream and stays
behind `net`; the crate never routes through it). The vendored subset
is functionally complete without `net` (POC #4: a full RFC 1928
CONNECT conversation passes `--no-default-features`).
## What is NOT Extracted (the cut — POC #4's gated inventory + two recipe additions)
## The Four Carried Changes (ADR-011)
These stay in `/workspace/fast-socks5` (the differential-testing
reference); the cut table below records the boundary, so a future
re-vendor knows the cut. Rows 1–5 are POC #4's five-file cfg map
(what POC #4 gated is what is not extracted); the socks4 row and the
legacy `Config`/`Authentication` layer are this recipe's two
additions — the socks4 module is out of RFC 1928 scope, and the
legacy auth layer was left ungated by POC #4 (it compiles without
net) but no extraction consumer reaches it. None of it is copied:
The vendor's diff vs upstream v1.0.0 is deliberate and bounded:
| Upstream file | Region NOT in `src/vendor/` |
|---|---|
| `src/server.rs` | `run_tcp_proxy`, `run_udp_proxy`/`run_udp_proxy_custom`, `udp_bind_random_port`, the legacy `Socks5Server`/`Incoming`/`Socks5Socket` (struct + impls), `DnsResolveHelper` trait + impl, `handle_udp_*`/`transfer_udp`, the net-only test module — plus the legacy `Config<A>` + `Authentication` trait + impls (`SimpleUserPassword`/`DenyAuthentication`/`AcceptAuthentication`, `authenticate_callback`), which POC #4 left ungated but which only this cut region consumes (rows 1–5 are POC #4's gate map; the `Config`/`Authentication` layer and the socks4 row are this recipe's two additions) |
| `src/client.rs` | `Socks5Datagram` (struct + impl), the `Socks5Stream<TcpStream>` convenience impl (`connect`/`connect_with_password`/`connect_raw`) |
| `src/util/stream.rs` | `tcp_connect*` fns (the `read_exact!`/`ready!` macros + `ConnectError` stay) |
| `src/util/target_addr.rs` | the `resolve_dns` method (the codec stays) |
| `src/lib.rs` | the net-heavy test module (consts/errors/UDP-header codec stay) |
| `src/socks4/` | the whole module (upstream's separate default-off `socks4` cargo feature; out of RFC 1928 scope) |
1. **The `net` feature gate** — ~120 `#[cfg]` insertions across 5
files, exactly POC #4's measured shape
(`poc-connect-wasm-findings.md` §The gating change: server.rs
~8 insertions; client.rs 87 diff lines (whitespace-inflated);
util/stream.rs, util/target_addr.rs, lib.rs ≤3; Cargo.toml's
`net = ["tokio/net", "dep:socket2", "dep:tokio-stream"]`).
2. **BIND** — absent upstream (every handler refuses
**Why none of it is needed in-tree** (ADR-011 §Amendment): no crate
consumer routes through the kernel-net region — the producer drives
the typestate directly (the ASSOCIATE path is the POC #2
typestate-driving shape; `run_udp_proxy_custom` is unusable on
channels regardless), the consumer sessions use `use_stream` plus
the first-party associate session, the doors are pure codecs +
first-party bridges, and the local backends are first-party tokio
code (ADR-008). The "producer-side local backend" is a bridge *onto
the channels-served service* (POC #1's door shape), not a standalone
vanilla server. If a future need surfaces (e.g. a standalone
vanilla-server example), the fix is re-vendor that piece then, as an
additive decision.
## The Carried Changes (ADR-011, re-read under extraction)
The extraction's diff vs upstream v1.0.0 is deliberate and bounded —
**two changes touch extracted code**; the other two of the original
four dissolve with the kernel-net region:
1. **BIND** — absent upstream (every handler refuses
`Socks5Command::TCPBind`). The crate drives the typestate itself
for the BIND shape (ADR-007); the vendor carries the shape
in-tree (`reply_success`'s `sock_addr` as the reply#1 carrier;
the reply#2 writer using the published `new_reply`).
3. **The `run_udp_proxy_custom` bind seam** — the seam is narrower
than it looks (`udp_bind_random_port` unconditionally; the reply
port derives from that kernel socket). In-tree fix: make the bind
optional / accept a caller-supplied socket. (The channels path
drives the typestate directly regardless; the fix serves the
`net`-gated path's embedders.)
4. **`new_reply` publication** — the reply encoder becomes public
2. **`new_reply` publication** — the reply encoder becomes public
(a `reply(code, addr)`-shaped constructor) so the BIND reply#2
writer doesn't re-encode the 10/22-byte reply form.
Dissolved: the `net` feature gate (no net code in-tree) and the
`run_udp_proxy_custom` bind seam (the handler isn't extracted).
Plus the provenance notes: the vendor point (v1.0.0) and the license
headers (MIT; preserve per-file headers or a bundled notice —
ADR-011's license obligation).
Plus the **extraction-time adjustments** (not carried changes —
mechanical edits to the copied code, ADR-011 §Amendment):
- The `socks4` module is out entirely (upstream's separate
default-off `socks4` cargo feature, which POC #4 never compiled;
out of RFC 1928 scope). `SocksError`'s `ReplySocks4Error` variant
(also `#[cfg(feature = "socks4")]` upstream) goes with it.
- The legacy `Config<A>`/`Authentication` auth layer is out
(ungated upstream, but consumed only by the cut legacy/net region
— `Socks5Server`/`Socks5Socket`/the `run_*` handlers; the
typestate's own `accept_no_auth`/`accept_password_auth` are the
crate's in-band auth surface). With it go `async-trait` (its four
upstream uses are all on this layer), the example auth impls
(`SimpleUserPassword`/`DenyAuthentication`/`AcceptAuthentication`),
and `SocksError`'s `AuthenticationRejected`-consuming legacy
plumbing that only they exercise.
- Upstream `log` calls in the extracted region are dropped or
re-pointed at the crate's `tracing`.
- Upstream `anyhow` error wrapping (`Context` / `Other(#[from]
anyhow::Error)`) is dropped — the crate's error surface is
`thiserror`-typed; the vendored `SocksError`/`SocksServerError`
stay the wire-level vocabulary.
## Maintenance Posture
- **The vendor is first-class code, not a patch stack**: the carried
changes live in-tree and co-evolve with the wrapper; the vendored
changes live in-tree and co-evolve with the wrapper; the extracted
subset's RFC fidelity is the crate's own test suite's job.
- **Differential testing**: `/workspace/fast-socks5` stays the
reference for RFC edge cases (reply-code mapping, domain
addressing, fragmentation). The crate's test suite covers the
edge cases the vendored code must keep passing; when in doubt,
diff behavior against the reference checkout.
edge cases the extracted code must keep passing; when in doubt,
diff behavior against the reference checkout. The kernel-net
region lives there too — the reference checkout is the only place
the full upstream surface exists.
- **Upstream posture**: fast-socks5 is third-party; nothing waits on
it. Offering the `net` gate upstream as a courtesy PR is optional
(POC #4: small enough to offer as-is); a future upstream release
may be re-based against deliberately, never absorbed silently
(the carried changes would need re-verification).
it. Offering a net-gate PR upstream is optional courtesy (POC #4:
small enough to offer as-is) and unrelated to this crate's build —
the extraction doesn't need it. A future upstream release may be
re-based against deliberately, never absorbed silently (the
extraction cut + carried changes would need re-verification).
- **No second codec, ever**: the vendored codecs are the only RFC
wire vocabulary in the crate (ADR-011's rationale; the alknet
anti-prior-art record is in phase-0 §Anti-prior-art).
@@ -86,8 +148,8 @@ ADR-011's license obligation).
| ADR | Decision | Summary |
|-----|----------|---------|
| [002](decisions/002-protocol-base.md) | Protocol base | the explicit typestate API only |
| [009](decisions/009-feature-gates.md) | Feature gates | `net` default-on; wasm builds use `default-features = false` |
| [011](decisions/011-inline-vendor.md) | The inline vendor | the subset in-tree; the four carried changes; v1.0.0 noted |
| [009](decisions/009-feature-gates.md) | Feature gates | `local`, `composed`; the default crate is wasm-clean with no `net` feature (ADR-011 §Amendment) |
| [011](decisions/011-inline-vendor.md) | The inline vendor | the extraction in-tree; two carried changes (BIND, `new_reply`); v1.0.0 noted |
## Open Questions
+35 -28
View File
@@ -168,9 +168,11 @@ not start from zero. It inherits:
layer (alktty `TtyBackend` / alktunnels no-trait precedent).
- **The wasm-clean default crate** — protocol-only code should compile
to `wasm32-unknown-unknown` (alktty/alktunnels precedent). The
vendored fast-socks5 subset is wasm-clean: the inline vendor gates
kernel-net behind a default-on `net`-shaped feature (OQ-SK-04
resolved; POC #4 verified the subset is functionally complete).
extracted fast-socks5 subset is wasm-clean by construction: the
inline vendor is an extraction (the `T`-generic subset only; the
kernel-net handlers are not copied — OQ-SK-04 resolved; ADR-011
§Amendment, 2026-09-21; POC #4 verified the subset is functionally
complete).
- **The upstream substrate this crate composes against** — alkcall
0.8.0 (reply projection; `ChannelRelay`/`HubLegTemplate` per ADR-051;
flavor-form open-op ids in discovery per ADR-047 amendment 3) and
@@ -219,13 +221,15 @@ load-bearing conclusions this crate inherits:
### fast-socks5 — the vendored implementation
`/workspace/fast-socks5` (v1.0.0, MIT — third-party upstream, not ours;
read-only reference checkout). **The posture is the inline vendor
(OQ-SK-04, resolved 2026-09-20): vendor the `T`-generic subset into this
crate behind a default-on `net`-shaped feature, carrying the wrapper's
needs in-tree.** `/workspace/fast-socks5` remains the differential-testing
reference for RFC edge cases (reply-code mapping, domain addressing,
fragmentation); note the vendor point (v1.0.0) in the vendored code. Key
surface points, verified:
read-only reference checkout). **The posture is the inline vendor as an
extraction (OQ-SK-04, resolved 2026-09-20; extraction pinned
2026-09-21, ADR-011 §Amendment): extract the `T`-generic subset into
this crate — the kernel-net handlers and the `socks4` module are not
copied — carrying the wrapper's needs in-tree.**
`/workspace/fast-socks5` remains the differential-testing reference
for RFC edge cases (reply-code mapping, domain addressing,
fragmentation); note the vendor point (v1.0.0) in the vendored code.
Key surface points, verified:
- **Explicit typestate server API** (`src/server.rs`):
`Socks5ServerProtocol<T, states::Opened|Authenticated|CommandRead>`,
@@ -284,15 +288,17 @@ surface points, verified:
proxy, refuse, or handle in-process. Structurally the shape of the
channels open handler (inspect params → dial or refuse) and of the
alknet ADR-090 client (hand the established stream to quinn).
- **What the vendor must add (the four carried needs):** the
kernel-net feature gate (POC #4: ~120 cfg lines, wasm-clean subset
functionally complete); **BIND** (absent upstream — fast-socks5
parses `Socks5Command::TCPBind` but every handler refuses it,
`lib.rs:384-386` / `server.rs:813-816`; the wrapper drives the
typestate itself); the `run_udp_proxy_custom` bind seam (above); and
the publication of `new_reply` (the reply encoder is private; the
BIND reply#2 writer otherwise hand-rolls the 10-byte reply —
`poc-bind-findings.md`).
- **What the vendor must add (the carried needs; re-read under the
2026-09-21 extraction amendment — ADR-011 §Amendment):** **BIND**
(absent upstream — fast-socks5 parses `Socks5Command::TCPBind` but
every handler refuses it, `lib.rs:384-386` / `server.rs:813-816`;
the wrapper drives the typestate itself) and the publication of
`new_reply` (the reply encoder is private; the BIND reply#2 writer
otherwise hand-rolls the 10-byte reply — `poc-bind-findings.md`).
The other two of the original four needs — the kernel-net feature
gate (POC #4: ~120 cfg lines, wasm-clean subset functionally
complete) and the `run_udp_proxy_custom` bind seam — dissolve with
the kernel-net region, which the extraction does not copy.
### iroh-socks5 — the structural sibling (evaluated, not the base)
@@ -783,7 +789,7 @@ drives the command dispatch itself — implementing BIND is wrapper-side
code, carried in the vendor (no upstream ask). POC-confirmed:
`reply_success`'s `sock_addr` parameter carries reply#1's `BND.ADDR`
verbatim; the courtesy item is publishing `new_reply` (rides the
vendor — see §Prior art's four carried needs).
vendor — see §Prior art's carried needs).
**The shape (convergent precedent: iroh-socks5's channels-native BIND —
server relays raw bytes post-reply for CONNECT/BIND, `docs/research/
@@ -1102,14 +1108,15 @@ except the two explicit, optional, feature-gated assembly shapes
protocol modules at the crate core; feature-gated backend modules
(local binds) never imported by the protocol layer; producer +
consumer + params modules mirroring POC #1/#2's structure. **The
fast-socks5 posture is the inline vendor (OQ-SK-04, resolved
2026-09-20)**: vendor the `T`-generic subset into this crate (the
codecs, typestate machinery, client `use_stream` path), gated behind
a default-on `net`-shaped feature (POC #4: ~120 cfg lines, wasm-clean
subset functionally complete) — and carry the four wrapper needs in
the same vendor: BIND (absent upstream; POC #5's typestate-driven
shape), the `run_udp_proxy_custom` bind seam, the `new_reply`
publication, and the wasm gate. `/workspace/fast-socks5` stays the
fast-socks5 posture is the inline vendor as an extraction (OQ-SK-04,
resolved 2026-09-20; extraction pinned 2026-09-21, ADR-011
§Amendment)**: extract the `T`-generic subset into this crate (the
codecs, typestate machinery, client `use_stream` path; the
kernel-net handlers and `socks4` are not copied, so there is no
`net` feature — POC #4's gate map survives as the extraction
recipe) — and carry the two extracted-region needs in the same
vendor: BIND (absent upstream; POC #5's typestate-driven shape) and
the `new_reply` publication. `/workspace/fast-socks5` stays the
differential-testing reference; note the vendor point (v1.0.0) in
the vendored code.
- **Open for Phase 1 ADRs** (inputs collected, decisions pending): the
+8 -2
View File
@@ -231,7 +231,10 @@ both asks are **superseded in shape**, not implemented as posed:
`run_udp_proxy_custom` unconditional-bind seam. (Posture note,
2026-09-20: fast-socks5 is third-party upstream and these are no
longer asks — all three move into the inline vendor, OQ-SK-04
resolved.)
resolved. 2026-09-21: under the extraction — ADR-011 §Amendment —
`new_reply` is the one that rides in-tree; the `net` gate and the
bind seam dissolve with the kernel-net region, which is not
copied.)
The POC's evidence stands as the record of what was validated against
alktunnels 0.1.0; the Phase 1 shape composes variant 2 against 0.2.0
@@ -261,7 +264,10 @@ are additive surface, discovered in priority order:
3. **(fast-socks5, courtesy)** `new_reply` is private — publish it
(or a constructor) so embedders don't re-encode the reply wire
form. Also still open from POC #4: the `net` feature gate and the
`run_udp_proxy_custom` unconditional-bind seam.
`run_udp_proxy_custom` unconditional-bind seam. (2026-09-21: under
the extraction — ADR-011 §Amendment — `new_reply` is the one that
rides in-tree; the `net` gate and the bind seam dissolve with the
kernel-net region, which is not copied.)
No asks block the POC's conclusions; 1–2 shape variant 2's *full*
fidelity, and the POC's stand-ins (carried `bind_addr`; sentinel
+4 -1
View File
@@ -181,7 +181,10 @@ were recorded as "asks" under the wrong ownership premise and move
into the vendor with it.
1. **Feature-gate kernel-net behind a default-on `net` feature**
(POC #4's change, ~120 lines of `#[cfg]`).
(POC #4's change, ~120 lines of `#[cfg]`). (2026-09-21: dissolved
under the extraction — ADR-011 §Amendment; the kernel-net region
is not copied, and POC #4's gate map survives as the extraction
recipe, not in-tree cfg.)
2. **`run_udp_proxy_custom` binds a kernel socket unconditionally**
(noted in phase-0 §Prior art, verified again here): the
customizable seam covers the relay closure and reply IP only; the