Files
alktunnels/tasks/tunnels/local-socket-halves.md
T
glm-5.3-flash 69498b79cc tasks: Phase 2 decomposition — 12-task dependency graph for v1
tasks/architecture/:
- oq-promotion-sync (planning): back-pointers from the phase-0 ledger
  + AGENTS.md to the promoted OQ tracker (the convergence checklist's
  final half)
- oq-tn-14-tracker: the Safe-Exit external-trigger tracker task for
  OQ-TN-14 (unix/stdio placement; [external-trigger, deferred-oq],
  risk trivial, level research per the two-halves rule)

tasks/tunnels/ (the implementation graph, 8 generations):
- crate-init: module skeleton per overview.md's module map
- params: TunnelParams + open-op spec (ADR-001 wire-stable surface)
- wire-codec: frame_datagram/DatagramReader + the 8 POC-pinned test
  families (ADR-003)
- producer-open-op: establisher (dial, plan flow R-01) + pump handler
  (pump_bidi inline R-02) + registration; POC-ported integration tests
- consumer-session: TunnelSession (open/adopt, data planes, teardown
  matrix — ADR-005); generalizes the reverse POC's ReverseTunnel
- producer-listen: the listen establisher + AcceptQueue contract
  (ADR-004 shape 2)
- local-socket-halves: the local feature (TCP/UDP/unix halves
  functions; truncation fail-loud per OQ-TN-13; unix ships per
  OQ-TN-14's lean-yes, stdio deferred)
- review-core-crates: review-injection point before the downstream
  tasks build on the high-risk producer/consumer shapes
- end-to-end-suite: 6 suites / >=20 tests consolidating both POC
  suites against the public API (the spec's executable form)
- review-impl: the phase-gate review (wire/API/conventions/docs sync;
  findings doc per the alkhttp/alkcall house pattern)

Graph verified with taskgraph: 12 tasks valid, no cycles, 8
generations; critical path = oq-promotion-sync -> crate-init ->
params -> wire-codec -> producer-open-op -> consumer-session ->
review-core-crates -> review-impl; risk concentrated in the two
session tasks (both POC-validated); parallel groups available at
generations 1 and 6
2026-09-07 19:07:20 +00:00

105 lines
3.8 KiB
Markdown

---
id: tunnels/local-socket-halves
name: "local feature — real socket halves functions (TCP dial, UDP connect, unix)"
status: pending
depends_on: [tunnels/producer-open-op, tunnels/wire-codec]
scope: moderate
risk: medium
impact: component
level: implementation
tags: [local, backend, sockets, feature-gate]
---
## Description
Add the `local` feature module (`src/local/`) per overview.md §Feature
Gates + ADR-004: the real-socket halves functions the assembly layer
injects as `DialFn`/`AcceptFn` closures. The POCs' substrate adapters
are the reference (they ran all of this); the task is packaging them
into the feature-gated module with the crate's conventions.
### Surface
```rust
// src/local/mod.rs (feature = "local"; non-wasm by design)
pub async fn dial_tcp(target: &str) -> Result<TargetHandle, TunnelEstablishError>;
// TcpStream::connect (5s timeout per the POCs) → into_split → boxed halves
pub async fn connect_udp(target: &str) -> Result<TargetHandle, TunnelEstablishError>;
// UdpSocket::bind(("0.0.0.0", 0)) → connect(target) → the FRAMED adapter
// (UdpHalf + codec — ADR-003; truncation fail-loud per OQ-TN-13)
pub async fn dial_unix(path: &str) -> Result<TargetHandle, TunnelEstablishError>;
// UnixStream::connect → into_split (OQ-TN-14: ships with local v1 — cheap,
// same halves shape as TCP)
pub struct TcpListenerHalves { listener: tokio::net::TcpListener }
impl TcpListenerHalves {
pub async fn bind(addr: &str) -> Result<Self, ...>;
pub fn accept_fn(&self, queue: &AcceptQueue) -> impl Future; // the accept loop feeding the queue
}
pub struct UdpAssociateHalves { sock: Arc<UdpSocket> } // the POC's associate shape
```
- **UDP adapter (`UdpHalf`)**: port from the forward POC
(`params.rs`'s `UdpHalfRead`/`UdpHalfWrite`) with the truncation fix:
`poll_recv` into a too-small caller buffer must fail loud
(OQ-TN-13's resolved posture — ADR-003). The codec wraps at this
boundary; the pump never sees UDP specifics.
- **Error mapping** to `TunnelEstablishError` variants (the POC's
`Into<EstablishmentError>` path).
- `TargetHandle` re-used from producer.rs (the halves type lives in
producer.rs; local/ depends on it, never vice versa).
### Feature wiring
```toml
[features]
default = []
local = ["tokio/net"]
```
- `tokio/net` is the only dep addition (no new external crates; the
POCs proved `rt+sync+io-util+macros+time+net` covers everything).
- The default crate stays wasm-clean: `local`-gated code must not be
importable from `producer.rs`/`consumer.rs` (convention 16 — backend
modules are never imported from the shared/producer/consumer
modules).
- Unix is IN v1 (OQ-TN-14's lean-yes posture; same halves shape as
TCP); stdio bridging is NOT (different lifecycle — deferred).
### Tests
Behind the feature: TCP dial round-trip through the real establisher
path, UDP associate + datagram round-trip with the framed adapter,
unix dial round-trip, truncation fail-loud (a short recv surfaces as
an error), the 1400-byte MTU datagram through bounded buffers (the
POC's sizing test).
## Acceptance Criteria
- [ ] `cargo test --features local` green; `cargo test` (default)
unaffected
- [ ] `cargo test --all-features` green (AGENTS.md convention 14)
- [ ] wasm32 check on the DEFAULT crate passes; `local` is non-wasm by
design (documented)
- [ ] Truncation fails loud (the OQ-TN-13 test)
- [ ] No socket type appears outside `src/local/`
- [ ] Clippy/fmt clean
## References
- docs/architecture/overview.md §Feature Gates, §Dependencies
- docs/architecture/decisions/004-no-backend-trait.md (the injection
point), 003 (the framed adapter)
- POC reference: `/workspace/alktunnels-udp-poc/src/producer.rs`
(`UdpHalf`, the dial paths), `params.rs` (the UDP adapter)
## Notes
> Agent fills during implementation.
## Summary
> Agent fills this on completion.