From f3c371310c72bd3dc78386435e6bad9c4ff7e7c6 Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Fri, 25 Sep 2026 11:07:48 +0000 Subject: [PATCH] =?UTF-8?q?tasks:=20the=20batch-2=20decomposition=20?= =?UTF-8?q?=E2=80=94=20the=20remaining=20assembly-layer=20tasks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assembly-integration-suite.md | 93 +++++++++++++ tasks/implementation/composed-accept.md | 122 ++++++++++++++++ tasks/implementation/composed-egress.md | 123 +++++++++++++++++ tasks/implementation/door-tcp.md | 130 ++++++++++++++++++ tasks/implementation/door-udp.md | 91 ++++++++++++ .../implementation/local-backend-producer.md | 93 +++++++++++++ tasks/implementation/review-assembly-layer.md | 95 +++++++++++++ 7 files changed, 747 insertions(+) create mode 100644 tasks/implementation/assembly-integration-suite.md create mode 100644 tasks/implementation/composed-accept.md create mode 100644 tasks/implementation/composed-egress.md create mode 100644 tasks/implementation/door-tcp.md create mode 100644 tasks/implementation/door-udp.md create mode 100644 tasks/implementation/local-backend-producer.md create mode 100644 tasks/implementation/review-assembly-layer.md diff --git a/tasks/implementation/assembly-integration-suite.md b/tasks/implementation/assembly-integration-suite.md new file mode 100644 index 0000000..239c9ab --- /dev/null +++ b/tasks/implementation/assembly-integration-suite.md @@ -0,0 +1,93 @@ +--- +id: implementation/assembly-integration-suite +name: Assembly — the cross-cutting integration suite + docs sync +status: pending +depends_on: [implementation/door-udp, implementation/local-backend-producer, implementation/composed-accept] +scope: moderate +risk: medium +impact: phase +level: implementation +--- + +## Description + +The batch-2 closing task (the core-integration-suite's assembly-layer +analogue): the cross-cutting suite that pins the whole assembly +story, plus the docs sync that records what landed. The per-component +tasks own their verticals; this task owns the compositions and the +spec-vs-implementation reconciliation. + +Deliverables: + +1. **Cross-cutting integration tests** (`tests/assembly-integration.rs`): + - **The full `-D` story end to end**: a vanilla tun2proxy-shaped + client → the cred-bearing TCP door → the channels-native + session → the producer with `local` providers → real echo + targets — CONNECT + UDP (the RFC-header'd datagram through the + UDP door) + BIND (the vanilla BIND conversation through the + door) all through one door process against one producer + registration (the one-registration-three-commands regression, + now through the doors). + - **The composed story end to end**: the producer with + `composed::direct_egress` + `composed::bindfirst_accept` — + CONNECT through the direct op, ASSOCIATE through the lazy + table, BIND through the bind-first listener (the ADR-008 + composed variant set, all three commands). + - **Mixed assembly**: one producer registration serving + channels-native sessions AND door clients AND composed seams + concurrently (the producer/consumer-roles-not-sides posture — + both shapes on one connection). + - **Teardown matrix, assembly edition**: door-client disconnect + tears the association/channel down (no leaks — the channel + count witnesses); the idle bounds fire; fresh sessions succeed + immediately after each teardown (no connection poisoning). + - **The wasm gates re-verified**: default + `--features composed` + wasm checks green (the `local` feature is non-wasm by design — + verify it *fails* to build on wasm, the alktty posture, and + record the observed error in Notes). +2. **Docs sync** (the architecture-sync check): + - `docs/architecture/README.md` Current State + the doc statuses + reflect the landed assembly layer (the "no `local`/`composed` + code exists yet" lines updated). + - `lib.rs` crate docs updated (the "not yet present" paragraph — + the doors/providers are landed now). + - Any spec drift the implementation surfaced reconciled in the + same change — never silently diverged (the batch-1 discipline). +3. **Full verification sweep** (the batch gate's input): + `cargo test`, `cargo test --all-features`, clippy + `--all-targets --all-features -- -D warnings`, `cargo fmt + --check`, both wasm checks (default + composed), and `cargo doc + --no-deps` (docs changed). + +## Acceptance Criteria + +- [ ] The full `-D` story (vanilla client → door → session → + producer → real target) pinned end to end for all three + commands +- [ ] The composed variant set pinned end to end (all three commands) +- [ ] Mixed assembly (doors + channels-native + composed) concurrent + on one producer — pinned +- [ ] The assembly teardown matrix pinned (door disconnects, idle + bounds, fresh-session-after-teardown) +- [ ] All four verification targets green (default, all-features, + wasm default, wasm composed) +- [ ] Docs synced (README Current State, lib.rs crate docs, any + drift reconciled) + +## References + +- docs/architecture/overview.md (the feature gates + module map — + the landed-shape check), consumer.md §The Front Doors, producer.md + §Producer-Side Local Backend, §Wiring +- docs/architecture/decisions/005/007/008/009/014 +- The batch-1 precedent: `tests/core-integration.rs` (the suite + shape), `tasks/implementation/core-integration-suite.md` (the + task shape) + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/implementation/composed-accept.md b/tasks/implementation/composed-accept.md new file mode 100644 index 0000000..ac1a9aa --- /dev/null +++ b/tasks/implementation/composed-accept.md @@ -0,0 +1,122 @@ +--- +id: implementation/composed-accept +name: Composed — the bind-first accept (the listener on a listen resource) +status: pending +depends_on: [implementation/composed-egress] +scope: moderate +risk: medium +impact: component +level: implementation +--- + +## Description + +The `composed` feature's accept provider (ADR-008's composed +variants; ADR-007 variant 2): the BIND accept whose listener lives on +an alktunnels 0.2.0 bind-first listen resource +(`register_tunnel_bindfirst_openable` + `BindFirstInjections`) — +per-listener ACL for free, hub-relayable. **Vanilla-fidelity with no +stand-ins since the graduation** (ADR-007): reply#1 advertises the +real OS-chosen `bound` (the open-op reply's `bound` field, alkcall +0.8.0 reply projection); reply#2 carries the real peer (the forwarded +op's `peer` param). + +Deliverables: + +1. **`src/composed/` additions** (feature `composed`) — the composed + accept, named per producer.md §Wiring: + - `bindfirst_accept(...)` → `BindAcceptFactory`: the POC #5 + `composed_bind_accept` shape **updated to the graduation** — + the POC's pinned workaround (reply#1 first, the tunnel open + rides the accept future; the factory carries an expected + `bind_addr`) is **retired** (ADR-007 §Consequences). The landed + shape: the factory opens the bind-first listen channel + (`TunnelListener::open` — the reply projection surfaces + `bound`), reply#1 advertises the real `bound`, and the accept + future correlates the forwarded sessions (the `listen` token → + the adopted forwarded sessions the assembly correlates; the + forwarded op's `peer` param is reply#2's peer). + - **The correlation shape is the design center** (the POC's + upstream-ask ledger retired by the graduation): the factory + returns `BindAccept` whose accept future waits for the ONE + forwarded session matching the listener's token. The + correlation registry is assembly-side bookkeeping (the + alktunnels `TunnelListener` docs: "forwarded sessions arrive as + ordinary adopted sessions on the manager; the listener does not + own the receipt queue"). The provider owns: the + `register_tunnel_forwarded_openable` serving side (the opener + half — the SOCKS5 producer's connection must serve the + forwarded op), the token→waiter map, and the per-forwarded + pump. Design the wait shape against the landed alkcall 0.8.0 + adopt surface (`manager.adopt_channel`) and record it in Notes. + - **The Sync-bound wrinkle** (shared with composed-egress): the + accepted halves must be `Box` (`BindTargetHandle`'s payloads); alktunnels' `Taken + Halves` yields `Send + Unpin` only. The owner-wrapper rides + **inside the handle payloads** (egress.rs's documented posture) + — the wrapper owns the adopted channel session so dropping the + handle drops everything (the teardown-at-birth discipline, + compiler-enforced). This is the load-bearing wrapper: design it + once (with composed-egress's needs), pin it with a test. + - **Peer-address fidelity**: the forwarded op's `peer` param is + reply#2's real peer (the graduation's win); the wrapper-aware + sentinel (`0.0.0.0:0`) is the in-band default's shape, not this + variant's. + - **The establishment bound covers only the open-op round trip** + (ADR-007): the accept wait lives in the teardown-governed pump + phase — the quiet wait's `select!` drop of the accept future IS + the client-gone teardown; the listener (inside the future) + closes with it. The forwarded-op correlation must survive a + client-gone drop without leaking the listener (the accept + future owns it end-to-end — the seam contract). + - Module doc: names the ADRs (007/008), the graduation's + vanilla-fidelity win, the correlation shape, the owner-wrapper. +2. **Tests** (`tests/composed-accept.rs`): the full composed BIND + story over channels — a bind-first tunnel producer + (`register_tunnel_bindfirst_openable` + injections), the SOCKS5 + producer with `composed::bindfirst_accept(...)`, a consumer BIND + session: reply#1 carries the real `bound`, an app server dials + the far listener, reply#2 carries the real peer, the pass-through + data plane round-trips; the client-gone teardown releases the + listener (drop-witness); two concurrent BIND sessions get + distinct tokens; a forwarded-open failure is per-accept isolated. + +## Acceptance Criteria + +- [ ] `cargo test` passes; `cargo test --all-features` passes +- [ ] `cargo check --features composed --target wasm32-unknown- + unknown` clean (hard gate) +- [ ] `cargo clippy --all-targets --all-features -- -D warnings` + clean; `cargo fmt --check` clean +- [ ] The graduation's shape lands (reply#1 = the real `bound`; + reply#2 = the forwarded op's real `peer`) — no expected-addr + stand-in — pinned by test +- [ ] The owner-wrapper rides inside the handle payloads (Sync + halves; teardown structural) — pinned by test +- [ ] The client-gone teardown releases the listener (the accept + future owns it end-to-end) — pinned by drop-witness test +- [ ] The forwarded-op serving side + correlation land; per-accept + isolation — pinned by tests + +## References + +- docs/architecture/decisions/007-bind-in-base.md (variant 2 + the + graduation), 008-provider-defaults.md, 004 (the accept seam's + contracts), 006 (the command gate) +- docs/architecture/producer.md §Wiring + (`composed::bindfirst_accept`), §The Three Seams +- POC reference: `/workspace/alksocks-bind-poc/src/accept.rs` + (`composed_bind_accept` — the superseded-in-shape workaround, the + owner-wrapper's first witness) +- Landed surfaces: `src/egress.rs` (`BindAccept`/`BindTargetHandle` + — the contracts), alktunnels 0.2.0 (`register_tunnel_bindfirst_ + openable`, `BindFirstInjections`, `TunnelListener`, + `register_tunnel_forwarded_openable`, `ForwardedHandler`) + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/implementation/composed-egress.md b/tasks/implementation/composed-egress.md new file mode 100644 index 0000000..d28808f --- /dev/null +++ b/tasks/implementation/composed-egress.md @@ -0,0 +1,123 @@ +--- +id: implementation/composed-egress +name: Composed — the direct-op egress (lazy destination→tunnel table) +status: pending +depends_on: [] +scope: moderate +risk: medium +impact: component +level: implementation +--- + +## Description + +The `composed` feature's egress provider (ADR-008's composed +variants): the ASSOCIATE egress that dials each destination through +an alktunnels 0.2.0 direct-op channel (`TunnelSession::open_direct`, +`Substrate::Udp`) — per-target ACL for free (each destination rides +its own `tunnel:direct`-governed channel), the lazy +destination→tunnel table LRU-evicted at a cap (POC #2's +`ComposedEgress` shape). + +Deliverables: + +1. **`Cargo.toml`** — `composed = ["dep:alktunnels"]` (the only + feature that adds the alktunnels dependency, ADR-009); alktunnels + pinned `"0.2.0"` (the sibling pin posture — convention 11's + deliberate-bump rule applies). Verify the composed graph stays + wasm-clean (alktunnels' default crate is wasm-clean; the composed + providers use only its ungated channels surface — the wasm check + with `--features composed` is a hard gate). +2. **`src/composed/mod.rs`** (feature `composed`) — the composed + providers, named per producer.md §Wiring: + - `direct_egress(...)` → `EgressFactory`: the POC #2 + `ComposedEgress` shape ported to the landed seam types — a lazy + destination→tunnel table: each new destination opens one + alktunnels UDP-tunnel channel (`open_direct`, `Substrate::Udp`), + a relay task per tunnel drains inbound datagrams into a shared + queue, the write half stays in the table (LRU-capped; dropping + a write half EOFs the tunnel producer-side; the session's Drop + reaps the channel entry — alktunnels ADR-005). + - **The Sync-bound wrinkle (the batch-1 review's seam contract vs + alktunnels' surface)**: the landed `TargetHandle`/`BindTarget + Handle` halves are `Box`, + but alktunnels' `TakenHalves` yields `Send + Unpin` only. The + composed providers need an **owner-wrapper inside the handle + payloads** (egress.rs's documented posture: "keep-alive state a + substrate needs rides inside the halves — a provider-side owner + wrapper"). For the egress the wrapper is the table entry's + session (the egress owns it — not a handle payload); for the + composed *accept* (the sibling task) the wrapper is load-bearing + — design it here, share it. + - **The readiness contract** (load-bearing): `next_reply` parks on + the shared inbound queue (`recv().await` — Pending until a + relay task delivers); a closed queue (all relays ended) returns + `Err` — never a hot `Ok(None)` (the POC's closed-queue arm; the + stage's zero-yield defense, review 002 R-1, is the backstop, + not the design). Replies carry *inner* bytes (`addr + data` — + the reply address is the entry's recorded destination; + framing-once). + - **Per-datagram `send` is awaited inline** (the stage + interleaves sends with reply drains) — a slow egress applies + backpressure to the association, not to other channels + (wire.md's note: "the batch-2 composed-egress author inherits + this bound knowingly"). The table's open cost rides the send + path (lazy open on first datagram — the POC's measured ~0.8 ms). + - The resource/target mapping: `open_direct` takes a + `SubstrateAddr` — the destination's `TargetAddr` renders to it + (IP forms direct; domain form passes through unresolved — + producer-side resolution, the ssh `-D` semantic; ADR-012). + Constructor config carries the LRU cap (the 256-channel default + cap bounds fan-out — ADR-010's posture; the cap is config, not + wire). + - Module doc: names the ADRs (008/009), the per-target-ACL + benefit, the readiness contract, the framing-once invariant, + the backpressure bound. +3. **Tests** (`tests/composed-egress.rs`): the POC #2 shape end to + end over channels — a tunnel-producing peer (the alktunnels + registry shape, `register_tunnel_direct_openable`), the composed + egress against it, datagram round-trips through the tunnel + (multiple destinations → multiple tunnels; the LRU eviction at + cap pinned by test); the readiness contract re-pinned (park-then- + datagram, no spin); the closed-queue → association-end shape; + framing-once (no double prefix — the POC #2 bug class). + +## Acceptance Criteria + +- [ ] `cargo test` passes; `cargo test --all-features` passes +- [ ] `cargo check --features composed --target wasm32-unknown- + unknown` clean — the composed graph is wasm-clean (hard gate) +- [ ] `cargo clippy --all-targets --all-features -- -D warnings` + clean; `cargo fmt --check` clean +- [ ] `alktunnels` pinned `"0.2.0"`; the dep rides `composed` alone + (default graph unchanged — `cargo tree` spot-check) +- [ ] The lazy table + LRU eviction land per the POC shape — pinned + by test +- [ ] The readiness contract honored (real async park; closed queue + → `Err`, never a hot `Ok(None)`) — pinned by test +- [ ] Framing-once (replies are inner bytes; no double prefix) — + pinned by test +- [ ] Per-datagram send backpressure documented on the constructor + +## References + +- docs/architecture/decisions/008-provider-defaults.md (the composed + variants), 009-feature-gates.md, 003 (the handle swap), 010 (the + cap), 012 (resolution) +- docs/architecture/producer.md §Wiring (`composed::direct_egress`), + §The Three Seams +- POC reference: `/workspace/alksocks-udp-poc/src/egress.rs` + (`ComposedEgress` — the table, the relay tasks, the closed-queue + posture) +- Landed surfaces: `src/egress.rs` (the seam contracts + the + owner-wrapper posture), alktunnels 0.2.0 + (`TunnelSession::open_direct`, `TakenHalves`, + `register_tunnel_direct_openable`) + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/implementation/door-tcp.md b/tasks/implementation/door-tcp.md new file mode 100644 index 0000000..5fb6146 --- /dev/null +++ b/tasks/implementation/door-tcp.md @@ -0,0 +1,130 @@ +--- +id: implementation/door-tcp +name: Front doors — the TCP door (raw-pipe + RFC 1929 cred-bearing) and the BIND door +status: pending +depends_on: [implementation/local-providers] +scope: moderate +risk: medium +impact: component +level: implementation +--- + +## Description + +The consumer's TCP front door (feature `local`): the ssh `-D` local +exposure — a real `socks5://127.0.0.1:port` endpoint that vanilla +clients (curl, browsers, tun2proxy) point at. Two variants (ADR-005), +plus the BIND door's reply#2 fidelity. The door is the +vanilla↔ACL boundary; the RFC codec lives only here. + +Deliverables: + +1. **`src/door.rs`** (feature `local`) — the door surface per + consumer.md §The Front Doors + overview.md §Module Map: + - **The NoAuth door** (POC #1's raw-pipe shape): bind a real + `TcpListener`; per accepted connection, pipe it raw onto a + channel `BiStream` (`copy_bidirectional`, **zero protocol + translation**). The producer runs the whole RFC conversation + through the pipe — the door adds nothing. This is the + loopback-only / wrapper-aware shape. + - **The cred-bearing door** (RFC 1929): the door terminates the + local RFC conversation itself — `accept_password_auth`-shaped + (the forked surface: `Socks5ServerProtocol::start` → + `negotiate_auth(&[PasswordAuthentication])` → + `read_username_password` → check → `accept`/`reject`), then + maps the accepted credential through the **static config + table** (`user:pass →` channel identity/endpoint selection, + ADR-005) and **re-originates the SOCKS conversation in-band** + via the consumer sessions (`Socks5ChannelSession::connect` / + `BindSession::bind`), piping the vanilla client's post-auth + bytes onto the session's pipe. **Do NOT pipe raw after auth**: + the vanilla client's command bytes were consumed by the door's + own RFC 1929 half; the in-band path re-speaks them from the + parsed target. The SOCKS password never crosses the channel as + a SOCKS password; the channel identity never reaches the + vanilla client. + - **The identity mechanism**: the credential→identity mapping + yields an alkcall `Identity` (scopes included); the door opens + the producer channel with it. The landed consumer sessions take + `&ChannelClient` — the door's per-credential identity rides the + open-op call's `auth_token` payload (CF-005 precedence (1): + payload `auth_token` → `identity_provider` resolution) or a + per-credential `ChannelClient` — decide at implementation + against the landed alkcall 0.8.0 surface + (`CallConnection::call_with_payload` carries `auth_token`; + `ServingConfig.identity_provider` resolves it producer-side) + and record the choice in Notes. The door checks credentials + itself (no forked `AuthMethod` impl needed — ADR-005). + - **The BIND door**: same listener shape; a vanilla BIND + conversation rides `BindSession` (reply#1's advertised address + passes through to the vanilla client unchanged — the + conversation is the client's own; the door adds nothing, + consumer.md §The BIND door's reply#2 fidelity). The BIND door + rides the same door variant split (NoAuth raw-pipe vs + cred-bearing re-origination). + - **The accept path owns the idle defense** (ADR-014): an + accepted connection that sends no first byte within a + configured window is closed door-side (an accepted socket is + not a channel — the cap regime does not bound it); plus + per-credential/per-peer session caps and accept-queue bounds as + constructor config. Keep the v1 shape simple: an idle-first-byte + timeout + a max-concurrent-sessions cap are the two knobs. + - **Door variant selection** is constructor config (ADR-005). + - Builder shape per consumer.md §Wiring: `FrontDoor::builder() + .credentials(cred_table).bind("127.0.0.1:1080").build()` → + `serve(channel_client)` (the NoAuth door omits `.credentials`). + `serve` runs the accept loop; per-connection failures are + logged + isolated (the loop continues — the alktunnels + per-accept isolation posture). +2. **Tests** (`tests/door-tcp.rs`): a vanilla RFC 1928 client (the + differential-suite raw-client pattern) through the NoAuth door → + CONNECT round-trip end to end (the POC #1 curl shape); the + cred-bearing door: good creds → RFC 1929 success → CONNECT + round-trip; bad creds → RFC 1929 rejection, no channel opened + (assert via the identity witness / channel count); the BIND door: + a vanilla BIND conversation through the door (the POC #5 door + shape); the idle-first-byte timeout closes a silent connection; + the session cap refuses beyond the bound; per-connection failure + isolation (one dead client doesn't kill the door). + +## Acceptance Criteria + +- [ ] `cargo test` passes; `cargo test --all-features` passes +- [ ] `cargo clippy --all-targets -- -D warnings` clean; `cargo fmt + --check` clean +- [ ] Default wasm checks stay green (door code is `local`-gated) +- [ ] The NoAuth door is a literal raw pipe (zero protocol + translation) — pinned by test +- [ ] The cred-bearing door terminates RFC 1929 locally and + re-originates in-band (never pipes raw post-auth) — pinned by + test (a vanilla client's command bytes must not reach the + producer as greeting bytes) +- [ ] The credential→identity mapping is the static config table + (ADR-005); the password never crosses the channel as a SOCKS + password — pinned by test (the producer's establisher witness + sees the mapped identity, never the password) +- [ ] The idle-first-byte timeout + session cap land as constructor + config — pinned by tests +- [ ] Per-connection failure isolation — pinned by test + +## References + +- docs/architecture/decisions/005-auth-split-by-door.md (the door + auth posture), 014-idle-timeout-posture.md (the door's accept-path + defense), 003 (the RFC codec at doors only) +- docs/architecture/consumer.md §The Front Doors, §Wiring +- docs/architecture/overview.md §Module Map (`door.rs`) +- POC references: `/workspace/alksocks-connect-poc/tests/ + connect_poc.rs` (`front_door_raw_pipe_vanilla_client`), `/workspace/ + alksocks-bind-poc/tests/bind_poc.rs` (`bind_vanilla_front_door`) +- Landed surfaces: `src/consumer.rs` (the session types), + `src/rfc1928/server.rs` (`accept_password_auth` — the forked + cred-bearing shape), alkcall CF-005 (`auth_token` precedence) + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/implementation/door-udp.md b/tasks/implementation/door-udp.md new file mode 100644 index 0000000..3d3566d --- /dev/null +++ b/tasks/implementation/door-udp.md @@ -0,0 +1,91 @@ +--- +id: implementation/door-udp +name: Front doors — the UDP door (the RFC codec boundary) +status: pending +depends_on: [implementation/door-tcp] +scope: narrow +risk: medium +impact: component +level: implementation +--- + +## Description + +The consumer's UDP front door (feature `local`): a real UDP socket +exposing an ASSOCIATE association to vanilla clients — the POC #2 +test-8 shape. The RFC UDP-header codec lives ONLY at this boundary +(ADR-003); the in-band wire carries `{addr, data}` frames. + +Deliverables: + +1. **`src/door.rs` additions** (feature `local`) — the UDP door: + - Bind a real UDP socket (the relay socket — the only place a + real address exists; the vanilla client `sendto()`s it). + - Open the association in-band via `AssociateSession::associate` + (the reply's sentinel BND.ADDR is received and never used — + the handle is the channel, ADR-003). + - **The datagram pump** (two pumps, joined — the ADR-050 + two-pump contract): socket→channel decodes the RFC header + (`parse_udp_request`), **FRAG ≠ 0 → refuse** (drop the + datagram; the v1 posture — fragmentation cannot be expressed + in-band), sends `{addr, data}` in-band + (`send_datagram`); channel→socket encodes the reply header + (`new_udp_header`) and sends the RFC-header'd datagram back to + the client's source address. Zero payload translation. + - **Association lifetime = channel lifetime** (ADR-003): the + door's association ends when the channel ends; the door's UDP + socket closes with it. A per-client idle bound (no datagrams + within a window → close the association) is the door-side + defense (ADR-014) — constructor config, mirroring the TCP + door's knobs. + - The door's bind address is constructor config (consumer.md: + "the UDP door's bind address is assembly-layer configuration"). + - Surface shape: follow the TCP door's builder (`FrontDoor` + additions or a sibling `UdpFrontDoor` — decide at + implementation against the landed `door.rs` shape and record + in Notes; the spec leaves the exact constructor shape open). +2. **Tests** (`tests/door-udp.rs`): the POC #2 test-8 shape end to + end — a real UDP echo target, the producer with `local::udp_ + egress()`, the door, a vanilla RFC-header'd datagram in → the + echo back RFC-header'd (the vanilla client simulated at the RFC + boundary); FRAG ≠ 0 refused (dropped, association stays healthy); + the empty-datagram invariant through the door (a zero-length + payload round-trips, never EOF — F-2); the idle bound closes an + idle association; a fresh association succeeds after teardown. + +## Acceptance Criteria + +- [ ] `cargo test` passes; `cargo test --all-features` passes +- [ ] `cargo clippy --all-targets -- -D warnings` clean; `cargo fmt + --check` clean +- [ ] Default wasm checks stay green (door code is `local`-gated) +- [ ] The RFC UDP-header codec appears only at the door boundary + (in-band stays `{addr, data}` frames) — pinned by test +- [ ] FRAG ≠ 0 refused at the door — pinned by test +- [ ] The empty datagram (zero-length payload) round-trips through + the door, never EOF — pinned by test (the F-2 invariant at the + vanilla boundary) +- [ ] Association lifetime = channel lifetime; teardown leaves the + connection healthy — pinned by test +- [ ] The idle bound is constructor config — pinned by test + +## References + +- docs/architecture/decisions/003-udp-handle-swap.md (the RFC codec + at doors only), 014-idle-timeout-posture.md +- docs/architecture/consumer.md §The Front Doors (the UDP door), + §Wiring +- POC reference: `/workspace/alksocks-udp-poc/tests/udp_poc.rs` + (`udp_front_door_vanilla_client_round_trip` — the pump shape) +- Landed surfaces: `src/consumer.rs` (`AssociateSession` — + `send_datagram`/`recv_datagram`/`split`), `src/rfc1928/mod.rs` + (`new_udp_header`/`parse_udp_request`), `src/wire.rs` (the frame + codec) + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/implementation/local-backend-producer.md b/tasks/implementation/local-backend-producer.md new file mode 100644 index 0000000..c91d350 --- /dev/null +++ b/tasks/implementation/local-backend-producer.md @@ -0,0 +1,93 @@ +--- +id: implementation/local-backend-producer +name: Producer-side local backend — the producer's front door (vanilla clients → a locally-served SOCKS5 resource) +status: pending +depends_on: [implementation/local-providers] +scope: narrow +risk: low +impact: component +level: implementation +--- + +## Description + +The producer-side local backend (feature `local`): the same door +shape in the other direction — the producer serves from a real +listener, so vanilla clients reach a *locally-served* SOCKS5 resource +(producer.md §Producer-Side Local Backend). This is the shape where +the SOCKS5 service rides a real `TcpListener` instead of (or alongside) +channels: each accepted TCP connection is handed to the landed +substrate-free entry `Socks5Producer::serve_socks5_over` directly — +the RFC conversation runs on the accepted socket, the seams dial +locally. + +Deliverables: + +1. **`src/local/` additions** (feature `local`) — the producer-side + listener shape: + - A `serve_local`-shaped constructor on the local module (exact + name at implementation; producer.md names no symbol — record + the choice in Notes): takes a `Socks5Producer` (the landed + builder type), a bind address, and the door-policy knobs (the + idle-first-byte timeout + max-concurrent-sessions cap — the + same two knobs the consumer door carries, ADR-014's + accept-path defense; an accepted socket is not a channel). + - The accept loop: bind the listener, per accepted connection + spawn `producer.serve_socks5_over(stream)`; per-connection + failures are logged + isolated (the loop continues). The + listener's lifetime is the caller's (a `close`/Drop shape — + the alktunnels `TunnelListener` teardown posture). + - **This is a front door for the producer side**: the door + surface (NoAuth raw pipe vs RFC 1929 cred-bearing, ADR-005) is + the consumer door's code shape. For the producer-side backend + the NoAuth shape is the primary one (the producer IS the RFC + conversation here — `serve_socks5_over` runs `accept_no_auth` + in-stream); a cred-bearing variant would wrap + `accept_password_auth` before `serve_socks5_over` — land the + NoAuth shape; the cred-bearing producer-side door is a + documented follow-up unless a test drives it (record the + decision in Notes). + - The BIND command gate on this path is the builder's + `bind_policy` (the landed `Socks5ProducerBuilder::bind_policy` + — the substrate-free entry's gate); document that the + per-caller gate (the pump handler's identity-derived one) does + not apply here — this path has no open-op identity. +2. **Tests** (`tests/local-backend-producer.rs`): a vanilla RFC 1928 + client against the locally-served producer — CONNECT round-trip + through `local::tcp_dial()` seams (the whole stack on real + sockets); BIND through the local backend (the faithful-RFC + variant: real reply#1/reply#2 addresses); the idle-first-byte + timeout; the session cap; per-connection failure isolation; the + `bind_policy(false)` gate refuses BIND RFC-faithfully (0x02). + +## Acceptance Criteria + +- [ ] `cargo test` passes; `cargo test --all-features` passes +- [ ] `cargo clippy --all-targets -- -D warnings` clean; `cargo fmt + --check` clean +- [ ] Default wasm checks stay green (backend code is `local`-gated) +- [ ] The backend serves via `Socks5Producer::serve_socks5_over` (the + landed substrate-free entry) — no new protocol-layer code +- [ ] The accept path owns the idle defense + session cap (the two + knobs) — pinned by tests +- [ ] Per-connection failure isolation — pinned by test +- [ ] The `bind_policy` gate is honored on this path — pinned by test + +## References + +- docs/architecture/producer.md §Producer-Side Local Backend, §Wiring +- docs/architecture/decisions/005-auth-split-by-door.md (the door + surface), 014-idle-timeout-posture.md, 006 (the bind gate) +- Landed surfaces: `src/producer.rs` (`Socks5Producer`/ + `Socks5ProducerBuilder::bind_policy`/`serve_socks5_over`), + `src/local/` (the providers from local-providers) +- POC reference: `/workspace/alksocks-connect-poc/src/producer.rs` + (the `serve_socks5_over` shape the POC validated) + +## Notes + +> To be filled by implementation agent + +## Summary + +> To be filled on completion \ No newline at end of file diff --git a/tasks/implementation/review-assembly-layer.md b/tasks/implementation/review-assembly-layer.md new file mode 100644 index 0000000..65d7714 --- /dev/null +++ b/tasks/implementation/review-assembly-layer.md @@ -0,0 +1,95 @@ +--- +id: review-assembly-layer +name: Review — the assembly layer (the batch-2 gate) +status: pending +depends_on: [implementation/assembly-integration-suite] +scope: narrow +risk: low +impact: project +level: review +--- + +## Description + +Review checkpoint closing batch 2 (the assembly layer: `local` +providers, the front doors, the producer-side local backend, +`composed` providers). After this gate the crate is feature-complete +against the Phase 1 spec set; the remaining work is release polish +(publish prep, the OQ-SK2-08 tuning note). + +Review against the architecture, in this order: + +1. **The never-binds posture** (the crate's defining requirement): + the protocol layer still binds nothing — every kernel bind lives + in `local`-gated code, is an explicit constructor decision, and + the default crate's dependency graph carries no `tokio/net` + (`cargo tree` spot-check). The doors' binds are the doors' own + (assembly-layer, ADR-009). +2. **Seam-contract compliance by the providers** (ADR-004): the + readiness contract honored by both egress providers (real async + parks; no hot `Ok(None)`); ownership rides the handle + structurally (the composed owner-wrapper inside the payloads; no + `_` bindings of owning values); the accept future owns its + resources end-to-end (both accept variants); framing-once (the + providers speak inner bytes; no double prefix). +3. **Door fidelity** (ADR-005/ADR-003): the NoAuth door is a literal + raw pipe; the cred-bearing door terminates RFC 1929 locally and + re-originates in-band (never pipes raw post-auth); the password + never crosses the channel as a SOCKS password; the RFC UDP-header + codec appears only at the door boundary; FRAG ≠ 0 refused. +4. **The graduation's shape** (ADR-007 variant 2): the composed + accept advertises the real `bound` and the real `peer` — no + expected-addr stand-in; the POC's superseded workaround is not + resurrected. +5. **Convention compliance** (AGENTS.md): no `unwrap`/`expect`/ + panics outside tests; `thiserror` surfaces; no comments in code + except doc comments + pinned non-obvious-correctness notes; + naming; module structure (feature-gated backend modules never + imported from the protocol/adapter/client modules — convention + 15's last line); producer/consumer vocabulary. +6. **Feature-gate hygiene** (ADR-009): `local` brings only + `tokio/net` (+ `socket2` if justified); `composed` brings only + alktunnels; the default graph unchanged; the wasm checks green + for default + composed; `local` on wasm fails to build (the + alktty posture — verify the failure is the expected one). +7. **POC-fidelity spot-check**: for two or three of the trickiest + assembly behaviors (the composed egress's LRU eviction + closed- + queue posture, the cred-bearing door's re-origination, the + bind-first correlation), diff the crate's behavior against the + POC crates' findings — the POCs are the empirical record; drift + is a finding (except where an ADR supersedes the POC's shape — + the bind-first graduation is the known one). +8. **Docs sync**: the specs match what landed (the integration + task's reconciliation verified; any residue is a finding). + +## Notes + +> To be filled by the review agent — see the Summary and the Findings. + +## Acceptance Criteria + +- [ ] The never-binds posture verified (protocol layer + default + graph) +- [ ] Seam-contract compliance verified for every provider +- [ ] Door fidelity verified (both variants + the UDP boundary) +- [ ] The graduation's shape verified (no stand-ins) +- [ ] Convention compliance swept +- [ ] Feature-gate hygiene verified (all four verification targets) +- [ ] POC-fidelity spot-check of the trickiest assembly behaviors +- [ ] Docs sync verified; findings recorded; the gate to release + polish opens + +## References + +- docs/architecture/overview.md, wire.md, producer.md, consumer.md + (the specs under review) +- docs/architecture/decisions/003/004/005/006/007/008/009/010/012/014 +- POC crates: `/workspace/alksocks-{connect,udp,bind}-poc` (the + fidelity sources) +- AGENTS.md (conventions 1–17; the verification commands) +- The batch-1 review precedent: + `tasks/implementation/review-protocol-core.md` + +## Summary + +> To be filled on completion \ No newline at end of file