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
|
(`rt`, `sync`, `io-util`, `macros`, `time`) — **do NOT use `features
|
||||||
= ["full"]`** — and keep socket/platform I/O feature-gated. Note:
|
= ["full"]`** — and keep socket/platform I/O feature-gated. Note:
|
||||||
`fast-socks5` itself uses `tokio::net` and `socket2` unconditionally;
|
`fast-socks5` itself uses `tokio::net` and `socket2` unconditionally;
|
||||||
the resolved posture (OQ-SK-04) is fork-first: vendor the needed
|
the posture is conditional (OQ-SK-04): if wasm matters for this
|
||||||
subset and feature-gate native-net pieces (the alkhttp crate shows a
|
crate, the path is fork-first (vendor the `T`-generic subset, gate
|
||||||
native-only crate is possible, but wasm-clean remains the preferred
|
native-net pieces — see OQ-SK-04 Case 1); if wasm does not matter,
|
||||||
posture here). Run the wasm check whenever a non-backend module
|
ride the native `router.rs` path with a plain dependency (Case 2 —
|
||||||
changes.
|
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
|
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
|
(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
|
One ALPN per protocol. The ALPN string is wire-stable once
|
||||||
published — decide via ADR before the first consumer.
|
published — decide via ADR before the first consumer.
|
||||||
|
|
||||||
17. **Upstream is ours — fork-first, PR as courtesy.** We own
|
17. **Upstream is ours — fork-first where it pays, asks for alkcall.**
|
||||||
`fast-socks5` (`/workspace/fast-socks5`, v1.0.0, MIT) and alkcall.
|
We own `fast-socks5` (`/workspace/fast-socks5`, v1.0.0, MIT) and
|
||||||
For changes this crate needs upstream, the preferred posture is:
|
alkcall. For fast-socks5, the posture is conditional (OQ-SK-04):
|
||||||
make the change as a minimal fork and use it — offer it upstream
|
if wasm matters for this crate (Case 1), make the change as a
|
||||||
as a PR and merge upstream if they want it, but never wait on
|
minimal fork and use it — offer it upstream as a PR and merge
|
||||||
approval (see `docs/research/phase-0.md` OQ-SK-04's resolved
|
upstream if they want it, but never wait on approval; if wasm does
|
||||||
posture). For alkcall (the actively co-developed substrate) the
|
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
|
alk* precedent still applies: file asks early and land them there
|
||||||
rather than working around them locally — the alktunnels precedent
|
rather than working around them locally — the alktunnels precedent
|
||||||
(the E-01/E-02 sweep, filed from its Phase 0 and landed in alkcall
|
(the E-01/E-02 sweep, filed from its Phase 0 and landed in alkcall
|
||||||
|
|||||||
+72
-63
@@ -445,68 +445,76 @@ published).
|
|||||||
unknown-unknown` has no `tokio::net`. The wrapper's own protocol layer
|
unknown-unknown` has no `tokio::net`. The wrapper's own protocol layer
|
||||||
can stay wasm-clean (the typestate API is generic over `T`), but
|
can stay wasm-clean (the typestate API is generic over `T`), but
|
||||||
depending on the crate at all may break `cargo check --target
|
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
|
**The decision tree (2026-09-12 clarification — two main forks, and the
|
||||||
fast-socks5 that gates `tokio::net`/`socket2` behind a default-on
|
preference between them is conditional):**
|
||||||
`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.
|
|
||||||
|
|
||||||
Action: verify the breakage empirically (a minimal crate depending on
|
- **Case 1: wasm matters for this crate → fork-first.** Make the
|
||||||
fast-socks5, `cargo check --target wasm32-unknown-unknown`), then take
|
minimal fork and use it — offer it upstream as a PR (merge if they
|
||||||
Option A upstream if confirmed. This OQ gates the wasm verification
|
want it; we carry the fork regardless; never wait on approval).
|
||||||
command in AGENTS.md (the expected-failure note is already written
|
The gating change is genuinely small (feature-gate
|
||||||
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.
|
|
||||||
|
|
||||||
**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
|
`tokio::net`/`socket2` behind a default-on `net` feature, per the
|
||||||
alktty `local` pattern). Concretely this likely means **vendoring the
|
alktty `local` pattern); the long-term cost of maintaining a
|
||||||
relevant subset into this crate** (the codecs, typestate machinery,
|
minimal fork of a well-written lib trends toward zero. Concretely
|
||||||
client `use_stream` path — the parts that are already `T`-generic) and
|
"fork" likely means **vendoring the relevant subset into this
|
||||||
gating the native-net pieces behind the crate's own `local` feature,
|
crate** (the codecs, typestate machinery, client `use_stream` path —
|
||||||
rather than publishing a divergent crate. fast-socks5 remains the
|
the parts that are already `T`-generic) and gating the native-net
|
||||||
reference checkout for differential testing of RFC edge cases
|
pieces behind the crate's own `local` feature, rather than
|
||||||
(reply-code mapping, domain addressing, fragmentation) and the PR
|
publishing a divergent crate. fast-socks5 remains the reference
|
||||||
source. If upstream accepts the feature-gating PR, the fork shrinks to
|
checkout for differential testing of RFC edge cases (reply-code
|
||||||
a plain dependency; if not, we carry it — either way this crate's
|
mapping, domain addressing, fragmentation) and the PR source; if
|
||||||
timeline is not hostage to approval. What is *not* off the table:
|
upstream accepts the PR, the fork shrinks to a plain dependency.
|
||||||
dropping wasm outright is still available (Option C) as the
|
- **Case 2: wasm doesn't matter for this crate → plain dependency, no
|
||||||
alkhttp precedent shows a crate can ship native-only — but it is not
|
fork.** Ride the `router.rs` native path exactly as-is (the
|
||||||
preferred; the fork makes the wasm-clean default achievable without
|
alkhttp precedent — a crate in this suite can ship native-only).
|
||||||
anyone's approval. (The user also flagged the alternative of simply
|
This is strictly better *if its premise holds*: no fork burden, no
|
||||||
not worrying about wasm for this crate and riding the `router.rs`
|
vendoring, upstream stays upstream. The alkhttp contrast is
|
||||||
native path; that remains viable if the fork's gating turns out more
|
instructive — alkhttp's substance (axum/hyper/reqwest) is
|
||||||
invasive than expected — an escape hatch, not the plan.)
|
socket-native, so nobody misses wasm there.
|
||||||
|
|
||||||
|
**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:
|
||||||
|
|
||||||
|
- **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
|
### 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
|
- [ ] OQ-SK-03 (UDP ASSOCIATE) — the shape space written (including
|
||||||
the phase-model-vs-alktty-demux analysis); resolve via research
|
the phase-model-vs-alktty-demux analysis); resolve via research
|
||||||
+ POC #2, ADR before the first consumer
|
+ POC #2, ADR before the first consumer
|
||||||
- [ ] OQ-SK-04 (fast-socks5 wasm) — verify empirically (POC #4);
|
- [ ] OQ-SK-04 (fast-socks5 wasm) — the decision tree is written
|
||||||
resolved posture: fork-first (vendor-and-vet, PR as courtesy) —
|
(Case 1: wasm wanted → fork-first; Case 2: wasm unwanted →
|
||||||
carry the minimal fork regardless of upstream's decision;
|
plain dep, router path); the root question ("does wasm make
|
||||||
wasm-drop (alkhttp precedent) is the escape hatch, not the plan
|
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;
|
- [ ] OQ-SK-05 (target policy) — folded into the OQ-SK-01 decision;
|
||||||
scope-gate convention pinned in Phase 1
|
scope-gate convention pinned in Phase 1
|
||||||
- [ ] OQ-SK-06 (noq client) — research pass (trait-shape comparison,
|
- [ ] OQ-SK-06 (noq client) — research pass (trait-shape comparison,
|
||||||
|
|||||||
Reference in New Issue
Block a user