docs: OQ-SK-04 restructured as the conditional decision tree it is
The root question is 'does wasm make sense for alksocks?', not 'how do we get wasm': Case 1 (wasm wanted) -> fork-first, PR as courtesy, carry regardless; Case 2 (wasm unwanted) -> plain dependency on the router.rs native path, no fork (the alkhttp precedent, strictly better when its premise holds). Decision inputs written: (a) is there a planned sandboxed (wasm) consumer of the SOCKS5 service, (b) is the fork genuinely minimal (POC #4 checks). Fork-first is a conditional preference, not a general one — do not carry a fork for an invariant's sake. AGENTS.md conventions 4/17 aligned.
This commit is contained in:
@@ -90,11 +90,13 @@ implementation agents.
|
||||
(`rt`, `sync`, `io-util`, `macros`, `time`) — **do NOT use `features
|
||||
= ["full"]`** — and keep socket/platform I/O feature-gated. Note:
|
||||
`fast-socks5` itself uses `tokio::net` and `socket2` unconditionally;
|
||||
the resolved posture (OQ-SK-04) is fork-first: vendor the needed
|
||||
subset and feature-gate native-net pieces (the alkhttp crate shows a
|
||||
native-only crate is possible, but wasm-clean remains the preferred
|
||||
posture here). Run the wasm check whenever a non-backend module
|
||||
changes.
|
||||
the posture is conditional (OQ-SK-04): if wasm matters for this
|
||||
crate, the path is fork-first (vendor the `T`-generic subset, gate
|
||||
native-net pieces — see OQ-SK-04 Case 1); if wasm does not matter,
|
||||
ride the native `router.rs` path with a plain dependency (Case 2 —
|
||||
the alkhttp precedent). The root question — does wasm make sense
|
||||
here — is the OQ to settle; do not carry a fork for an invariant's
|
||||
sake. Run the wasm check whenever a non-backend module changes.
|
||||
|
||||
5. **Wire format is stable** — the SOCKS5 protocol itself is RFC 1928
|
||||
(fixed); this crate's wire surface is the ALPN + the channel open-op
|
||||
@@ -227,13 +229,14 @@ implementation agents.
|
||||
One ALPN per protocol. The ALPN string is wire-stable once
|
||||
published — decide via ADR before the first consumer.
|
||||
|
||||
17. **Upstream is ours — fork-first, PR as courtesy.** We own
|
||||
`fast-socks5` (`/workspace/fast-socks5`, v1.0.0, MIT) and alkcall.
|
||||
For changes this crate needs upstream, the preferred posture is:
|
||||
make the change as a minimal fork and use it — offer it upstream
|
||||
as a PR and merge upstream if they want it, but never wait on
|
||||
approval (see `docs/research/phase-0.md` OQ-SK-04's resolved
|
||||
posture). For alkcall (the actively co-developed substrate) the
|
||||
17. **Upstream is ours — fork-first where it pays, asks for alkcall.**
|
||||
We own `fast-socks5` (`/workspace/fast-socks5`, v1.0.0, MIT) and
|
||||
alkcall. For fast-socks5, the posture is conditional (OQ-SK-04):
|
||||
if wasm matters for this crate (Case 1), make the change as a
|
||||
minimal fork and use it — offer it upstream as a PR and merge
|
||||
upstream if they want it, but never wait on approval; if wasm does
|
||||
not matter (Case 2), ride the native path with a plain dependency,
|
||||
no fork. For alkcall (the actively co-developed substrate) the
|
||||
alk* precedent still applies: file asks early and land them there
|
||||
rather than working around them locally — the alktunnels precedent
|
||||
(the E-01/E-02 sweep, filed from its Phase 0 and landed in alkcall
|
||||
|
||||
+71
-62
@@ -445,68 +445,76 @@ published).
|
||||
unknown-unknown` has no `tokio::net`. The wrapper's own protocol layer
|
||||
can stay wasm-clean (the typestate API is generic over `T`), but
|
||||
depending on the crate at all may break `cargo check --target
|
||||
wasm32-unknown-unknown` at the dependency-graph level. Options:
|
||||
wasm32-unknown-unknown` at the dependency-graph level.
|
||||
|
||||
- **Option A: upstream feature-gating ask** — add a feature to
|
||||
fast-socks5 that gates `tokio::net`/`socket2` behind a default-on
|
||||
`net` feature (like alktty's `local`), leaving the codecs, typestate
|
||||
machinery, and client `use_stream` path wasm-clean. We own upstream
|
||||
(AGENTS.md convention 17); the alk* precedent is to make asks early.
|
||||
- **Option B: protocol reimplementation** — the wrapper reimplements
|
||||
the SOCKS5 state machine over `tokio::io` generics (RFC 1928 is
|
||||
small); fast-socks5 remains the native-path implementation. Duplicates
|
||||
protocol logic; against the "wrap, don't fork" vision unless A fails.
|
||||
- **Option C: wasm drops off the invariant for this crate** — accept a
|
||||
native-only default. Against the alktty/alktunnels precedent;
|
||||
document as an ADR-worthy exception if forced.
|
||||
**The decision tree (2026-09-12 clarification — two main forks, and the
|
||||
preference between them is conditional):**
|
||||
|
||||
Action: verify the breakage empirically (a minimal crate depending on
|
||||
fast-socks5, `cargo check --target wasm32-unknown-unknown`), then take
|
||||
Option A upstream if confirmed. This OQ gates the wasm verification
|
||||
command in AGENTS.md (the expected-failure note is already written
|
||||
there).
|
||||
- **Case 1: wasm matters for this crate → fork-first.** Make the
|
||||
minimal fork and use it — offer it upstream as a PR (merge if they
|
||||
want it; we carry the fork regardless; never wait on approval).
|
||||
The gating change is genuinely small (feature-gate
|
||||
`tokio::net`/`socket2` behind a default-on `net` feature, per the
|
||||
alktty `local` pattern); the long-term cost of maintaining a
|
||||
minimal fork of a well-written lib trends toward zero. Concretely
|
||||
"fork" likely means **vendoring the relevant subset into this
|
||||
crate** (the codecs, typestate machinery, client `use_stream` path —
|
||||
the parts that are already `T`-generic) and gating the native-net
|
||||
pieces behind the crate's own `local` feature, rather than
|
||||
publishing a divergent crate. fast-socks5 remains the reference
|
||||
checkout for differential testing of RFC edge cases (reply-code
|
||||
mapping, domain addressing, fragmentation) and the PR source; if
|
||||
upstream accepts the PR, the fork shrinks to a plain dependency.
|
||||
- **Case 2: wasm doesn't matter for this crate → plain dependency, no
|
||||
fork.** Ride the `router.rs` native path exactly as-is (the
|
||||
alkhttp precedent — a crate in this suite can ship native-only).
|
||||
This is strictly better *if its premise holds*: no fork burden, no
|
||||
vendoring, upstream stays upstream. The alkhttp contrast is
|
||||
instructive — alkhttp's substance (axum/hyper/reqwest) is
|
||||
socket-native, so nobody misses wasm there.
|
||||
|
||||
**Fork vs reimplement vs wrap — resolved as fork-first (2026-09-12
|
||||
discussion, supersedes the earlier upstream-ask-first weighting).** The
|
||||
wrap surface was re-checked and **no structural fork is needed** for the
|
||||
channels path: CONNECT rides the interception points as-is —
|
||||
`read_command` → own dial → `reply_success` → `transfer` (the
|
||||
`examples/router.rs` shape), with the producer's `BiStream` as `T` on
|
||||
both sides of `transfer`. UDP is ours either way: `run_udp_proxy_custom`
|
||||
takes a custom `transfer` closure for the relay half, `reply_success`
|
||||
accepts any `SocketAddr` (the sentinel reply), and
|
||||
`new_udp_header`/`parse_udp_request` are public, pure, and reusable.
|
||||
The one hardwired piece — `run_udp_proxy`'s peer relay socket
|
||||
(`udp_bind_random_port`, socket2-based, called inside the default
|
||||
handler before `run_udp_proxy_custom` even runs) — is in the *default*
|
||||
handler, not the protocol: the wrapper's custom closure never needs it.
|
||||
**The root question is therefore: does wasm make sense for alksocks?**
|
||||
Not "how do we get wasm" — that is Case 1's solved problem. Analysis
|
||||
for the decision:
|
||||
|
||||
**Decision: fork-first, "fork" = inline in this crate (vendor-and-vet).**
|
||||
Do the feature gating as a minimal, minimal-diff fork and use it —
|
||||
offering it upstream as a PR (merge if they want it; we carry the fork
|
||||
regardless). Rationale: depending on someone else's approval cadence is
|
||||
the worst part of the upstream-ask path; the long-term cost of
|
||||
maintaining a minimal fork of a well-written lib trends toward zero
|
||||
(modern tooling/AI assistance makes small-diff rebase-and-review cheap);
|
||||
and the changes needed here are genuinely small (feature-gate
|
||||
`tokio::net`/`socket2` behind a default-on `net` feature, per the
|
||||
alktty `local` pattern). Concretely this likely means **vendoring the
|
||||
relevant subset into this crate** (the codecs, typestate machinery,
|
||||
client `use_stream` path — the parts that are already `T`-generic) and
|
||||
gating the native-net pieces behind the crate's own `local` feature,
|
||||
rather than publishing a divergent crate. fast-socks5 remains the
|
||||
reference checkout for differential testing of RFC edge cases
|
||||
(reply-code mapping, domain addressing, fragmentation) and the PR
|
||||
source. If upstream accepts the feature-gating PR, the fork shrinks to
|
||||
a plain dependency; if not, we carry it — either way this crate's
|
||||
timeline is not hostage to approval. What is *not* off the table:
|
||||
dropping wasm outright is still available (Option C) as the
|
||||
alkhttp precedent shows a crate can ship native-only — but it is not
|
||||
preferred; the fork makes the wasm-clean default achievable without
|
||||
anyone's approval. (The user also flagged the alternative of simply
|
||||
not worrying about wasm for this crate and riding the `router.rs`
|
||||
native path; that remains viable if the fork's gating turns out more
|
||||
invasive than expected — an escape hatch, not the plan.)
|
||||
- **What wasm would serve:** the protocol layer (components 1+2 of the
|
||||
three-component split, §Vision) in a sandboxed adapter — the
|
||||
alktty/alktunnels posture that a wasm-compiled protocol crate is the
|
||||
protocol layer for downstream TS/Python adapters. The natural wasm
|
||||
shape is a **consumer**: speak RFC 1928 client-side over a channels
|
||||
`BiStream` (pure byte framing; the producer dials targets, so no
|
||||
local sockets are needed on the client side). A wasm **producer** is
|
||||
odd standalone (no kernel egress) but composes — its dialer can open
|
||||
alktunnels channels further downstream, which is the hub story.
|
||||
- **What wasm would not serve:** component 3 (the quinn/noq client
|
||||
wrapper) is native-only in practice — it wraps kernel UDP sockets for
|
||||
UDP ASSOCIATE; no wasm story is expected there regardless.
|
||||
- **The alkhttp contrast, honestly weighed:** for alkhttp,
|
||||
"an HTTP client accessed over a channel" was judged weird, and the
|
||||
crate ships native-only. alksocks differs structurally: its protocol
|
||||
substance *is* byte-framing over a generic `T` — the part that costs
|
||||
nothing to keep wasm-clean. But "costs nothing under the fork" is
|
||||
only an argument once Case 1 is chosen; it is not itself the use
|
||||
case. The use case is a planned sandboxed SOCKS consumer.
|
||||
|
||||
**Decision inputs (what actually settles Case 1 vs Case 2):**
|
||||
|
||||
1. **Is there a planned sandboxed (wasm) consumer of the SOCKS5
|
||||
service?** If the downstream TS/Python adapter story includes a
|
||||
channels-connected sandbox that wants proxied egress, wasm matters
|
||||
→ Case 1. If not → Case 2.
|
||||
2. **Is the fork genuinely minimal?** POC #4 (OQ-SK-07) verifies
|
||||
empirically how invasive the gating is. If the gating turns out
|
||||
structural (the `T`-generic surface can't be separated from
|
||||
`tokio::net` without a rewrite), the fork cost rises and Case 2
|
||||
gains weight — reimplementing the protocol to serve a wasm story
|
||||
nobody has yet defeats the purpose.
|
||||
|
||||
Both inputs point the same way when aligned: fork-first in Case 1 is
|
||||
the preferred branch *because* wasm is wanted there — not as a general
|
||||
preference for forks over upstream asks. If the adapter story never
|
||||
materializes, Case 2 (plain dep, router path) is the obvious choice,
|
||||
and no one should carry a fork for an invariant's sake.
|
||||
|
||||
### OQ-SK-05: Target policy and egress scoping
|
||||
|
||||
@@ -670,10 +678,11 @@ Candidate reading for the research specialist (to be expanded):
|
||||
- [ ] OQ-SK-03 (UDP ASSOCIATE) — the shape space written (including
|
||||
the phase-model-vs-alktty-demux analysis); resolve via research
|
||||
+ POC #2, ADR before the first consumer
|
||||
- [ ] OQ-SK-04 (fast-socks5 wasm) — verify empirically (POC #4);
|
||||
resolved posture: fork-first (vendor-and-vet, PR as courtesy) —
|
||||
carry the minimal fork regardless of upstream's decision;
|
||||
wasm-drop (alkhttp precedent) is the escape hatch, not the plan
|
||||
- [ ] OQ-SK-04 (fast-socks5 wasm) — the decision tree is written
|
||||
(Case 1: wasm wanted → fork-first; Case 2: wasm unwanted →
|
||||
plain dep, router path); the root question ("does wasm make
|
||||
sense for alksocks?") settles on the adapter-story input +
|
||||
POC #4's minimality check
|
||||
- [ ] OQ-SK-05 (target policy) — folded into the OQ-SK-01 decision;
|
||||
scope-gate convention pinned in Phase 1
|
||||
- [ ] OQ-SK-06 (noq client) — research pass (trait-shape comparison,
|
||||
|
||||
Reference in New Issue
Block a user