Split the last open substrate-placement question: - Unix: ships with the local feature v1 (dial_unix — same halves shape as TCP; the wire enum already carried unix per ADR-001; the params task's schema list includes all three values) - Stdio: OUT of scope — a spawned process's stdin/stdout/stderr IS alktty's pipe mode (LocalTtyBackend + tokio::process + Stdio::piped, alktty tty-local.md): three multiplexed logical streams + the exit-code control chunk (alktty ADR-004) + signal forwarding (REQ-TTY-02). A stdio bridge here would be alktty's runner mode with the terminal stripped out — a strictly worse duplicate that also drops the semantics that matter (a byte tunnel has neither exit codes nor signals). Remote command execution composes via alktty on the same channels substrate. Updated: open-questions.md OQ-TN-14 (resolved), overview.md feature gate + deps + OQ summary, producer.md OQ ref, OQ-TN-10 promotion (#3 split), phase-0-findings + both POC summaries' resolution notes, params task (schema enum includes unix), local-socket-halves task (unix ships, stdio does NOT — with the composition rationale), oq-tn-14-tracker task repurposed (boundary-maintenance: re-opens only if a consumer needs stdio-without-process-semantics — which would need its own ADR, or if the alktty/alktunnels boundary needs sharpening). Verified: taskgraph valid (12 tasks, no cycles)
109 lines
4.1 KiB
Markdown
109 lines
4.1 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.
|
|
- **Unix**: `dial_unix` ships (OQ-TN-14 resolved — same halves shape
|
|
as TCP). **Stdio does NOT** — a spawned process's stdio is alktty's
|
|
pipe mode (exit codes + signals are tty semantics, not tunnel
|
|
semantics); remote command execution composes via alktty, not here.
|
|
- **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. |